从学龄前开始解读FFMPEG代码 之 avformat_find_stream_info函数二

开始学习前想说的话

上一篇文章的结尾讲到,在开始循环前以及第一次循环内,avformat_find_stream_info都做了哪些事情,同时也提到avformat_find_stream_info这个函数就是通过多个循环来完成工作的,所以,接下来这一篇则继续按照讲解循环的形式来说明。

函数实现2-第二个循环的内容

话不多说先上代码,从第二个循环开始

    for (i = 0; i < ic->nb_streams; i++) {
#if FF_API_R_FRAME_RATE
        ic->streams[i]->info->last_dts = AV_NOPTS_VALUE;
#endif
        ic->streams[i]->info->fps_first_dts = AV_NOPTS_VALUE;
        ic->streams[i]->info->fps_last_dts  = AV_NOPTS_VALUE;
    }

第二次循环设置非常短,只有六行功能代码,内容实际上是围绕着每个stream中的info中dts来展开的,这个info是AVStream中的一个结构体,专门提供给avformat_find_stream_info来使用的,定义在avformat.h中,正如注释中写的那样

/**
     * Stream information used internally by avformat_find_stream_info()
     */
    struct {
        int64_t last_dts;
        int64_t duration_gcd;
        int duration_count;
        int64_t rfps_duration_sum;
        double (*duration_error)[2][MAX_STD_TIMEBASES];
        int64_t codec_info_duration;
        int64_t codec_info_duration_fields;
        int frame_delay_evidence;

        /**
         * 0  -> decoder has not been searched for yet.
         * >0 -> decoder found
         * <0 -> decoder with codec_id == -found_decoder has not been found
         */
        int found_decoder;

        int64_t last_duration;

        /**
         * Those are used for average framerate estimation.
         */
        int64_t fps_first_dts;
        int     fps_first_dts_idx;
        int64_t fps_last_dts;
        int     fps_last_dts_idx;

    } *info;

而在第二次循环中,相当于将AVStream中包含的三个dts相关的内容进行一下清空设置(设置为AV_NOPTS_VALUE),在后续中可以用于进行帧率的计算。
而关于dts,这个概念涉及到一个解码时间和显示时间的问题(pts),属于音视频编解码技术中的内容,这里不做过多展开。

函数实现3-第三次循环的内容

第三次循环是一个“死循环”,不过它并不会产生真正的死循环,先看一下代码

    read_size = 0;
    for (;;) {
        int analyzed_all_streams;
        if (ff_check_interrupt(&ic->interrupt_callback)) {
            ret = AVERROR_EXIT;
            av_log(ic, AV_LOG_DEBUG, "interrupted\n");
            break;
        }

        /* check if one codec still needs to be handled */
        for (i = 0; i < ic->nb_streams; i++) {
            int fps_analyze_framecount = 20;
            int count;

            st = ic->streams[i];
            if (!has_codec_parameters(st, NULL))
                break;
            /* If the timebase is coarse (like the usual millisecond precision
             * of mkv), we need to analyze more frames to reliably arrive at
             * the correct fps. */
            if (av_q2d(st->time_base) > 0.0005)
                fps_analyze_framecount *= 2;
            if (!tb_unreliable(st->internal->avctx))
                fps_analyze_framecount = 0;
            if (ic->fps_probe_size >= 0)
                fps_analyze_framecount = ic->fps_probe_size;
            if (st->disposition & AV_DISPOSITION_ATTACHED_PIC)
                fps_analyze_framecount = 0;
            /* variable fps and no guess at the real fps */
            count = (ic->iformat->flags & AVFMT_NOTIMESTAMPS) ?
                       st->info->codec_info_duration_fields/2 :
                       st->info->duration_count;
            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.
            if (st->info->frame_delay_evidence && count < 2 && st->internal->avctx->has_b_frames == 0)
                break;
            if (!st->internal->avctx->extradata &&
                (!st->internal->extract_extradata.inited ||
                 st->internal->extract_extradata.bsf) &&
                extract_extradata_check(st))
                break;
            if (st->first_dts == AV_NOPTS_VALUE &&
                !(ic->iformat->flags & AVFMT_NOTIMESTAMPS) &&
                st->codec_info_nb_frames < ((st->disposition & AV_DISPOSITION_ATTACHED_PIC) ? 1 : ic->max_ts_probe) &&
                (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO ||
                 st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))
                break;
        }
        analyzed_all_streams = 0;
        if (!missing_streams || !*missing_streams)
        if (i == ic->nb_streams) {
            analyzed_all_streams = 1;
            /* NOTE: If the format has no header, then we need to read some
             * packets to get most of the streams, so we cannot stop here. */
            if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) {
                /* If we found the info for all the codecs, we can stop. */
                ret = count;
                av_log(ic, AV_LOG_DEBUG, "All info found\n");
                flush_codecs = 0;
                break;
            }
        }
        /* We did not get all the codec info, but we read too much data. */
        if (read_size >= probesize) {
            ret = count;
            av_log(ic, AV_LOG_DEBUG,
                   "Probe buffer size limit of %"PRId64" bytes reached\n", probesize);
            for (i = 0; i < ic->nb_streams; i++)
                if (!ic->streams[i]->r_frame_rate.num &&
                    ic->streams[i]->info->duration_count <= 1 &&
                    ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO &&
                    strcmp(ic->iformat->name, "image2"))
                    av_log(ic, AV_LOG_WARNING,
                           "Stream #%d: not enough frames to estimate rate; "
                           "consider increasing probesize\n", i);
            break;
        }

        /* NOTE: A new stream can be added there if no header in file
         * (AVFMTCTX_NOHEADER). */
        ret = read_frame_internal(ic, &pkt1);
        if (ret == AVERROR(EAGAIN))
            continue;

        if (ret < 0) {
            /* EOF or error*/
            eof_reached = 1;
            break;
        }

        pkt = &pkt1;

        if (!(ic->flags & AVFMT_FLAG_NOBUFFER)) {
            ret = ff_packet_list_put(&ic->internal->packet_buffer,
                                     &ic->internal->packet_buffer_end,
                                     pkt, 0);
            if (ret < 0)
                goto find_stream_info_err;
        }

        st = ic->streams[pkt->stream_index];
        if (!(st->disposition & AV_DISPOSITION_ATTACHED_PIC))
            read_size += pkt->size;

        avctx = st->internal->avctx;
        if (!st->internal->avctx_inited) {
            ret = avcodec_parameters_to_context(avctx, st->codecpar);
            if (ret < 0)
                goto find_stream_info_err;
            st->internal->avctx_inited = 1;
        }

        if (pkt->dts != AV_NOPTS_VALUE && st->codec_info_nb_frames > 1) {
            /* check for non-increasing dts */
            if (st->info->fps_last_dts != AV_NOPTS_VALUE &&
                st->info->fps_last_dts >= pkt->dts) {
                av_log(ic, AV_LOG_DEBUG,
                       "Non-increasing DTS in stream %d: packet %d with DTS "
                       "%"PRId64", packet %d with DTS %"PRId64"\n",
                       st->index, st->info->fps_last_dts_idx,
                       st->info->fps_last_dts, st->codec_info_nb_frames,
                       pkt->dts);
                st->info->fps_first_dts =
                st->info->fps_last_dts  = AV_NOPTS_VALUE;
            }
            /* Check for a discontinuity in dts. If the difference in dts
             * is more than 1000 times the average packet duration in the
             * sequence, we treat it as a discontinuity. */
            if (st->info->fps_last_dts != AV_NOPTS_VALUE &&
                st->info->fps_last_dts_idx > st->info->fps_first_dts_idx &&
                (pkt->dts - (uint64_t)st->info->fps_last_dts) / 1000 >
                (st->info->fps_last_dts     - (uint64_t)st->info->fps_first_dts) /
                (st->info->fps_last_dts_idx - st->info->fps_first_dts_idx)) {
                av_log(ic, AV_LOG_WARNING,
                       "DTS discontinuity in stream %d: packet %d with DTS "
                       "%"PRId64", packet %d with DTS %"PRId64"\n",
                       st->index, st->info->fps_last_dts_idx,
                       st->info->fps_last_dts, st->codec_info_nb_frames,
                       pkt->dts);
                st->info->fps_first_dts =
                st->info->fps_last_dts  = AV_NOPTS_VALUE;
            }

            /* update stored dts values */
            if (st->info->fps_first_dts == AV_NOPTS_VALUE) {
                st->info->fps_first_dts     = pkt->dts;
                st->info->fps_first_dts_idx = st->codec_info_nb_frames;
            }
            st->info->fps_last_dts     = pkt->dts;
            st->info->fps_last_dts_idx = st->codec_info_nb_frames;
        }
        if (st->codec_info_nb_frames>1) {
            int64_t t = 0;
            int64_t limit;

            if (st->time_base.den > 0)
                t = av_rescale_q(st->info->codec_info_duration, st->time_base, AV_TIME_BASE_Q);
            if (st->avg_frame_rate.num > 0)
                t = FFMAX(t, av_rescale_q(st->codec_info_nb_frames, av_inv_q(st->avg_frame_rate), AV_TIME_BASE_Q));

            if (   t == 0
                && st->codec_info_nb_frames>30
                && st->info->fps_first_dts != AV_NOPTS_VALUE
                && st->info->fps_last_dts  != AV_NOPTS_VALUE)
                t = FFMAX(t, av_rescale_q(st->info->fps_last_dts - st->info->fps_first_dts, st->time_base, AV_TIME_BASE_Q));

            if (analyzed_all_streams)                                limit = max_analyze_duration;
            else if (avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) limit = max_subtitle_analyze_duration;
            else                                                     limit = max_stream_analyze_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(pkt);
                break;
            }
            if (pkt->duration) {
                if (avctx->codec_type == AVMEDIA_TYPE_SUBTITLE && pkt->pts != AV_NOPTS_VALUE && st->start_time != AV_NOPTS_VALUE && pkt->pts >= st->start_time) {
                    st->info->codec_info_duration = FFMIN(pkt->pts - st->start_time, st->info->codec_info_duration + pkt->duration);
                } else
                    st->info->codec_info_duration += pkt->duration;
                st->info->codec_info_duration_fields += st->parser && st->need_parsing && avctx->ticks_per_frame ==2 ? st->parser->repeat_pict + 1 : 2;
            }
        }
        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
#if FF_API_R_FRAME_RATE
            ff_rfps_add_frame(ic, st, pkt->dts);
#endif
            if (pkt->dts != pkt->pts && pkt->dts != AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
                st->info->frame_delay_evidence = 1;
        }
        if (!st->internal->avctx->extradata) {
            ret = extract_extradata(st, pkt);
            if (ret < 0)
                goto find_stream_info_err;
        }

        /* If still no information, we try to open the codec and to
         * decompress the frame. We try to avoid that in most cases as
         * it takes longer and uses more memory. For MPEG-4, we need to
         * decompress for QuickTime.
         *
         * If AV_CODEC_CAP_CHANNEL_CONF is set this will force decoding of at
         * least one frame of codec data, this makes sure the codec initializes
         * the channel configuration and does not only trust the values from
         * the container. */
        try_decode_frame(ic, st, pkt,
                         (options && i < orig_nb_streams) ? &options[i] : NULL);

        if (ic->flags & AVFMT_FLAG_NOBUFFER)
            av_packet_unref(pkt);

        st->codec_info_nb_frames++;
        count++;
    }

怎么说呢,非常非常的长,但是不用慌张,一步步来啃。
目前为止,我们还未在视频流中挖掘任何信息,没有对stream做探测和检查操作,所以在这个死循环中,就是要做好stream的挖掘探测,在这个死循环中,就会对stream中的数据进行probe,读取一定的数据到内存中,并根据数据内容做好设置工作。这也就是在循环开始前设置了read_size的原因。

首先,该函数会使用ff_check_interrupt来检测是否有用户层申请了中断请求,如果申请了中断那么通过中断函数,( avctx ic中的interrupt_callback回调函数)来结束整个死循环。(毕竟是死循环,还是有可能产生用户主动结束的情况)
接下来,再次对所有的streams进行一次遍历,检查stream中是否还有没有进行处理的编解码器(使用has_codec_parameters函数,来检查stream),如果还有没做处理的,那么进行进一步处理,否则通过break跳出当前stream的遍历(此时还没有跳出for(;;)的大循环,只是跳出了stream的遍历而已)。只有在做好处理的情况下,进行接下来的工作。
在确认了has_codec之后,在进一步处理中,根据各种可能出现的情况,(比如timebase过小(在mkv中常见),AVFormatContext ic中设置了fps_probe_size则直接拿来用,或者设置了特殊的标记位等等), 设置好fps_analyze_framecount;之后,还有一个连续判断是否break退出遍历,其中包括没有B帧,extradata检查,dts和timestamp检查等等。一句话总结的话,这里的一系列配置和检查实际上都是为了计算帧率等信息做好的准备。

之后,是一个退出循环的契机:

        analyzed_all_streams = 0;
        if (!missing_streams || !*missing_streams)
        if (i == ic->nb_streams) {
            analyzed_all_streams = 1;
            /* NOTE: If the format has no header, then we need to read some
             * packets to get most of the streams, so we cannot stop here. */
            if (!(ic->ctx_flags & AVFMTCTX_NOHEADER)) {
                /* If we found the info for all the codecs, we can stop. */
                ret = count;
                av_log(ic, AV_LOG_DEBUG, "All info found\n");
                flush_codecs = 0;
                break;
            }
        }

如果missing_stream不存在的话(这个变量在find_stream刚开始时在option中进行了搜索)那么就会进入这个判断,在遍历的i达到AVFormatContext ic中存在的stream的值的时候,将已经全部分析完成的标记位置1,如果ic中没有设置无header的标记时,那么就代表所有的stream都完成了信息搜索,可以退出循环了。

接下来,在完成检查后,进行一个判断,如果读取数据read_size大于推测大小probe_size的话,则代表推测数据实际上并不够,此时需要根据streams信息报出警告,并跳出循环 (这里的触发条件实际上要经过多次循环才有可能触发,第一次执行该循环时不会发生这种情况)

之后,使用read_frame_internal函数读取一帧数据到pkt1这个AVPacket结构体中,再使用ff_packet_list_put将pkt数据读入到ic(AVFormatContext结构体)缓存buffer中,并增加已读入size的大小 read_size += pkt->size
伴随着更新读入数据大小,如果st中没有完成avctx的数据初始化(通过avctx_inited来判断),那么则使用avcodec_paameters_to_context函数来更新avctx中所包含的参数,进行初始化
接下来,进入一个dts是否连续的判断,在dts的差距大于序列中平均分组持续时间的1000倍时,进行dts的警告,并将stream中的头dts和尾部dts设置为AV_NOPT_VALUE,之后再对头pts和尾部pts等相关数据进行设置(这里的dts判断以及处理应该也是涉及到音视频编解码算法的内容 我也并没有很弄懂是怎么样的操作)

在完成dts连续性检查后,判断已经处理的帧数是否大于1,在大于1的情况下,需要根据st中time_base,avg_frame_rate.num等数据,设置好两个变量 t 以及limit,在t>=limit时表示已经超出了时间duration,同样是一个判断退出循环的机制
之后,根据读入pkt的duration,设置好stream的codec_info_duration, (从前一帧到该帧所经过的pts)
如果解码的stream是视频类型,则还需要根据dts pts等设置好frame_delay_evidence,在这之后,根据读入的pkt设置好st中的extradata(通过extract_extradata() 函数来实现extradata的设置。
如果这个时候,还是没有找到足够的信息,那么就会尝试解压一些数据出来并做分析。
在这段的注释中写到,如果仍然没有找到信息,我们尝试打开编解码器并且解压缩帧。 我们需要在大多数情况下尽量避免做这样的事情,因为很需要很多的时间,并使用更多的内存。 对于MPEG-4,我们需要解压QuickTime。
如果设置AV_CODEC_CAP_CHANNEL_CONF,这将强制解码至少一帧编解码器数据,这确保编解码器初始化通道配置,并且不仅信任来自容器的值
尝试解压一帧的数据(使用try_decode_frame函数)。有些参数,如vidoe的pix_fmt是需要调用h264_decode_frame才可以获取其pix_fmt的。
在此之后,释放掉读入的pkt,将已经分析的帧和计算变量count两个计数变量自增1

总结来说,这个大循环里,所作的事情就是,检查一下所有的Stream中准备的信息是否完备,如果都已经完备,那么直接就可以返回,否则读取一帧数据,并通过解码获取到例如pix_fmt等等解压之后才能获取到的参数信息。

但是在一系列操作中,存在一种情况,假设视频文件非常短,只有几帧图像,那么可能会出现到达文件末尾EOF的情况。所以,接下来要做的就是产生了EOF特殊情况的处理。

结尾时要说的话

这里已经可以看到,find_stream已经正式发力了,面对不同的stream数据类型,需要做好各种检查,甚至进行视频解码,来获取到stream的真实信息和内容,而在循环里的各种检查也可以看出音视频格式错综复杂,面对的文件情况也很多。下一篇文章,进行EOF的处理以及编解码器的冲刷,并对第四次大循环进行说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值