Node.js Ffmpeg-如何强制MJPEG输出整个帧?
我正在使用ffmpeg处理从远程摄像机传入的MPEGTS流,并使用我的应用程序将其发送给多个客户端 从技术上讲,我使用ffmpeg将传入流转换为MJPEG输出,并将数据块(来自ffmpeg进程stdout)管道化到客户端http响应上的可写流 然而,我面临一个问题——并非所有的数据块都代表一个完整的“整体”框架。因此,在浏览器中将它们显示为一行会导致视频闪烁,随机显示半完整的帧。 我知道这一点,因为当打印每个区块长度时,大多数情况下都会产生一个大值(X),但有时我会得到两个连续区块,长度(2/5X)后跟(3/5X) 所以问题是-有没有办法强制ffmpeg进程只输出整个帧?如果没有,我是否有办法“手动”检查每个数据块,并查找指示帧开始/结束的标题/元数据/标志Node.js Ffmpeg-如何强制MJPEG输出整个帧?,node.js,ffmpeg,video-streaming,mjpeg,Node.js,Ffmpeg,Video Streaming,Mjpeg,我正在使用ffmpeg处理从远程摄像机传入的MPEGTS流,并使用我的应用程序将其发送给多个客户端 从技术上讲,我使用ffmpeg将传入流转换为MJPEG输出,并将数据块(来自ffmpeg进程stdout)管道化到客户端http响应上的可写流 然而,我面临一个问题——并非所有的数据块都代表一个完整的“整体”框架。因此,在浏览器中将它们显示为一行会导致视频闪烁,随机显示半完整的帧。 我知道这一点,因为当打印每个区块长度时,大多数情况下都会产生一个大值(X),但有时我会得到两个连续区块,长度(2/5
用于输出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标准输出会产生大小不一的数据块,这意味着它们并不总是代表整个帧(完整JPEG)图像 因此,我编写了一些代码来处理内存中的“半块”,并将它们附加在一起,直到帧完成,而不是将它们推给消费者(目前是一个显示视频流的web浏览器) 这似乎解决了问题,因为视频中没有闪烁
const _SOI = Buffer.from([0xff, 0xd8]);
const _EOI = Buffer.from([0xff, 0xd9]);
private size: number = 0;
private chunks: any[] = [];
private jpegInst: any = null;
private pushWholeMjpegFrame(chunk: any): void {
const chunkLength = chunk.length;
let pos = 0;
while (true) {
if (this.size) {
const eoi = chunk.indexOf(_EOI);
if (eoi === -1) {
this.chunks.push(chunk);
this.size += chunkLength;
break;
} else {
pos = eoi + 2;
const sliced = chunk.slice(0, pos);
this.chunks.push(sliced);
this.size += sliced.length;
this.jpegInst = Buffer.concat(this.chunks, this.size);
this.chunks = [];
this.size = 0;
this.sendJpeg();
if (pos === chunkLength) {
break;
}
}
} else {
const soi = chunk.indexOf(_SOI, pos);
if (soi === -1) {
break;
} else {
pos = soi + 500;
}
const eoi = chunk.indexOf(_EOI, pos);
if (eoi === -1) {
const sliced = chunk.slice(soi);
this.chunks = [sliced];
this.size = sliced.length;
break;
} else {
pos = eoi + 2;
this.jpegInst = chunk.slice(soi, pos);
this.sendJpeg();
if (pos === chunkLength) {
break;
}
}
}
}
}
如果我的解决方案能够得到改进和优化,我希望能得到更多有教育意义的意见,以及更多关于问题根源的知识,或许还有一种通过ffmpeg实现所需行为的方法,
因此,请随时使用更多的答案和评论来保持这个问题的活力。在这里和StackExchange上的评论之后,从ffmpeg过程输出的MJPEG流似乎应该由整个帧组成。收听ffmpeg ChildProcess标准输出会产生大小不一的数据块,这意味着它们并不总是代表整个帧(完整JPEG)图像 因此,我编写了一些代码来处理内存中的“半块”,并将它们附加在一起,直到帧完成,而不是将它们推给消费者(目前是一个显示视频流的web浏览器) 这似乎解决了问题,因为视频中没有闪烁
const _SOI = Buffer.from([0xff, 0xd8]);
const _EOI = Buffer.from([0xff, 0xd9]);
private size: number = 0;
private chunks: any[] = [];
private jpegInst: any = null;
private pushWholeMjpegFrame(chunk: any): void {
const chunkLength = chunk.length;
let pos = 0;
while (true) {
if (this.size) {
const eoi = chunk.indexOf(_EOI);
if (eoi === -1) {
this.chunks.push(chunk);
this.size += chunkLength;
break;
} else {
pos = eoi + 2;
const sliced = chunk.slice(0, pos);
this.chunks.push(sliced);
this.size += sliced.length;
this.jpegInst = Buffer.concat(this.chunks, this.size);
this.chunks = [];
this.size = 0;
this.sendJpeg();
if (pos === chunkLength) {
break;
}
}
} else {
const soi = chunk.indexOf(_SOI, pos);
if (soi === -1) {
break;
} else {
pos = soi + 500;
}
const eoi = chunk.indexOf(_EOI, pos);
if (eoi === -1) {
const sliced = chunk.slice(soi);
this.chunks = [sliced];
this.size = sliced.length;
break;
} else {
pos = eoi + 2;
this.jpegInst = chunk.slice(soi, pos);
this.sendJpeg();
if (pos === chunkLength) {
break;
}
}
}
}
}
如果我的解决方案能够得到改进和优化,我希望能得到更多有教育意义的意见,以及更多关于问题根源的知识,或许还有一种通过ffmpeg实现所需行为的方法,
所以,请随时用更多的答案和评论来保持这个问题的活力。我也遇到了同样的问题,最后来到这里。正如其他人所说,这种ffmpeg行为是通过设计实现的,并且这个问题可以在ffmpeg之外轻松解决,正如OP所示。考虑FFMPEG输出是一个流。和一般的流一样,内容是以片段的形式发送的。这使得数据流更加一致,因为块的大小与每个帧的大小没有直接关系。它允许吞吐量在某种程度上是一致的(相对于其相邻块),即使在压缩方案导致一些帧由于运动、纯色等而在大小上完全不同时也是如此
OP的回答为我指明了正确的方向,我编写了自己的略为简单的实现,用于在香草ES6中构建完整的JPG图像。如果这对其他人有帮助,以下几点对我来说很好。它将ffmpeg mjpeg块通过管道传输到标准输出,并查找SOI和EOI标记(请参阅),以构建完整的base64 JPG图像,以供在中使用。我遇到了同样的问题,并在这里结束。正如其他人所说,这种ffmpeg行为是通过设计实现的,并且这个问题可以在ffmpeg之外轻松解决,正如OP所示。考虑FFMPEG输出是一个流。和一般的流一样,内容是以片段的形式发送的。这使得数据流更加一致,因为块的大小与每个帧的大小没有直接关系。它允许吞吐量在某种程度上是一致的(相对于其相邻块),即使在压缩方案导致一些帧由于运动、纯色等而在大小上完全不同时也是如此
OP的回答为我指明了正确的方向,我编写了自己的略为简单的实现,用于在香草ES6中构建完整的JPG图像。如果这对其他人有帮助,以下几点对我来说很好。它将ffmpeg mjpeg块通过管道传输到标准输出,并查找SOI和EOI标记(请参阅),以构建完整的base64 JPG图像,供添加
-线程类型帧使用并检查。我实际上将传入流映射到2个输出-1用于以30秒的时间在本地磁盘上写入文件(所有文件都播放完美),另一个是mjpeg输出流。那么我应该在哪里补充你的建议呢?在请求整个命令时,或在“-map”上指定mjpeg?共享您的完整命令。ffmpeg-i--map 0:v-c:v mjpeg-f mjpeg--map 0:v-c: