与ffmpeg帧数不一致

Ric*_*bby 2 video ffmpeg codec hevc

我经常遇到 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
Run Code Online (Sandbox Code Playgroud)

输出如下

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
Run Code Online (Sandbox Code Playgroud)

然后

297 297 3597
300 597 3597
300 897 3597
300 1197 3597
300 1497 3597
300 1797 3597 <--- output are consistent based on ffprobe
Run Code Online (Sandbox Code Playgroud)

但是如果我使用以下命令使用 ffmpeg 检查段大小

ffmpeg -hwaccel cuda -i clips/part_$i.mp4 -vsync 2 -f null - 
Run Code Online (Sandbox Code Playgroud)

对于第 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
Run Code Online (Sandbox Code Playgroud)

对于所有其他部分,它是不一致的,应该是 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
Run Code Online (Sandbox Code Playgroud)

这个问题与任何其他间隔大小完全相同,例如 10 秒我会得到以下视频大小:

ffprobe 597 - 600 ...
ffmpeg 597 597 ...
Run Code Online (Sandbox Code Playgroud)

我认为这可能与源 vfr 或 cfr 有关,但我尝试将输入转换为 cfr,但没有任何变化。

此外,我尝试每秒强制关键帧检查是否是以下参数的关键帧问题:-force_key_frames“expr:gte(t,n_forced * 1)”,但问题完全相同。

我究竟做错了什么?hvc1 中的文件经常发生这种情况,我真的不知道如何处理。

Rot*_*tem 6

差异的根源在于 FFprobe 计算丢弃的数据包,而 FFmpeg 不将丢弃的数据包计为帧。

\n
\n

您的结果与使用 3 个 B 帧(每个 P 帧或 I 帧有 3 个连续 B 帧)创建的视频流一致。

\n

根据维基百科

\n
\n

I\xe2\x80\x91 帧的可压缩性最低,但不需要其他视频帧进行解码。
\nP\xe2\x80\x91frames 可以使用之前帧的数据进行解压缩,并且比 I\xe2\x80\x91frames 更具可压缩性。
\nB\xe2\x80\x91frames 可以使用前一帧和前一帧作为数据参考,以获得最高的数据压缩量。

\n
\n

当将包含 P 帧和 B 帧的视频分割成片段而不重新编码时,依赖链会中断。

\n
    \n
  • (几乎)总是有一些帧依赖于前一段或下一段的帧。
  • \n
  • 上述帧被保留,但匹配的数据包被标记为“丢弃”(用AV_PKT_FLAG_DISCARDflag标记)。
  • \n
\n
\n

为了处理同一数据集,我们构建了合成视频(用作输入)。

\n

使用以下命令构建合成视频:

\n
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\n
Run Code Online (Sandbox Code Playgroud)\n
    \n
  • -g 60将 GOP 大小设置为 60 帧(每 60 帧插入一个关键帧)。
  • \n
  • bframes=3:b-adapt=0强制 3 个连续的 B 帧。
  • \n
\n

为了验证 I/P/B 帧的数量,我们可以使用 FFprobe:

\n
ffprobe -i test_video.mp4 -show_frames -show_entries frame=pict_type\n
Run Code Online (Sandbox Code Playgroud)\n

输出如下:

\n

pict_type=I
\n pict_type=B
\n pict_type=B
\n pict_type=B
\n pict_type=P
\n pict_type=B
\n pict_type=B
\n pict_type=B
\n...

\n
\n

按时间分段视频(每段 5 秒):

\n
ffmpeg -i test_video.mp4 -f segment -vcodec copy -reset_timestamps 1 -segment_time 5 clips/part_%d.mp4\n
Run Code Online (Sandbox Code Playgroud)\n

FFprobe 计数:
\n 297 1497 1200
\n 300 1797 1200
\n 300 2097 1200
\n303 2400 1200

\n

FFmpeg 计数:
\n frame= 297
\n frame= 297
\n frame= 297
\nframe= 300

\n

正如您所看到的,结果与您的输出一致。

\n
\n

我们可以使用 FFprobe 来识别“丢弃”的数据包:

\n
ffprobe -i part_1.mp4 -show_packets\n
Run Code Online (Sandbox Code Playgroud)\n

寻找flags=_D
\n数据包flags=_D被标记为“已丢弃”
\n注意:在视频流中,每个数据包都匹配一个帧。

\n

FFprobe 输出开头为:
\n flags=K_
\n flags=_D
\n flags=_D
\ flags=_D
n flags=__
\n\n flags=__
\n flags=__
\n...

\n

对于每个中间段,有 3 个数据包被标记为“丢弃”,这就是 FFmpeg 与 FFprobe 相比丢失 3 个帧的原因。

\n