Node.js Ffmpeg-如何强制MJPEG输出整个帧?

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

我正在使用ffmpeg处理从远程摄像机传入的MPEGTS流,并使用我的应用程序将其发送给多个客户端

从技术上讲,我使用ffmpeg将传入流转换为MJPEG输出,并将数据块(来自ffmpeg进程stdout)管道化到客户端http响应上的可写流

然而,我面临一个问题——并非所有的数据块都代表一个完整的“整体”框架。因此,在浏览器中将它们显示为一行会导致视频闪烁,随机显示半完整的帧。 我知道这一点,因为当打印每个区块长度时,大多数情况下都会产生一个大值(X),但有时我会得到两个连续区块,长度(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标准输出会产生大小不一的数据块,这意味着它们并不总是代表整个帧(完整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: