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

问题描述 投票:1回答:1

我正在与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。当交付给浏览器时,它们是“半帧”。

node.js ffmpeg video-streaming mjpeg
1个回答
0
投票

在此处以及在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的盒子,因此,随时可以通过提供更多答案和评论来使这个问题继续存在。

© www.soinside.com 2019 - 2024. All rights reserved.