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

开始学习前想说的话

这是avformat_find_stream_info函数解读文章的最后一篇,在最后这一篇解读文章中会对最后的两次大循环进行解读。最后的两次循环,是为了find stream这样一个操作做好兜底的准备,将没有做好初始化的属性最后进行一次检查和补充,完成函数的全部任务。

函数实现6-计算时间相关参数

在进入第五次循环之前,find stream函数还进行了一个时间参数的计算以及做了一部分提前设置

    if (probesize)
        estimate_timings(ic, old_offset);

    av_opt_set(ic, "skip_clear", "0", AV_OPT_SEARCH_CHILDREN);

    if (ret >= 0 && ic->nb_streams)
        /* We could not have all the codec parameters before EOF. */
        ret = -1;

这里使用了estimate_timings(ic, old_offset) 函数来帮助计算时间有关的参数,之后将skip_clear设置到avctx ic中,再将之前可能由于codec设置失败所改变的ret复位到-1s
在estimate_timings函数中,其实也进行了所有stream的循环和操作:

static void estimate_timings(AVFormatContext *ic, int64_t old_offset)
{
    int64_t file_size;

    /* get the file size, if possible */
    if (ic->iformat->flags & AVFMT_NOFILE) {
        file_size = 0;
    } else {
        file_size = avio_size(ic->pb);
        file_size = FFMAX(0, file_size);
    }

    if ((!strcmp(ic->iformat->name, "mpeg") ||
         !strcmp(ic->iformat->name, "mpegts")) &&
        file_size && (ic->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
        /* get accurate estimate from the PTSes */
        estimate_timings_from_pts(ic, old_offset);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_PTS;
    } else if (has_duration(ic)) {
        /* at least one component has timings - we use them for all
         * the components */
        fill_all_stream_timings(ic);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_STREAM;
    } else {
        /* less precise: use bitrate info */
        estimate_timings_from_bit_rate(ic);
        ic->duration_estimation_method = AVFMT_DURATION_FROM_BITRATE;
    }
    update_stream_timings(ic);

    {
        int i;
        AVStream av_unused *st;
        for (i = 0; i < ic->nb_streams; i++) {
            st = ic->streams[i];
            if (st->time_base.den)
            av_log(ic, AV_LOG_TRACE, "stream %d: start_time: %0.3f duration: %0.3f\n", i,
                   (double) st->start_time * av_q2d(st->time_base),
                   (double) st->duration   * av_q2d(st->time_base));
        }
        av_log(ic, AV_LOG_TRACE,
                "format: start_time: %0.3f duration: %0.3f bitrate=%"PRId64" kb/s\n",
                (double) ic->start_time / AV_TIME_BASE,
                (double) ic->duration   / AV_TIME_BASE,
                (int64_t)ic->bit_rate / 1000);
    }
}

可以看见,在该函数中主要是通过pts,或者已知的stream的时长,或者通过bitrate计算来完成avctx中关于时间的参数设置,之后再对ic中所有的streams进行了一次循环,但这里的循环并没有做相关计算和操作,只是进行了时间参数的信息打印,具体的时间参数计算函数则要到estimate_timings_from_pts,fill_all_stream_timings,还有estimate_timings_from_bit_rate里面去寻找了,由于是find stream函数讲解,不做过多的音视频计算说明的讲解展开。

函数实现7-第五次循环

回到find stream中,接下来是第五次循环

    for (i = 0; i < ic->nb_streams; i++) {
        const char *errmsg;
        st = ic->streams[i];

        /* if no packet was ever seen, update context now for has_codec_parameters */
        if (!st->internal->avctx_inited) {
            if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO &&
                st->codecpar->format == AV_SAMPLE_FMT_NONE)
                st->codecpar->format = st->internal->avctx->sample_fmt;
            ret = avcodec_parameters_to_context(st->internal->avctx, st->codecpar);
            if (ret < 0)
                goto find_stream_info_err;
        }
        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' and 'probesize' options\n",
                   i, buf, errmsg);
        } else {
            ret = 0;
        }
    }

	compute_chapters_end(ic);

第五次循环首先对每个stream的avctx_inited标识位进行检查,在没有进行标识,没有进行packet检查时,就需要更新avctx了,对于音频格式且未设置forma时,设置好format并且使用avcodec_parameters_to_context设置好当前遍历到的stream的编解码参数;
之后,再次使用has_codec_parameters来检查stream中是否已经设置好各项参数,在没有设置好的情况下进行警告。
在完成第五次循环后,使用了compute_chapters_end来设置好AVChapter章节的end末尾属性。在end属性的的设置过程中也用到了之前设置好的avctx上下文中的各项参数。

函数实现8-第六次循环

在最后一次循环里,则是通过stream的内部codec参数来做好stream自身剩下一些参数的补充和设置。

    /* update the stream parameters from the internal codec contexts */
    for (i = 0; i < ic->nb_streams; i++) {
        st = ic->streams[i];

        if (st->internal->avctx_inited) {
            int orig_w = st->codecpar->width;
            int orig_h = st->codecpar->height;
            ret = avcodec_parameters_from_context(st->codecpar, st->internal->avctx);
            if (ret < 0)
                goto find_stream_info_err;
#if FF_API_LOWRES
            // The decoder might reduce the video size by the lowres factor.
            if (st->internal->avctx->lowres && orig_w) {
                st->codecpar->width = orig_w;
                st->codecpar->height = orig_h;
            }
#endif
        }

#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
        ret = avcodec_parameters_to_context(st->codec, st->codecpar);
        if (ret < 0)
            goto find_stream_info_err;

#if FF_API_LOWRES
        // The old API (AVStream.codec) "requires" the resolution to be adjusted
        // by the lowres factor.
        if (st->internal->avctx->lowres && st->internal->avctx->width) {
            st->codec->lowres = st->internal->avctx->lowres;
            st->codec->width = st->internal->avctx->width;
            st->codec->height = st->internal->avctx->height;
        }
#endif

        if (st->codec->codec_tag != MKTAG('t','m','c','d')) {
            st->codec->time_base = st->internal->avctx->time_base;
            st->codec->ticks_per_frame = st->internal->avctx->ticks_per_frame;
        }
        st->codec->framerate = st->avg_frame_rate;

        if (st->internal->avctx->subtitle_header) {
            st->codec->subtitle_header = av_malloc(st->internal->avctx->subtitle_header_size);
            if (!st->codec->subtitle_header)
                goto find_stream_info_err;
            st->codec->subtitle_header_size = st->internal->avctx->subtitle_header_size;
            memcpy(st->codec->subtitle_header, st->internal->avctx->subtitle_header,
                   st->codec->subtitle_header_size);
        }

        // 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;
FF_ENABLE_DEPRECATION_WARNINGS
#endif

        st->internal->avctx_inited = 0;
    }

进入循环后,检查avctx_inited标识位,对于已经完成初始化的stream,先使用avcodec_parameters_from_context,根据已经设置好的AVCodecContext来设置好当前遍历到的stream中的parameter各项属性,也设置好宽高属性。

之后,使用avcodec_parameters_to_context来反向地通过codecpar来设置好编解码器codec的各项属性,也就是整体属性数据流程是 avctx–>codecpar–>codec来变化的。
要注意到这里ffmpeg是加了版本控制的,对于比较新的版本(LIBAVFORMAT_VERSION_MAJOR>=59)来说这一段代码是不需要的。在最后将遍历到的stream的inited属性重置为0,完成设置工作。

函数实现9-err的情况和收尾工作

在函数最后,是上面的函数实现出现异常时的跳转以及正常的收尾工作


find_stream_info_err:
    for (i = 0; i < ic->nb_streams; i++) {
        st = ic->streams[i];
        if (st->info)
            av_freep(&st->info->duration_error);
        avcodec_close(ic->streams[i]->internal->avctx);
        av_freep(&ic->streams[i]->info);
        av_bsf_free(&ic->streams[i]->internal->extract_extradata.bsf);
        av_packet_free(&ic->streams[i]->internal->extract_extradata.pkt);
    }
    if (ic->pb)
        av_log(ic, AV_LOG_DEBUG, "After avformat_find_stream_info() pos: %"PRId64" bytes read:%"PRId64" seeks:%d frames:%d\n",
               avio_tell(ic->pb), ic->pb->bytes_read, ic->pb->seek_count, count);
    return ret;

收尾工作中实际上也经历了一次循环,将所有stream进行了遍历,关闭了在find stream工作中所用到的各项codec,内存空间以及packet等等。最后进行了输出打印,并返回处理结果ret

结尾时要说的话

avformat_find_stream_info的四篇解读总算是写完了。总体来说,这个函数的任务还是非常重要且复杂的。整个stream中的各项参数在最开始时几乎都是空的,需要做填空题一个个填上,open_input只是读取文件头,并保存一部分的数据,但这之后AVStream中的数据大部分都是没有完成填空的。
find_stream_info则是负责来将各项重要属性字段完成填空,在open_input的时候,已经解析到很多数据了,所以可以直接来完成填空,填空完成直接成功返回。而在需要更深入的数据,比如pix_fmt等等数据属性时,就需要进行一部分数据的解码(一旦涉及到解码,那就可能出现各种特殊的异常情况,比如EOF,比如缓存中数据需要flushing等等),这就导致该函数的功能复杂,循环也多。同时,start_time,帧率等等数据也会在这个函数里完成计算。该函数包含的很多子函数也涉及到很多音视频的基础知识,对于学习音视频原理和理解ffmpeg来说都值得往下详细展开和深入解读。不过这里空间太小,我写不下(费马式开摆!)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值