我正在与ffmpeg一起处理来自远程摄像机的传入MPEGTS流,并使用我的应用将其传送到多个客户端。
[从技术上讲,我正在使用ffmpeg将输入流转换为MJPEG输出,并将数据块(来自ffmpeg进程stdout)通过管道传输到客户端http响应上的可写流。
但是,我面临一个问题-并非所有数据块都代表完整的“整个”框架。因此,将它们在浏览器中连续显示会导致视频随机闪烁,且帧数为半完整。我知道这一点是因为打印每个块的长度时,大多数时候结果都是一个大值(X),但是我时不时地得到2个连续的块,其长度为(2 / 5X),然后是(3 / 5X)。
所以问题-是否有一种方法可以强制ffmpeg进程仅输出整个帧?如果没有,我是否有办法“手动”检查每个数据块并寻找标题/元数据/标志来指示帧的开始/结束?
我的用于输出MJPEG的ffmpeg命令是:
ffmpeg -i - -c:v mjpeg -f mjpeg -
解释:
“-i-” :(输入)是进程的标准输入(不是静态文件)
“-c:v mjpeg”:使用mjpeg编解码器
“-f mjpeg”:输出将为mjpeg格式
“-”:未指定输出(文件或url)-将是进程标准输出
编辑:这是一些console.log打印以可视化问题:
%%% FFMPEG Info %%%
frame= 832 fps= 39 q=24.8 q=29.0 size= 49399kB time=00:00:27.76 bitrate=14577.1kbits/s speed=1.29x
data.length: 60376
data.length: 60411
data.length: 60465
data.length: 32768
data.length: 27688
data.length: 32768
data.length: 27689
data.length: 60495
data.length: 60510
data.length: 60457
data.length: 59811
data.length: 59953
data.length: 59889
data.length: 59856
data.length: 59936
data.length: 60049
data.length: 60091
data.length: 60012
%%% FFMPEG Info %%%
frame= 848 fps= 38 q=24.8 q=29.0 size= 50340kB time=00:00:28.29 bitrate=14574.4kbits/s speed=1.28x
data.length: 60025
data.length: 60064
data.length: 60122
data.length: 60202
data.length: 60113
data.length: 60211
data.length: 60201
data.length: 60195
data.length: 60116
data.length: 60167
data.length: 60273
data.length: 60222
data.length: 60223
data.length: 60267
data.length: 60329
%%% FFMPEG Info %%%
frame= 863 fps= 38 q=24.8 q=29.0 size= 51221kB time=00:00:28.79 bitrate=14571.9kbits/s speed=1.27x
如您所见,整个帧大约为60k(我的指示是我正在浏览器中查看的干净视频流),但是输出时不时地包含2个连续的块,总计约60k。当交付给浏览器时,它们是“半帧”。
在此处以及在StackExchange上的注释之后,似乎ffmpeg进程输出的MJPEG流应该由整个帧组成。收听ffmpeg ChildProcess stdout会产生大小不同的数据块-这意味着它们并不总是代表整个帧(完整JPEG)图像。
因此,我不只是将它们推送给消费者(当前是一个显示视频流的Web浏览器),而是编写了一些代码来处理内存中的“半块”并将它们附加在一起,直到框架完成。
这似乎解决了问题,因为我在视频中没有出现闪烁。
private pushWholeMjpegFrame(data: any): void {
try {
const decimalArray = new Uint8Array(data.buffer);
const tail: string = `${decimalArray[decimalArray.length - 2]}_${decimalArray[decimalArray.length - 1]}`;
if (!this.tempDataChunk) {
if (tail === this.JPEG_END_FLAG) {
this.push(data);
this.tempDataChunk = undefined;
return;
} else {
this.tempDataChunk = Buffer.from(data);
return;
}
} else {
this.tempDataChunk = Buffer.concat([this.tempDataChunk, data]);
if (tail === this.JPEG_END_FLAG) {
this.push(this.tempDataChunk);
this.tempDataChunk = undefined;
return;
}
return;
}
} catch (e) {
this.push(data);
AppLogger.error(`pushWholeMjpegFrame error: ` + e.message ? e.message : e, this.TAG);
return;
}}
this.tempDataChunk
表示一个本地调用成员,以未定义的形式发起,用于在“半块”的情况下保持要与下一个块块一起占用的块缓冲区]
this.tempDataChunk
是类的只读成员,其字符串值为'255_217'
如果可以改进和优化它,我很乐意在解决方案上获得更多有教养的意见,以及有关问题根源的更多知识,也许还有一种使期望的行为脱离现实的方法。装有ffmpeg的盒子,因此,随时可以通过提供更多答案和评论来使这个问题继续存在。