摘要:在使用FFmpeg库时通常使用avformat_find_stream_info
相关函数来探测流的基本信息,为了更加深入理解FFmpeg的基本流程,本文根据FFmpeg 5.0的源码详细描述了该函数的具体实现。
关键字:FFmpeg
读者须知:读者需要了解FFmpeg的基本使用流程,以及一些FFmpeg的基本常识,了解FFmpegIO相关的内容,以及大致的解码流程。
1 avformat_find_stream_info
avformat_open_input
的主要工作时打开流并且对流进行初步的检测设置一些当前流的基本属性。而avformat_find_stream_info
在调用时其耗时是比较久做的流信息探测的工作更多。avformat_find_stream_info
比仅仅会读取文件来获取流信息,而且会尝试进行部分解码,根据AVPcaket
和AVFrame
更加准确的判断流的基本信息。部分场景下虽然跳过解码过程可以提升avformat_find_stream_info
的执行速度,但是也会导致流探测的信息不准确。
/**
* Read packets of a media file to get stream information. This
* is useful for file formats with no headers such as MPEG. This
* function also computes the real framerate in case of MPEG-2 repeat
* frame mode.
* The logical file position is not changed by this function;
* examined packets may be buffered for later processing.
*
* @param ic media file handle
* @param options If non-NULL, an ic.nb_streams long array of pointers to
* dictionaries, where i-th member contains options for
* codec corresponding to i-th stream.
* On return each dictionary will be filled with options that were not found.
* @return >=0 if OK, AVERROR_xxx on error
*
* @note this function isn't guaranteed to open all the codecs, so
* options being non-empty at return is a perfectly normal behavior.
*
* @todo Let the user decide somehow what information is needed so that
* we do not waste time getting stuff the user does not need.
*/
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
avformat_find_stream_info
的最终目的就是获取目标流的详细信息如果拿不到就会尝试获取码流AVPacket
,从码流中解析相关流信息,如果码流中也拿不到就会尝试解码从AVFrame
中获取。
2 详细调用过程
avformat_find_stream_info
代码的内容非常难以阅读,这里不贴对应的代码了,我们简单分析下。
在进行探测前下面这部分代码用来设置探测的时长,可以看到部分时长是和格式强相关的,应该和文件本身关系比较大。
max_stream_analyze_duration = max_analyze_duration;
max_subtitle_analyze_duration = max_analyze_duration;
if (!max_analyze_duration) {
max_stream_analyze_duration =
max_analyze_duration = 5*AV_TIME_BASE;
max_subtitle_analyze_duration = 30*AV_TIME_BASE;
if (!strcmp(ic->iformat->name, "flv"))
max_stream_analyze_duration = 90*AV_TIME_BASE;
if (!strcmp(ic->iformat->name, "mpeg") || !strcmp(ic->iformat->name, "mpegts"))
max_stream_analyze_duration = 7*AV_TIME_BASE;
}
然后就是循环遍历打开每个流的解码器,为后续解码做准备,这部分就是调用avcodec_find_decoder
和avcodec_open2
查找解码器和打开解码器,具体实现等到讲解码时再说。
之后便是不断循环检测,循环内部会不断判断当前是否已经成功获取完整的流信息,成功或者超出探测时长上限的话就结束;否则就会从读取AVPacket
和解码帧AVFrame
,读取AVPacket
和解码AVFrame
分别是调用ff_read_packet
和avcodec_send_packet,avcodec_receive_frame
进行码流的读取和解码。
estimate_timings
用于估算当前媒体文件的时长,根据不同的格式会采取不同的方式:
mpeg
和mpegts
会采用pts的方式估算,即调用estimate_timings_from_pts
估算;- 如果非情况1且流本身已经探测到一部分时长信息,则调用
fill_all_stream_timings
根据已有的时长相关信息进行统一; - 其他情况调用
estimate_timings_from_bit_rate
,利用文件码流大小和码率估算;
estimate_timings_from_pts
核心就是累加AVPacket
的时长,如下:
for (;;) {
if (read_size >= DURATION_MAX_READ_SIZE << (FFMAX(retry - 1, 0)))
break;
do {
ret = ff_read_packet(ic, pkt);
} while (ret == AVERROR(EAGAIN));
if (ret != 0)
break;
read_size += pkt->size;
st = ic->streams[pkt->stream_index];
if (pkt->pts != AV_NOPTS_VALUE &&
(st->start_time != AV_NOPTS_VALUE ||
st->internal->first_dts != AV_NOPTS_VALUE)) {
if (pkt->duration == 0) {
ff_compute_frame_duration(ic, &num, &den, st, st->internal->parser, pkt);
if (den && num) {
pkt->duration = av_rescale_rnd(1,
num * (int64_t) st->time_base.den,
den * (int64_t) st->time_base.num,
AV_ROUND_DOWN);
}
}
duration = pkt->pts + pkt->duration;
found_duration = 1;
if (st->start_time != AV_NOPTS_VALUE)
duration -= st->start_time;
else
duration -= st->internal->first_dts;
if (duration > 0) {
if (st->duration == AV_NOPTS_VALUE || st->internal->info->last_duration<= 0 ||
(st->duration < duration && FFABS(duration - st->internal->info->last_duration) < 60LL*st->time_base.den / st->time_base.num))
st->duration = duration;
st->internal->info->last_duration = duration;
}
}
av_packet_unref(pkt);
}
而estimate_timings_from_bit_rate
代码比较简单就是duration=filesize/bitrate
。
duration = av_rescale(filesize, 8LL * st->time_base.den,
ic->bit_rate *
(int64_t) st->time_base.num);
st->duration = duration;
最后根据已经探测的的信息统一对时间等信息进行统一。
3 参考文献
- FFmpeg源代码简单分析:avformat_find_stream_info()