author: hjjdebug
date: 2025年 04月 02日 星期三 14:06:06 CST
description: avformat_find_stream_info 代码简明注释与理解
文章目录
- 1. 前言
- 2. 代码标注
- 3 补充:
- 3.1. 关于avctx->ticks_per_frame,
- 3.2. 关于avctx->time_base 的计算, 由帧率导出.
- 3.3. 关于 avctx->framerate 帧率的计算.
1. 前言
600行代码如何阅读, 单步调试有死循环,累到手抽筋,跟不出来,
采用重点标注之法,非重点用…标注, 只跟一层(就是本层).
把代码缩减到 200多行 并重点标注,
至此理解了avformat_find_stream_info 到底都找了什么东西,怎么找的.
2. 代码标注
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 dtsfor (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_wcount = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ? //有时间戳取值流内部 duration_countst->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加1count_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 AVCodecParametersst->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()
3 补充:
3.1. 关于avctx->ticks_per_frame,
经查, 发现对于h264的流,它直接就初始化其为2. avctx->ticks_per_frame = 2;
3.2. 关于avctx->time_base 的计算, 由帧率导出.
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 帧率到时基的计算过程
3.3. 关于 avctx->framerate 帧率的计算.
其在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}