author: hjjdebug
date: 2025年 04月 02日 星期三 14:06:06 CST
description: avformat_find_stream_info 代码简明注释与理解
600行代码如何阅读, 单步调试有死循环,累到手抽筋,跟不出来,
采用重点标注之法,非重点用…标注, 只跟一层(就是本层).
把代码缩减到 200多行 并重点标注,
至此理解了avformat_find_stream_info 到底都找了什么东西,怎么找的.
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
{
//设置max_analyze_duration 为5秒或7秒等
if (!max_analyze_duration) {
.....
}
//第一次枚举流,100行代码
for (i = 0; i < ic->nb_streams; i++) {
//功能,初始化内部av codec context: avctx
//把流的时基赋值给内部avctx
//用流的信息更新流的参数信息及流内部信息
//根据流参数codec_id,初始化分析器
//把流codecpar copy给avctx
//根据codec_id 找到codec
//调用avcodec_open2打开codec
}
//初始化 internal->info 的 last_dts, fps(frame per stream)的 first dts,last dts
for (i = 0; i < ic->nb_streams; i++) {
......
}
//死循环,要找到其退出条件
//其退出有正常退出, 超大小退出,超时间退出
for (;;) {
const AVPacket *pkt;
int analyzed_all_streams;
ff_check_interrupt(&ic->interrupt_callback); //回调一下用户设置的回调函数,没有则直接返回
/* check if one codec still needs to be handled */
//这个小循环总的功能是判断条件是否满足,不满足就break, 一旦break后面就还得取包,解包找信息
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
if (!has_codec_parameters(st, NULL)) //还没有codec参数就退出
break;
//设置fps_analyze_framecount 数,
int fps_analyze_framecount = 20;
int count; //到处用count,可不是好习惯,跟外边的count重名了,把外面的改名吧,外边叫count_w
count = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ? //有时间戳取值流内部 duration_count
st->internal->info->codec_info_duration_fields/2 :
st->internal->info->duration_count;
//如果视频流的额定帧率和平均帧率都为0, 帧数count 小于fps_analyze_framecount,就退出
//换句话说,分析的帧数还不够就退出
if (!(st->r_frame_rate.num && st->avg_frame_rate.num) &&
st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
if (count < fps_analyze_framecount)
break;
}
// Look at the first 3 frames if there is evidence of frame delay
// but the decoder delay is not set.
//检查前3个frame, 是bframe, 有delay证据退出
//有extradata且还没有初始化等等,退出.
//流的first_dts无效并且流有时间戳并其还满足其它条件,退出.
}
analyzed_all_streams = 0;
if (!missing_streams || !*missing_streams) //如果 missing_streams 为假,且i==ic->nb_streams, 就是找到了所有流信息
if (i == ic->nb_streams) { // 上个小循环提前退出,这个条件就不会满足了.
analyzed_all_streams = 1; //分析了所有的流
if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) { //format 有头部,认为信息已经找全
av_log(ic, AV_LOG_DEBUG, "All info found\n");
break; //这个退出才是找到所有信息后的退出!!
}
}
/* We did not get all the codec info, but we read too much data. */
if (read_size >= probesize) { // probesize 的作用
//读取大小超过探测大小,提示探测大小已经到达
//如果视频流额定帧率为0并且duration_count<=1, 提示没有足够的帧来评估帧率
break; // 退出死循环.
}
ret = read_frame_internal(ic, pkt1); //读一个包
if (!(ic->flags & AVFMT_FLAG_NOBUFFER)) { //如果ic 有缓存,送到packet_buffer中
ret = avpriv_packet_list_put(&ic->internal->packet_buffer,
&ic->internal->packet_buffer_end,
pkt1, NULL, 0);
pkt = &ic->internal->packet_buffer_end->pkt; //从缓存底部取包给pkt
} else {
pkt = pkt1; //无缓存直接给pkt
}
st = ic->streams[pkt->stream_index];
if (!(st->disposition & AV_DISPOSITION_ATTACHED_PIC))
read_size += pkt->size; //如果流不是仅带一个封面的流,则read_size加包的大小
avctx = st->internal->avctx;
if (!st->internal->avctx_inited) { // avctx 还没有初始化,进行一次初始化,把流参数copy给它
ret = avcodec_parameters_to_context(avctx, st->codecpar);
st->internal->avctx_inited = 1;
}
//下面仅在包pkt-pts有效,且st->codec_info_nb_frames>1才能走到,就是说从第2个frame才进入
if (pkt->dts != AV_NOPTS_VALUE && st->codec_info_nb_frames > 1) {
/* check for non-increasing dts */
//检查流的时间戳是否是连续递长的,不是则给警告.
//检查dts 是否连续,如果时差超过1000个包为不连续,不连续给警告
//更新流的dts 值
}
//从第2个frame 才有意义
if (st->codec_info_nb_frames>1) {
int64_t t = 0;
int64_t limit;
//计算分析的时长 t
//判定分析的包的时间是否大于限定的分析时间,超过给出提示,退出分析(max_duration 的作用)
if (t >= limit) {
av_log(ic, AV_LOG_VERBOSE, "max_analyze_duration %"PRId64" reached at %"PRId64" microseconds st:%d\n",
limit,
t, pkt->stream_index);
if (ic->flags & AVFMT_FLAG_NOBUFFER)
av_packet_unref(pkt1);
break;
}
if (pkt->duration) {
//更新st->internal->info->codec_info_duration_fields // 时长成员变量
}
}
//判断视频流是否是dts,pts不想等的类型(有bframe的流都这样),这叫解frame必需要等待的证据. 或者叫有b_frame的证据
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
ff_rfps_add_frame(ic, st, pkt->dts);
if (pkt->dts != pkt->pts && pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
st->internal->info->frame_delay_evidence = 1;
}
if (!st->internal->avctx->extradata) {
ret = extract_extradata(st, pkt); //解包的额外数据
}
//因为还没有找到信息,所以解码这个包.
try_decode_frame(ic, st, pkt,
(options && i < orig_nb_streams) ? &options[i] : NULL);
st->codec_info_nb_frames++; //解码个数加1, count_w加1
count_w++;
}
//退出死循环后
if (eof_reached) { 如果文件尾已经到达, 对于处理小文件的时候!
//那就继续运行,如果没打开codec, 则调用avcodec_open2打开它,并从pts更新dts
}
if (flush_codecs) {
//如果刷空codec, 那就把空包给decoder 来刷空decoder, 一般不用刷新
err = try_decode_frame(ic, st, empty_pkt,
(options && i < orig_nb_streams)
? &options[i] : NULL);
}
ff_rfps_calculate(ic); //计算额定frame
//下面近100行代码,给出平均帧率和额定帧率的计算
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
avctx = st->internal->avctx;
if (avctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
if (avctx->codec_id == AV_CODEC_ID_RAWVIDEO && !avctx->codec_tag && !avctx->bits_per_coded_sample) {
//更新一下codec_tag 如果需要的话, 我测试的流不需要
uint32_t tag= avcodec_pix_fmt_to_codec_tag(avctx->pix_fmt);
avctx->codec_tag= tag;
}
/* estimate average framerate if not set by demuxer */
if (st->internal->info->codec_info_duration_fields &&
!st->avg_frame_rate.num && // 还没有平均帧率?
st->internal->info->codec_info_duration) {
int best_fps = 0;
double best_error = 0.01;
//平均帧率应该是帧数/时长, 时长换成了秒, 60000是最大输出值限制,分母中为啥有个2?
//这是因为隔行扫描的视频一帧是按2场计算的,所以逐行扫描也应该符合它一帧按2场计算
av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
st->internal->info->codec_info_duration_fields * (int64_t) st->time_base.den,
st->internal->info->codec_info_duration * 2 * (int64_t) st->time_base.num, 60000);
//下面代码是一个与400个频率构成的表比较,看看跟标准频率差别最小的是哪一个,找到最好的,
//如果误差<0.01或更小, 那这个频率应该是标准频率,重新赋值给平均帧率
for (j = 0; j < MAX_STD_TIMEBASES; j++) {
......
}
if (best_fps)
av_reduce(&st->avg_frame_rate.num, &st->avg_frame_rate.den,
best_fps, 12 * 1001, INT_MAX);
}
//还没有额定帧率? 由avctx->time_base 和ticks_per_frame 给出
//ticks_per_frame 是一个frame中视频周期的次数,例如h264中,从SPS中获得该参数
//我测试的数据是 time_base{1,50},ticks_per_frame=2, 计算得{25,1}
if (!st->r_frame_rate.num) {
if (avctx->time_base.den * (int64_t) st->time_base.num
<= avctx->time_base.num * avctx->ticks_per_frame * (uint64_t) st->time_base.den) {
av_reduce(&st->r_frame_rate.num, &st->r_frame_rate.den,
avctx->time_base.den, (int64_t)avctx->time_base.num * avctx->ticks_per_frame, INT_MAX);
} else {
st->r_frame_rate.num = st->time_base.den;
st->r_frame_rate.den = st->time_base.num;
}
}
if (st->internal->display_aspect_ratio.num && st->internal->display_aspect_ratio.den) {
AVRational hw_ratio = { avctx->height, avctx->width };
st->sample_aspect_ratio = av_mul_q(st->internal->display_aspect_ratio,
hw_ratio);
}
}
else if (avctx->codec_type == AVMEDIA_TYPE_AUDIO)
{ //音频简单,只是赋值 bits_per_coded_sample;
if (!avctx->bits_per_coded_sample)
avctx->bits_per_coded_sample = av_get_bits_per_sample(avctx->codec_id);
}
}
}
if (probesize)
estimate_timings(ic, old_offset); //估算和打印流的开始时间,持续时间
//判定信息是否找到
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
if (!has_codec_parameters(st, &errmsg)) { //如果没有参数,则打印警告信息
char buf[256];
avcodec_string(buf, sizeof(buf), st->internal->avctx, 0);
av_log(ic, AV_LOG_WARNING,
"Could not find codec parameters for stream %d (%s): %s\n"
"Consider increasing the value for the 'analyzeduration' (%"PRId64") and 'probesize' (%"PRId64") options\n",
i, buf, errmsg, ic->max_analyze_duration, ic->probesize);
}
}
ret = compute_chapters_end(ic); //计算章节
/* update the stream parameters from the internal codec contexts */
//把内部avctx 数据copy到 st->codecpar, 和 st->codec
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
if (st->internal->avctx_inited) {
ret = avcodec_parameters_from_context(st->codecpar, st->internal->avctx);
ret = add_coded_side_data(st, st->internal->avctx);
ret = avcodec_parameters_to_context(st->codec, st->codecpar);
st->codec->framerate = st->avg_frame_rate;
// Fields unavailable in AVCodecParameters
st->codec->coded_width = st->internal->avctx->coded_width;
st->codec->coded_height = st->internal->avctx->coded_height;
st->codec->properties = st->internal->avctx->properties;
}
find_stream_info_err: //非正常退出或正常退出
for (i = 0; i < ic->nb_streams; i++) {
st = ic->streams[i];
释放内存对象
}
return ret;
}
如此我才第一次单步走过avformat_find_stream_info()
经查, 发现对于h264的流,它直接就初始化其为2. avctx->ticks_per_frame = 2;
avctx->time_base = av_inv_q(av_mul_q(avctx->framerate, (AVRational){avctx->ticks_per_frame, 1}));
计算得到的是{1,50}, 因为avctx->framerate={25,1}, avctx->ticks_per_frame={2,1},相乘为{50,1}
取反为{1,50}, 这就是avctx 帧率到时基的计算过程
其在h264_slice_header_init 中, 由sps(sequence parameter set) 来设置,不过也用到了ticks_per_frame
int64_t den = sps->time_scale;
av_reduce(&h->avctx->framerate.den, &h->avctx->framerate.num,
sps->num_units_in_tick * h->avctx->ticks_per_frame, den, 1 << 30);
其中 sps->time_scale 为 50 => den=50
sps->num_units_in_tick = 1,
h->avctx->ticks_per_frame =2, 两者相乘为2,分母time_scale为50,约分简化
算得: avctx->framerate={25,1}