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:[email protected]: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>