海康sdk实时音视频流剥离-视频流(二)

1、需求

上一篇写了海康sdk获取音频流,后来我发现了更好的实现方式。先说下使用sdk的限制,单设备可正常获取数据,多设备情况下,核心的HCNetSDK.FRealDataCallBack_V30 demo里有,经过咨询海康工程师,这个类多设备情况需要做成单例,这样注册的设备都可以通过回调方法invoke获取实时音视频流,那么怎么区分呢, 可以通过Pointer pBuffer 这个对象。总而言之,复杂不说,这种多设备实现下,还会产生给的pBuffer对象内设备标识乱码情况。懒得问海康工程师了,麻烦!

后续我通过JavaCV拉取RTSP流实现。不需要手动解码,而且代码层面易于维护,非常方便。

2、代码

我使用JavaCV1.5.9版本,摄像头音视频格式,设置H264,AAC,实测PCM音频格式JavaCV无法解析。

ini 复制代码
public void run(ApplicationArguments args) {
        if (!enabled) return;
        String[] rstpArr = {
            "rtsp://admin:hk123456@192.168.1.2:554/ISAPI/streaming/audio/1"
        };
        for (int i = 0; i < rstpArr.length; i++) {
            String rtspUrl = rstpArr[i];
            processCameraStream(rtspUrl, "camera-" + i);
        }
    }
    
    
// 自定义线程池
    private ExecutorService saveWhisperAlarmExecutorService = new ThreadPoolExecutor(
        50, // 核心线程数
        100, // 最大线程数
        60, // 空闲线程存活时间
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(1000), // 有界队列,避免OOM
        new ThreadPoolExecutor.CallerRunsPolicy() // 队列满时,由调用线程直接执行任务
    );
    
 // 核心代码
 public void processCameraStream(String rtspUrl, String cameraId) {
        executorService.submit(() -> {
            FFmpegFrameGrabber grabber = null;
            FFmpegFrameRecorder recorder = null;

            try {
                grabber = new FFmpegFrameGrabber(rtspUrl);
                grabber.setOption("rtsp_transport", "tcp"); // 使用TCP传输,避免UDP丢包
                grabber.setAudioChannels(1); // 设置音频通道,1通道
                grabber.setSampleRate(16000); // 设置音频采样率,16000
                grabber.setAudioCodecName("aac"); //设置编码格式,AAC

                grabber.start();

               

                 Frame frame;
                 while ((frame = grabber.grab()) != null) {
		                 recorder.record(frame);
                                                                                         if(frame == null){
            break;
        }
        // 保存wav格式音频文件,用于测试
        String filePath = "./cache/test.wav"
                                                                                         recorder = new FFmpegFrameRecorder(filePath, 1);
        recorder.setSampleRate(16000);
        recorder.setAudioCodec(avcodec.AV_CODEC_ID_PCM_S16LE);
        recorder.setFormat("wav");
        recorder.start();

        // 音频流
        if (frame.samples != null) {
            if (frame.samples != null && frame.samples[0] instanceof ShortBuffer) {
                ShortBuffer shortBuffer = (ShortBuffer) frame.samples[0];// 使用ShortBuffer接收,而非ByteBuffer
                byte[] aacData = new byte[shortBuffer.remaining() * 2]; // 每个 short 占 2 个字节
                ByteBuffer byteBuffer = ByteBuffer.wrap(aacData); //转换为ByteBuffer
            }
        }

        // 视频流,这里获取I帧转成图片,仅用于测试
                                                                                         BufferedImage bufferedImage = null;
        if (frame.keyFrame) {
            bufferedImage = converter.getBufferedImage(frame);
        }else {
            // 使用 grabImage() 获取完整图像
            Frame fullFrame = grabber.grabImage();
            bufferedImage = converter.getBufferedImage(fullFrame);
        }
        // 存储,用于测试
        File outputFile = new File("./cache/test.jpg");					ImageIO.write(bufferedImage, "jpg", outputFile);
        }
         } catch (Exception e) {
         log.error("rtsp取流异常:", e);
                    } finally {
                        try {
                            if (recorder != null) recorder.release();
                            if (recorder != null) recorder.stop();
                            if (grabber != null) grabber.stop();
                        } catch (Exception e) {
                            log.error("rtsp资源关闭异常:", e);
                        }
                    }
                });
            }

3、总结

以上简单几十行代码即可分别获取摄像头的视频流,音频流,稍作处理就可以供后续业务流程,想想之前用sdk拿音视频流,对比一下, 真是泪目。代码中我使用了线程池,是为了提高并发,同时我加了个参数"camera-" + i,后续可以将线程池的业务代码封装出去,接收一个摄像头标识数据,例如ip或自有的数据库id等,这样就可做到区分摄像头数据。

4、JavaCV包体积过大

pom引入,会将所有平台的包都放进去,需要做的就是引入需要的包就行。相关资料,无论ai还是网上都比较蛋疼,javacv不同版本,内部集成版本差异也大。我的引入如下,需要什么平台引入什么平台,瘦身前jar包1个G,瘦身后jar包200M左右。

xml 复制代码
<properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <javacv.version>1.5.9</javacv.version>
        <javacv.opencv.version>4.7.0-1.5.9</javacv.opencv.version>
        <javacv.ffmpeg.version>6.0-1.5.9</javacv.ffmpeg.version>
        <javacv.openblas.version>0.3.23-1.5.9</javacv.openblas.version>
        <javacpp.platform.android-arm>android-arm</javacpp.platform.android-arm>
        <javacpp.platform.android-arm64>android-arm64</javacpp.platform.android-arm64>
        <javacpp.platform.android-x86>android-x86</javacpp.platform.android-x86>
        <javacpp.platform.android-x86_64>android-x86_64</javacpp.platform.android-x86_64>
        <javacpp.platform.ios-arm>ios-arm</javacpp.platform.ios-arm>
        <javacpp.platform.ios-arm64>ios-arm64</javacpp.platform.ios-arm64>
        <javacpp.platform.ios-x86>ios-x86</javacpp.platform.ios-x86>
        <javacpp.platform.ios-x86_64>ios-x86_64</javacpp.platform.ios-x86_64>
        <javacpp.platform.macosx-x86_64>macosx-x86_64</javacpp.platform.macosx-x86_64>
        <javacpp.platform.linux-armhf>linux-armhf</javacpp.platform.linux-armhf>
        <javacpp.platform.linux-arm64>linux-arm64</javacpp.platform.linux-arm64>
        <javacpp.platform.linux-ppc64le>linux-ppc64le</javacpp.platform.linux-ppc64le>
        <javacpp.platform.linux-x86>linux-x86</javacpp.platform.linux-x86>
        <javacpp.platform.linux-x86_64>linux-x86_64</javacpp.platform.linux-x86_64>
        <javacpp.platform.windows-x86>windows-x86</javacpp.platform.windows-x86>
        <javacpp.platform.windows-x86_64>windows-x86_64</javacpp.platform.windows-x86_64>
    </properties>
    
    
  <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacpp</artifactId>
            <version>${javacv.version}</version>
            <classifier>${javacpp.platform.windows-x86_64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>openblas</artifactId>
            <version>${javacv.openblas.version}</version>
            <classifier>${javacpp.platform.windows-x86_64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>opencv</artifactId>
            <version>${javacv.opencv.version}</version>
            <classifier>${javacpp.platform.windows-x86_64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>ffmpeg</artifactId>
            <version>${javacv.ffmpeg.version}</version>
            <classifier>${javacpp.platform.windows-x86_64}</classifier>
        </dependency>
        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv</artifactId>
            <version>${javacv.version}</version>
        </dependency>
相关推荐
zopple2 小时前
常见的 Spring 项目目录结构
java·后端·spring
cjy0001114 小时前
springboot的 nacos 配置获取不到导致启动失败及日志不输出问题
java·spring boot·后端
小江的记录本5 小时前
【事务】Spring Framework核心——事务管理:ACID特性、隔离级别、传播行为、@Transactional底层原理、失效场景
java·数据库·分布式·后端·sql·spring·面试
sheji34165 小时前
【开题答辩全过程】以 基于springboot的校园失物招领系统为例,包含答辩的问题和答案
java·spring boot·后端
程序员cxuan5 小时前
人麻了,谁把我 ssh 干没了
人工智能·后端·程序员
wuyikeer6 小时前
Spring Framework 中文官方文档
java·后端·spring
Victor3566 小时前
MongoDB(61)如何避免大文档带来的性能问题?
后端
Victor3567 小时前
MongoDB(62)如何避免锁定问题?
后端
wuyikeer7 小时前
Spring BOOT 启动参数
java·spring boot·后端
子木HAPPY阳VIP8 小时前
Ubuntu 22.04 VMware 设置固定IP配置
人工智能·后端·目标检测·机器学习·目标跟踪