FFmpeg 库:HLS 的完全恒定的段持续时间

use*_*612 6 c c++ ffmpeg

我们正在使用 FFmpeg 库 git-ee94362 libavformat v55.2.100。我们的目的是使用 HLS 将两个流(视频和音频)混合到 M3U8 播放列表中。此外,我们希望每个 TS 段文件的持续时间恰好为 3.0 秒(帧速率为 25 fps)。

为了达到它,我们试图设置几个选项和属性,即:
-segment_time-keyint_min-scenechange_threshold-gop_size-force_key_frames。

我们的代码如下所示:

AVCodecContext *codec_ctx = NULL;
AVFormatContext *ofmt_ctx = NULL;

int ret = 0, gopSize = (int)(3.0 * 25);   // 3 sec * 25 fps

// ofmt_ctx and codec_ctx initialization and filling are OK, but: 
codec_ctx->time_base.num = 1;
codec_ctx->time_base.den = 25 // fps

// It seems, that the following three lines have no effect without explisit setting of the "hls_time" property
codec_ctx->keyint_min = gopSize;       // in FFMpeg application, the corresponding option is "-keyint_min 3"
codec_ctx->scenechange_threshold = 0;  // in FFMpeg application, the corresponding option is "-sc_threshold 0"
codec_ctx->gop_size = gopSize;         // in FFMpeg application, the corresponding option is "-g 3"

ret = av_opt_set_double(ofmt_ctx, "hls_time", 3.0, AV_OPT_SEARCH_CHILDREN);

// Any of the following lines causes "Option not found" error.
ret = av_opt_set(codec_ctx->priv_data, "profile", "main", AV_OPT_SEARCH_CHILDREN);
ret = av_opt_set(codec_ctx->priv_data, "preset", "ultrafast", AV_OPT_SEARCH_CHILDREN);
ret = av_opt_get(ofmt_ctx, "segment_time",  AV_OPT_SEARCH_CHILDREN, &str);
ret = av_opt_set((ofmt_ctx, "segment_time", "3.0", AV_OPT_SEARCH_CHILDREN);
Run Code Online (Sandbox Code Playgroud)

无论如何,TS 文件的持续时间是不同的,(~2-3 秒),而不是 3.0 秒。我们的问题是:解决问题的最佳方法是什么?

安德烈·莫切诺夫。

The*_*EEP 2

您面临的主要问题可能是您的视频文件在合适的位置没有关键帧。如果您只是从输入中复制流,那么这尤其是一个问题。

FFmpeg 根据关键帧来计算何时“剪切”片段。仔细想想,这是有道理的。您不能只在两个关键帧之间进行剪切,因为每个片段都需要独立发挥全部功能。现在,有人可能会争辩说 FFmpeg 应该自己插入新的关键帧,但这太容易使用了,不是吗;)

值得庆幸的是,您可以使用 FFmpeg 强制关键帧。使用参数或在代码中自行设置标志。您说您已经尝试过强制关键帧,但我认为您没有正确执行此操作。

我的这次测试取得了很好的结果。它只是命令行,抱歉,但您似乎已经知道如何在代码中应用命令行参数,所以您应该没问题。另请注意,我不使用“hls_XXX”参数,因为 a) 我真的不信任它们,b) 这样我认为它也应该适用于非 HLS 流。

ffmpeg -i inputFile.mov -force_key_frames "expr:gte(t,n_forced*10)" -strict -2 -c:a aac -c:v libx264 -f segment -segment_list_type m3u8 -segment_list_size 0 -segment_time 10.0 -segment_time_delta 0.1 -segment_list stream/test.m3u8 stream/test%02d.ts 
Run Code Online (Sandbox Code Playgroud)

您可以在此处查看force_key_frames 命令的具体工作原理。

现在我用 C++ 实现了上述命令,并添加了一些内容。但没有“force_key_frames”,因为我在转码过程中手动设置关键帧。这是我所做的:

AVDictionary* headerOptions(0);
av_dict_set(&headerOptions, "segment_format", "mpegts", 0);
av_dict_set(&headerOptions, "segment_list_type", "m3u8", 0);
av_dict_set(&headerOptions, "segment_list", _playlistFileName.c_str(), 0);
av_dict_set_int(&headerOptions, "segment_list_size", 0, 0);
av_dict_set(&headerOptions, "segment_time_delta", TO_STRING(1.00).c_str(), 0);
av_dict_set(&headerOptions, "segment_time", TO_STRING(_segmentDuration).c_str(), 0);
av_dict_set_int(&headerOptions, "reference_stream", _videoStream->index, 0);
av_dict_set(&headerOptions, "segment_list_flags", "cache+live", 0);
avformat_write_header(_formatContext, &headerOptions);
Run Code Online (Sandbox Code Playgroud)

这是生成的 m3u8:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:11
#EXTINF:10.083333,
test00.ts
#EXTINF:10.000000,
test01.ts
#EXTINF:10.000000,
test02.ts
#EXTINF:10.000000,
test03.ts
#EXTINF:10.000000,
test04.ts
#EXTINF:10.000000,
test05.ts
#EXTINF:0.083333,
test06.ts
#EXT-X-ENDLIST
Run Code Online (Sandbox Code Playgroud)

它并不完美(第一部分有点偏离),但我相信你不会得到比这更好的结果。

当然,最好的选择是确保您的输入文件在复制流时始终具有正确的关键帧,但有时您无法控制获得的文件。

边注

当您在代码中使用 FFmpeg 时,请务必先使用 cli ffmpeg 命令尝试在代码中执行的操作。如果你能让它以这种方式工作,你至少知道要在代码中设置哪些参数。如果它可以使用命令行工具工作,那么您就知道它一定可以在代码中以某种方式实现;)