如何使用 FFMPEG 分割视频,以便每个块都以关键帧开头?

Posted

技术标签:

【中文标题】如何使用 FFMPEG 分割视频,以便每个块都以关键帧开头?【英文标题】:How to split a video using FFMPEG so that each chunk starts with a key frame? 【发布时间】:2012-12-09 22:01:09 【问题描述】:

我们需要将大型实时 WMV 视频馈送分成大小相同的小块。我们制作了一个可以正常工作的脚本,除了一件事:视频块不以关键帧开头,因此在播放大多数视频块时,它们不会显示任何图像,直到最终来自原始视频的关键帧到达。

有没有办法告诉 ffmpeg 让输出视频从关键帧开始?

下面是我们的命令行现在的样子:

ffmpeg.exe -i "C:\test.wmv" -ss 00:00:00 -t 00:00:05 -acodec copy -vcodec copy -async 1 -y  "0000.wmv"
ffmpeg.exe -i "C:\test.wmv" -ss 00:00:05 -t 00:00:05 -acodec copy -vcodec copy -async 1 -y  "0001.wmv"

等等……

【问题讨论】:

您使用的是最新的 ffmpeg 版本吗? 是最新的:ffmpeg 版本 N-47062-g26c531c。我们还尝试使用 -force_key_frames:v 00:00:00 或 -force_key_frames:v 00:00:05,但对输出没有影响。我相信使用 -vcodec copy 实际上意味着它只是在新文件中复制图片组,但不会进行任何类型的重新编码来添加关键帧。是这样吗? -vcodec copy 不重新编码:它只执行解复用和复用。 有没有办法让我将文件分成多个块,以便每个块都以关键帧开始?它们不必具有相同的持续时间。但它们必须是完全连续的。如果我在输入文件上指定 -ss,它会在关键帧处剪切,但随后我会通过在 2 个连续的块中重新包含相同的 GOP 来看到我的视频重叠。 【参考方案1】:

FFMPEG 的最新版本包括一个新选项“segment”,它完全符合我的想法。

ffmpeg -i INPUT.mp4 -acodec copy -f segment -vcodec copy -reset_timestamps 1 -map 0 OUTPUT%d.mp4

这会生成一系列编号的输出文件,这些文件会根据关键帧分成几段。在我自己的测试中,它运行良好,虽然我没有在超过几分钟的时间内使用它,而且只是 MP4 格式。

【讨论】:

好像例子已经移到这里了:ffmpeg.org/ffmpeg-formats.html#Examples-9 @jox 好像又搬家了,转至:ffmpeg.org/… @TheAudiophile 你是对的,谢谢。以下链接直接指向示例:ffmpeg.org/ffmpeg-formats.html#Examples-10【参考方案2】:

正如官方FFMPEG Docs 所述,我最好指定-ss timestart 之前 -i input_file.ext,因为它设置(或者我理解)生成视频的开头到在您指定的时间戳之前找到的最近的关键帧

将您的示例更改为:

ffmpeg.exe -ss 00:00:00 -i "C:\test.wmv" -t 00:00:05 -acodec copy -vcodec copy -async 1 -y  "0000.wmv"

我已经在.flv.mp4 文件上测试了这种方法。

【讨论】:

虽然这有点搞砸了视频的持续时间,例如我得到的视频显示为 8 秒长,即使我指定视频持续 5 秒。视频确实在 5 秒后结束,但在所有媒体播放器中,它都显示为 8 秒长。有什么想法吗? 这很好用!只是它确实保证在开始时有 1 秒的冻结帧,但对于 30 fps 的视频,持续时间对我来说很好。 我认为这是不正确的。如果您在“搜索”页面向下滚动,它会专门针对 codec copy 表示:Using -ss as input option together with -c:v copy might not be accurate since ffmpeg is forced to only use/split on i-frames. Though it will—if possible—adjust the start time of the stream to a negative value to compensate for that. Basically, if you specify "second 157" and there is no key frame until second 159, it will include two seconds of audio (with no video) at the start, then will start from the first key frame. So be careful when splitting and doing codec copy. @Ondra:好的,但是如何防止这种情况,以生成从头到尾正确播放的视频?【参考方案3】:

这是我可以开始工作的解决方案:

按照 av501 和 d33pika 的建议,我使用 ffprobe 来查找关键帧的位置。因为 ffprobe 非常冗长,可能需要几秒钟甚至几分钟才能输出所有关键帧,并且无法从冗长的视频中确定我们想要的帧范围,所以我分为 5 个步骤:

    从原始文件中导出一个视频块,大约是所需块大小的两倍。

    ffmpeg -i source.wmv -ss 00:00:00 -t 00:00:06 -acodec copy -vcodec copy -async 1 -y  0001.wmv
    

    使用 ffprobe 查找关键帧的位置。在所需的块大小之后选择最近的关键帧。

    ffprobe -show_frames -select_streams v -print_format json=c=1 0001.wmv
    

    从 ffprobe 的输出中获取该关键帧之前帧的pkt_dts_time

    ffmpeg 在步骤 1 的导出块上,指定相同的输入和输出文件,并指定 -ss 00:00:00-t [在步骤 3 中找到的值]。

    ffmpeg -i 0001.wmv -ss 00:00:00 -t 00:00:03.1350000 -acodec copy -vcodec copy -async 1 -y 0001.wmv
    

    在第 1 步重新开始,使用-ss [在第 3 步中通过迭代找到的值的累积总和]。

通过这种方式,我能够以一种有效且稳健的方式在关键帧处分割视频。

【讨论】:

在您的帮助下,我能够做到这一点,但是,最初的 dts/pts 发生了一些变化(这里是 500 毫秒)。文档表明,如果 -ss 在 -i 之前通过,则它充当搜索,在我的情况下,它修复了移位。阅读 -ss 部分的 man ffmpeg :) 为什么使用关键帧之前的帧而不是关键帧本身?回复:“从 ffprobe 的输出中获取该关键帧之前帧的 pkt_dts_time。” 如果您没有在关键帧之前进行剪切,则该关键帧将作为该块的最后一帧包含在内,并且下一个块不会以关键帧开始。 对于 ffprobe 的声明“无法从冗长的视频中确定我们想要的帧范围”是不正确的,或者至少不再是正确的。例如,添加选项 -read_intervals 18.6%+4.2 从 18.6 秒开始读取并持续 4.2 秒。【参考方案4】:

使用较新版本的ffmpeg,可以通过使用ffprobeffmpegmuxer 来实现此目的。

    使用ffprobe 和awk 来识别尽可能接近的关键帧 到你想要的块长度。
ffprobe -show_frames -select_streams v:0 \
        -print_format csv [SOURCE_VIDEO] 2>&1 |
grep -n frame,video,1 |
awk 'BEGIN  FS=","   print $1 " " $5 ' |
sed 's/:frame//g' |
awk 'BEGIN  previous=0; frameIdx=0; size=0;  

  split($2,time,".");
  current=time[1];
  if (current-previous >= [DURATION_IN_SECONDS])
    a[frameIdx]=$1; frameIdx++; size++; previous=current;
  

END 
  str=a[0];
  for(i=1;i<size;i++)  str = str "," a[i];  print str;
'

在哪里

[SOURCE_VIDEO] = 要分段的视频的路径 [DURATION_IN_SECONDS] = 所需的片段长度(以秒为单位)

输出是以逗号分隔的关键帧字符串。

    使用上面的关键帧输出作为ffmpeg 的输入。
ffmpeg -i [SOURCE_VIDEO] -codec copy -map 0 -f segment \
       -segment_frames [OUTPUT_OF_STEP_1] [SEGMENT_PREFIX] \
       _%03d.[SOURCE_VIDEO_EXTENSION]

在哪里

[SOURCE_VIDEO] = 要分段的视频的路径 [OUTPUT_OF_STEP_1] = 逗号分隔的关键帧字符串 [SEGMENT_PREFIX] = 段输出名称 [SOURCE_VIDEO_EXTENSION] = 源视频的扩展名(例如,mp4、mkv)

【讨论】:

【参考方案5】:

使用ffprobe -show_frames -pretty &lt;stream&gt; 识别关键帧。

【讨论】:

看起来是一个很有前途的解决方案。会调查。我查看了文档,但无法轻松找到对视频的一小部分进行 ffprobe 的方法(例如,只有前 10 秒)。因为视频很长,它会生成几 MB 的输出,需要几秒钟才能生成。 ffprobe 中是否有选项可以仅在特定范围内输出特定流的帧? ffprobe -show_frames -select_streams v 仅过滤到视频帧。现在我只需要能够指定帧范围或时间范围。 我浏览了文档,没有发现任何关于限制视频特定时间范围的信息。所以我想我会使用 ffmpeg 来获取一个块,然后在其上调用 ffprobe 以获取关键帧,然后在其上调用 ffmpeg 以在关键帧之前进行最终分割。 @sboisse, -read_intervals 现在允许您指定时间或数据包间隔进行探测。例如ffprobe -show_frames -read_intervals 10%+20,01:30%01:45 -i &lt;file&gt; 探测 10-30 秒,然后是 1:30-1:45。【参考方案6】:

如果您愿意编写一些脚本并希望我以特定的间隔帧,那么一种方法是

    运行 ffprobe 并从输出中收集 I 帧的位置

    ffprobe -show_streams

    使用相同的脚本运行一系列 -ss -t 命令以获取所需的块。

然后你可以让你的脚本决定最小帧数[假设有两张 I 图片在 10 帧之内,你真的不想在那里分块]。

另一种方法是使用 gstreamer 的 multisfilesink 并将模式设置为关键帧 [但是这会在每个关键帧处分块,因此可能并不理想]

【讨论】:

以上是关于如何使用 FFMPEG 分割视频,以便每个块都以关键帧开头?的主要内容,如果未能解决你的问题,请参考以下文章

如何用ffmpeg制作马赛克,而不会同时播放每个视频?

FFmpeg:如何有效地分割视频?

如何通过静音部分分割视频或音频

ffmpeg视频剪切时长不正确

想要使用ffmpeg来完成如下两个功能: 1.视频截图 2.视频分割功能

使用 ffmpeg 按大小分割视频文件 [关闭]