与 ffmpeg 的帧数不一致
Inconsistent frame number with ffmpeg
我经常遇到 hvc1 视频在 ffprobe 信息和 FFmpeg 信息之间获取不一致帧数的问题,我想知道这个问题的可能原因是什么,如果没有,如何解决它重新编码视频。
我用我的测试视频编写了以下示例脚本
我将视频分成 5 秒的片段,我得到 ffprobe 给出了预期的视频长度,但 FFmpeg 给出的每个片段都比预期少 3 帧,但第一个片段除外。
如果我分割 10 秒或任何分割,问题完全相同,我总是丢失 3 帧。
我注意到第一个片段总是比其他片段小 3 帧(在 ffprobe 上),并且它是唯一一致的片段。
这是我为测试此问题而编写的示例脚本:
# get total video frame number using ffprobe or ffmpeg
total_num_frames=$(ffprobe -v quiet -show_entries stream=nb_read_packets -count_packets -select_streams v:0 -print_format json test_video.mp4 | jq '.streams[0].nb_read_packets' | tr -d '"')
echo $total_num_frames
ffmpeg -hwaccel cuda -i test_video.mp4 -vsync 2 -f null -
# Check ffprobe of each segment is consistent
rm -rf clips && mkdir clips && \
ffmpeg -i test_video.mp4 -acodec copy -f segment -vcodec copy -reset_timestamps 1 -segment_time 5 -map 0 clips/part_%d.mp4
count_frames=0
for i in {0..5}
do
num_packets=$(ffprobe -v quiet -show_entries stream=nb_read_packets -count_packets -select_streams v:0 -print_format json clips/part_$i.mp4 | jq '.streams[0].nb_read_packets' | tr -d '"')
count_frames=$(($count_frames+$num_packets))
echo $num_packets $count_frames $total_num_frames
done
输出如下
3597
ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 9 (Ubuntu 9.3.0-10ubuntu2)
configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test_video.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf58.29.100
Duration: 00:00:59.95, start: 0.035000, bitrate: 11797 kb/s
Stream #0:0(und): Video: hevc (Main) (hvc1 / 0x31637668), yuv420p(tv, bt709), 1920x1080, 11692 kb/s, 60.01 fps, 60 tbr, 19200 tbn, 19200 tbc (default)
Metadata:
handler_name : Core Media Video
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 91 kb/s (default)
Metadata:
handler_name : Core Media Audio
Stream mapping:
Stream #0:0 -> #0:0 (hevc (native) -> wrapped_avframe (native))
Stream #0:1 -> #0:1 (aac (native) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, null, to 'pipe:':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf58.29.100
Stream #0:0(und): Video: wrapped_avframe, nv12, 1920x1080, q=2-31, 200 kb/s, 60 fps, 60 tbn, 60 tbc (default)
Metadata:
handler_name : Core Media Video
encoder : Lavc58.54.100 wrapped_avframe
Stream #0:1(und): Audio: pcm_s16le, 44100 Hz, mono, s16, 705 kb/s (default)
Metadata:
handler_name : Core Media Audio
encoder : Lavc58.54.100 pcm_s16le
frame= 3597 fps=788 q=-0.0 Lsize=N/A time=00:00:59.95 bitrate=N/A speed=13.1x
video:1883kB audio:5162kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
然后
297 297 3597
300 597 3597
300 897 3597
300 1197 3597
300 1497 3597
300 1797 3597 <--- output are consistent based on ffprobe
但是如果我使用以下命令检查 ffmpeg 的段大小
ffmpeg -hwaccel cuda -i clips/part_$i.mp4 -vsync 2 -f null -
第 0 部分没问题
frame= 297 fps=0.0 q=-0.0 Lsize=N/A time=00:00:04.95 bitrate=N/A speed=12.5x
video:155kB audio:424kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
所有其他部分不一致,应该是 300
frame= 297 fps=0.0 q=-0.0 Lsize=N/A time=00:00:04.95 bitrate=N/A speed=12.3x
video:155kB audio:423kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
问题与任何其他间隔大小完全相同,例如 10 秒我会得到以下视频大小:
ffprobe 597 - 600 ...
ffmpeg 597 597 ...
我认为这可能与源 vfr 或 cfr 有关,但我尝试将输入转换为 cfr,但没有任何改变。
此外,我尝试每秒强制关键帧检查是否是以下参数的关键帧问题:-force_key_frames“expr:gte(t,n_forced*1)”,但问题完全一样。
我做错了什么?我经常遇到 hvc1 中的文件,我真的不知道如何处理。
差异的来源是 FFprobe 计算丢弃的数据包,而 FFmpeg 不将丢弃的数据包计为帧。
您的结果与使用 3 个 B 帧(每个 P 帧或 I 帧对应 3 个连续的 B 帧)创建的视频流一致。
根据Wikipedia:
I‑frames are the least compressible but don't require other video frames to decode.
P‑frames can use data from previous frames to decompress and are more compressible than I‑frames.
B‑frames can use both previous and forward frames for data reference to get the highest amount of data compression.
在不重新编码的情况下将具有 P 帧和 B 帧的视频分割成片段时,依赖链会中断。
- (几乎)总是有帧依赖于前一段或下一段的帧。
- 保留上述帧,但匹配的数据包被标记为“丢弃”(标记为
AV_PKT_FLAG_DISCARD
标志)。
为了处理同一个数据集,我们构建了合成视频(用作输入)。
使用以下命令构建合成视频:
ffmpeg -y -r 60 -f lavfi -i testsrc=size=384x256:rate=1 -vf "setpts=N/60/TB" -g 60 -vcodec libx265 -x265-params crf=28:bframes=3:b-adapt=0 -tag:v hvc1 -pix_fmt yuv420p -t 20 test_video.mp4
-g 60
将 GOP 大小设置为 60 帧(每 60 帧插入一个关键帧)。
bframes=3:b-adapt=0
强制 3 个连续的 B 帧。
为了验证I/P/B帧的数量,我们可以使用FFprobe:
ffprobe -i test_video.mp4 -show_frames -show_entries frame=pict_type
输出如下:
pict_type=I
pict_type=B
pict_type=B
pict_type=B
pict_type=P
pict_type=B
pict_type=B
pict_type=B
...
按时间对视频进行分段(每段 5 秒):
ffmpeg -i test_video.mp4 -f segment -vcodec copy -reset_timestamps 1 -segment_time 5 clips/part_%d.mp4
FFprobe 计数:
297 1497 1200
300 1797 1200
300 2097 1200
303 2400 1200
FFmpeg 计数:
frame= 297
frame= 297
frame= 297
frame= 300
可以看到,结果和你的输出是一致的。
我们可以使用 FFprobe 识别“丢弃”的数据包:
ffprobe -i part_1.mp4 -show_packets
寻找flags=_D
.
flags=_D
的数据包被标记为“已丢弃”
注意:在视频流中,每个数据包都匹配一个帧。
FFprobe 输出开始于:
flags=K_
flags=_D
flags=_D
flags=_D
flags=__
flags=__
flags=__
...
对于每个中间段,有 3 个数据包被标记为“丢弃”,这就是 FFmpeg 与 FFprobe 相比丢失 3 个帧的原因。
我经常遇到 hvc1 视频在 ffprobe 信息和 FFmpeg 信息之间获取不一致帧数的问题,我想知道这个问题的可能原因是什么,如果没有,如何解决它重新编码视频。
我用我的测试视频编写了以下示例脚本
我将视频分成 5 秒的片段,我得到 ffprobe 给出了预期的视频长度,但 FFmpeg 给出的每个片段都比预期少 3 帧,但第一个片段除外。
如果我分割 10 秒或任何分割,问题完全相同,我总是丢失 3 帧。
我注意到第一个片段总是比其他片段小 3 帧(在 ffprobe 上),并且它是唯一一致的片段。
这是我为测试此问题而编写的示例脚本:
# get total video frame number using ffprobe or ffmpeg
total_num_frames=$(ffprobe -v quiet -show_entries stream=nb_read_packets -count_packets -select_streams v:0 -print_format json test_video.mp4 | jq '.streams[0].nb_read_packets' | tr -d '"')
echo $total_num_frames
ffmpeg -hwaccel cuda -i test_video.mp4 -vsync 2 -f null -
# Check ffprobe of each segment is consistent
rm -rf clips && mkdir clips && \
ffmpeg -i test_video.mp4 -acodec copy -f segment -vcodec copy -reset_timestamps 1 -segment_time 5 -map 0 clips/part_%d.mp4
count_frames=0
for i in {0..5}
do
num_packets=$(ffprobe -v quiet -show_entries stream=nb_read_packets -count_packets -select_streams v:0 -print_format json clips/part_$i.mp4 | jq '.streams[0].nb_read_packets' | tr -d '"')
count_frames=$(($count_frames+$num_packets))
echo $num_packets $count_frames $total_num_frames
done
输出如下
3597
ffmpeg version 4.2.4-1ubuntu0.1 Copyright (c) 2000-2020 the FFmpeg developers
built with gcc 9 (Ubuntu 9.3.0-10ubuntu2)
configuration: --prefix=/usr --extra-version=1ubuntu0.1 --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --arch=amd64 --enable-gpl --disable-stripping --enable-avresample --disable-filter=resample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librsvg --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxml2 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-nvenc --enable-chromaprint --enable-frei0r --enable-libx264 --enable-shared
libavutil 56. 31.100 / 56. 31.100
libavcodec 58. 54.100 / 58. 54.100
libavformat 58. 29.100 / 58. 29.100
libavdevice 58. 8.100 / 58. 8.100
libavfilter 7. 57.100 / 7. 57.100
libavresample 4. 0. 0 / 4. 0. 0
libswscale 5. 5.100 / 5. 5.100
libswresample 3. 5.100 / 3. 5.100
libpostproc 55. 5.100 / 55. 5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test_video.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf58.29.100
Duration: 00:00:59.95, start: 0.035000, bitrate: 11797 kb/s
Stream #0:0(und): Video: hevc (Main) (hvc1 / 0x31637668), yuv420p(tv, bt709), 1920x1080, 11692 kb/s, 60.01 fps, 60 tbr, 19200 tbn, 19200 tbc (default)
Metadata:
handler_name : Core Media Video
Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, mono, fltp, 91 kb/s (default)
Metadata:
handler_name : Core Media Audio
Stream mapping:
Stream #0:0 -> #0:0 (hevc (native) -> wrapped_avframe (native))
Stream #0:1 -> #0:1 (aac (native) -> pcm_s16le (native))
Press [q] to stop, [?] for help
Output #0, null, to 'pipe:':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2mp41
encoder : Lavf58.29.100
Stream #0:0(und): Video: wrapped_avframe, nv12, 1920x1080, q=2-31, 200 kb/s, 60 fps, 60 tbn, 60 tbc (default)
Metadata:
handler_name : Core Media Video
encoder : Lavc58.54.100 wrapped_avframe
Stream #0:1(und): Audio: pcm_s16le, 44100 Hz, mono, s16, 705 kb/s (default)
Metadata:
handler_name : Core Media Audio
encoder : Lavc58.54.100 pcm_s16le
frame= 3597 fps=788 q=-0.0 Lsize=N/A time=00:00:59.95 bitrate=N/A speed=13.1x
video:1883kB audio:5162kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
然后
297 297 3597
300 597 3597
300 897 3597
300 1197 3597
300 1497 3597
300 1797 3597 <--- output are consistent based on ffprobe
但是如果我使用以下命令检查 ffmpeg 的段大小
ffmpeg -hwaccel cuda -i clips/part_$i.mp4 -vsync 2 -f null -
第 0 部分没问题
frame= 297 fps=0.0 q=-0.0 Lsize=N/A time=00:00:04.95 bitrate=N/A speed=12.5x
video:155kB audio:424kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
所有其他部分不一致,应该是 300
frame= 297 fps=0.0 q=-0.0 Lsize=N/A time=00:00:04.95 bitrate=N/A speed=12.3x
video:155kB audio:423kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
问题与任何其他间隔大小完全相同,例如 10 秒我会得到以下视频大小:
ffprobe 597 - 600 ...
ffmpeg 597 597 ...
我认为这可能与源 vfr 或 cfr 有关,但我尝试将输入转换为 cfr,但没有任何改变。
此外,我尝试每秒强制关键帧检查是否是以下参数的关键帧问题:-force_key_frames“expr:gte(t,n_forced*1)”,但问题完全一样。
我做错了什么?我经常遇到 hvc1 中的文件,我真的不知道如何处理。
差异的来源是 FFprobe 计算丢弃的数据包,而 FFmpeg 不将丢弃的数据包计为帧。
您的结果与使用 3 个 B 帧(每个 P 帧或 I 帧对应 3 个连续的 B 帧)创建的视频流一致。
根据Wikipedia:
I‑frames are the least compressible but don't require other video frames to decode.
P‑frames can use data from previous frames to decompress and are more compressible than I‑frames.
B‑frames can use both previous and forward frames for data reference to get the highest amount of data compression.
在不重新编码的情况下将具有 P 帧和 B 帧的视频分割成片段时,依赖链会中断。
- (几乎)总是有帧依赖于前一段或下一段的帧。
- 保留上述帧,但匹配的数据包被标记为“丢弃”(标记为
AV_PKT_FLAG_DISCARD
标志)。
为了处理同一个数据集,我们构建了合成视频(用作输入)。
使用以下命令构建合成视频:
ffmpeg -y -r 60 -f lavfi -i testsrc=size=384x256:rate=1 -vf "setpts=N/60/TB" -g 60 -vcodec libx265 -x265-params crf=28:bframes=3:b-adapt=0 -tag:v hvc1 -pix_fmt yuv420p -t 20 test_video.mp4
-g 60
将 GOP 大小设置为 60 帧(每 60 帧插入一个关键帧)。bframes=3:b-adapt=0
强制 3 个连续的 B 帧。
为了验证I/P/B帧的数量,我们可以使用FFprobe:
ffprobe -i test_video.mp4 -show_frames -show_entries frame=pict_type
输出如下:
pict_type=I
pict_type=B
pict_type=B
pict_type=B
pict_type=P
pict_type=B
pict_type=B
pict_type=B
...
按时间对视频进行分段(每段 5 秒):
ffmpeg -i test_video.mp4 -f segment -vcodec copy -reset_timestamps 1 -segment_time 5 clips/part_%d.mp4
FFprobe 计数:
297 1497 1200
300 1797 1200
300 2097 1200
303 2400 1200
FFmpeg 计数:
frame= 297
frame= 297
frame= 297
frame= 300
可以看到,结果和你的输出是一致的。
我们可以使用 FFprobe 识别“丢弃”的数据包:
ffprobe -i part_1.mp4 -show_packets
寻找flags=_D
.
flags=_D
的数据包被标记为“已丢弃”
注意:在视频流中,每个数据包都匹配一个帧。
FFprobe 输出开始于:
flags=K_
flags=_D
flags=_D
flags=_D
flags=__
flags=__
flags=__
...
对于每个中间段,有 3 个数据包被标记为“丢弃”,这就是 FFmpeg 与 FFprobe 相比丢失 3 个帧的原因。