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

问题描述 投票:0回答:6

我们需要将大型实时 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"

等等...

video ffmpeg split keyframe
6个回答
53
投票

FFMPEG 的最新版本包含一个新选项“段”,它正是我认为您需要的。

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

这会生成一系列编号的输出文件,这些文件根据关键帧分为多个片段。在我自己的测试中,它运行良好,尽管我没有使用它超过几分钟并且仅以 MP4 格式使用它。


45
投票

正如官方FFMPEG Docs所述,对我来说指定

-ss timestart
before
-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
文件上测试了此方法。


22
投票

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

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

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

    ffmpeg -i source.wmv -ss 00:00:00 -t 00:00:06 -acodec copy -vcodec copy -async 1 -y  0001.wmv
    
  2. 使用 ffprobe 查找关键帧所在的位置。选择所需块大小后最接近的关键帧。

    ffprobe -show_frames -select_streams v -print_format json=c=1 0001.wmv
    
  3. 从 ffprobe 的输出中获取该关键帧之前帧的

    pkt_dts_time

  4. 在步骤 1 导出的块上使用 ffmpeg,指定相同的输入和输出文件,并指定

    -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
    
  5. 使用

    -ss
    [步骤 3 中通过迭代找到的值的累积总和]从步骤 1 重新开始。

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


16
投票

使用较新版本的

ffmpeg
,可以通过使用
ffprobe
ffmpeg
muxer
来实现此目的。

  1. 使用
    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] = 所需的片段长度(以秒为单位)

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

  1. 使用上面的关键帧输出作为
    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 <stream>
识别关键帧。


2
投票

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

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

    ffprobe-show_streams

  2. 使用相同的脚本运行一系列 -ss -t 命令来获取您想要的块。

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

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

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