博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ffmpeg源码分析 (五)
阅读量:5740 次
发布时间:2019-06-18

本文共 13083 字,大约阅读时间需要 43 分钟。

hot3.png

前言

    我们已经分析了demo中的第一步 avformat_open_input 这是一段不短的旅程,总的来说,我们已经完成了AVFormatContext的创建和初始化,找到了文件对应的AVInputFormat,也已经创建了AVStream,当然也初始化了AVIOContext,找到了操作文件的URLProtocol。

    下一个调用的方法是 avformat_find_stream_info 我们会读取一些媒体文件的数据,然后分析文件的真正信息。

    该方法也是一个比较复杂的方法了,这里我做了一个调用关系图

    

avformat_find_stream_info

    先来看一下函数的定义,参数只有两个,一个是AVFormatContext,还有一个是很少用到的额外参数字典。函数的介绍很长,但是总结来说就是获取media的信息。

/** * 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);

    函数的实现非常长,我们一点点来看

int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options){    .........    av_opt_set(ic, "skip_clear", "1", AV_OPT_SEARCH_CHILDREN);    //初始化最大读取时长    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;    }    if (ic->pb)        av_log(ic, AV_LOG_DEBUG, "Before avformat_find_stream_info() pos: %"PRId64" bytes read:%"PRId64" seeks:%d nb_streams:%d\n",               avio_tell(ic->pb), ic->pb->bytes_read, ic->pb->seek_count, ic->nb_streams);    //遍历所有通道    for (i = 0; i < ic->nb_streams; i++) {        .........        // only for the split stuff         //如果parser解析器还没有被设置,那么需要去初始化解析器        //在demo中pareser解析器已经被设置了,所以不需要运行。        //实际上 av_parser_init 方法就是遍历解析器列表,和codec_id进行对比,选择一个。        //然后调用 AVCodecParser 的 parser_init方法        //解析器仅用于需要将数据进行切片的地方,比如h264需要将数据切成一个一个packat        if (!st->parser && !(ic->flags & AVFMT_FLAG_NOPARSE) && st->request_probe <= 0) {            st->parser = av_parser_init(st->codecpar->codec_id);            if (st->parser) {                if (st->need_parsing == AVSTREAM_PARSE_HEADERS) {                    st->parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;                } else if (st->need_parsing == AVSTREAM_PARSE_FULL_RAW) {                    st->parser->flags |= PARSER_FLAG_USE_CODEC_TS;                }            } else if (st->need_parsing) {                av_log(ic, AV_LOG_VERBOSE, "parser not found for codec "                       "%s, packets or times may be invalid.\n",                       avcodec_get_name(st->codecpar->codec_id));            }        }

    解析器的是整个ffmpeg工作流程中比较重要的一部分,我们可能会在以后慢慢研究,当前我们关注的就是如何读取信息的,

.........             ret = avcodec_parameters_to_context(avctx, st->codecpar);        if (ret < 0)            goto find_stream_info_err;        if (st->request_probe <= 0)            st->internal->avctx_inited = 1;        //搜索AVStream匹配的decoder        codec = find_probe_decoder(ic, st, st->codecpar->codec_id);

    find_probe_decoder我们在后面详细分析

..............  //初始化AVCodecContext,打开decoder /* Ensure that subtitle_header is properly set. */        if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE            && codec && !avctx->codec) {            if (avcodec_open2(avctx, codec, options ? &options[i] : &thread_opt) < 0)                av_log(ic, AV_LOG_WARNING,                       "Failed to open codec in %s\n",__FUNCTION__);        }        // Try to just open decoders, in case this is enough to get parameters.        if (!has_codec_parameters(st, NULL) && st->request_probe <= 0) {            if (codec && !avctx->codec)                if (avcodec_open2(avctx, codec, options ? &options[i] : &thread_opt) < 0)                    av_log(ic, AV_LOG_WARNING,                           "Failed to open codec in %s\n",__FUNCTION__);        }............

    

.............read_size = 0;//循环读取数据获取媒体信息    for (;;) {        .......        //遍历所有AVStream,查看是否还有通道需要读取数据,如果某个通道还需要获取数据,直接退出这个循环        /* 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];            //has_codec_parameters 这个方法用来判断AVStream是否已经读取到了需要的信息(参数是否被赋值),            //实现上挺简单的            if (!has_codec_parameters(st, NULL))                break;            ......        }        //如果所有AVStream的信息都已经被读出,那么退出循环。        ..........        //读取一帧压缩码流数据        /* NOTE: A new stream can be added there if no header in file         * (AVFMTCTX_NOHEADER). */        ret = read_frame_internal(ic, &pkt1);                //根据这一帧获取信息        ......         /* 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. */        //如果还没有足够的信息,那么就会尝试着打开decoder,解码数据。        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++;    }.........

    DTS、PTS 的概念如下所述:

  • DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
  • PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。

    需要注意的是:虽然 DTS、PTS 是用于指导播放端的行为,但它们是在编码的时候由编码器生成的。

/**     * Real base framerate of the stream.     * This is the lowest framerate with which all timestamps can be     * represented accurately (it is the least common multiple of all     * framerates in the stream). Note, this value is just a guess!     * For example, if the time base is 1/90000 and all frames have either     * approximately 3600 or 1800 timer ticks, then r_frame_rate will be 50/1.     */    //计算AVStream 的 r_frame_rate    ff_rfps_calculate(ic);    //计算 AVStream avg_frame_rate    ..........    // 用于估算AVFormatContext以及AVStream的时长duration    if (probesize)        estimate_timings(ic, old_offset);    ..........

 

find_probe_decoder

    这是一个比较重要的方法,用于初始化AVStream的AVCodec。

static const AVCodec *find_probe_decoder(AVFormatContext *s, const AVStream *st, enum AVCodecID codec_id){    const AVCodec *codec;    // 如果显示指定了使用h264,那就大胆的去搜索h264的decodec,规则就是去匹配AVCodec的name字段。#if CONFIG_H264_DECODER    /* Other parts of the code assume this decoder to be used for h264,     * so force it if possible. */    if (codec_id == AV_CODEC_ID_H264)        return avcodec_find_decoder_by_name("h264");#endif    // 寻找codec    codec = find_decoder(s, st, codec_id);    if (!codec)        return NULL;        if (codec->capabilities & AV_CODEC_CAP_AVOID_PROBING) {        const AVCodec *probe_codec = NULL;        while (probe_codec = av_codec_next(probe_codec)) {            if (probe_codec->id == codec_id &&                    av_codec_is_decoder(probe_codec) &&                    !(probe_codec->capabilities & (AV_CODEC_CAP_AVOID_PROBING | AV_CODEC_CAP_EXPERIMENTAL))) {                return probe_codec;            }        }    }    return codec;}
static const AVCodec *find_decoder(AVFormatContext *s, const AVStream *st, enum AVCodecID codec_id){    //如果AVCodecContext的codec不是空,那么直接返回。#if FF_API_LAVF_AVCTXFF_DISABLE_DEPRECATION_WARNINGS    if (st->codec->codec)        return st->codec->codec;FF_ENABLE_DEPRECATION_WARNINGS#endif    //如果AVStream指定了默认的Codec,那么返回。    switch (st->codecpar->codec_type) {    case AVMEDIA_TYPE_VIDEO:        if (s->video_codec)    return s->video_codec;        break;    case AVMEDIA_TYPE_AUDIO:        if (s->audio_codec)    return s->audio_codec;        break;    case AVMEDIA_TYPE_SUBTITLE:        if (s->subtitle_codec) return s->subtitle_codec;        break;    }    //根据codec_id寻找对应的AVCodec    return avcodec_find_decoder(codec_id);}

 

try_decode_frame

    该函数用于尝试解码。定义如下

/* returns 1 or 0 if or if not decoded data was returned, or a negative error */static int try_decode_frame(AVFormatContext *s, AVStream *st, AVPacket *avpkt,                            AVDictionary **options)

    入参都很正,完全能看懂。

/* returns 1 or 0 if or if not decoded data was returned, or a negative error */static int try_decode_frame(AVFormatContext *s, AVStream *st, AVPacket *avpkt,                            AVDictionary **options){    AVCodecContext *avctx = st->internal->avctx;    const AVCodec *codec;    int got_picture = 1, ret = 0;    AVFrame *frame = av_frame_alloc();    AVSubtitle subtitle;    AVPacket pkt = *avpkt;    int do_skip_frame = 0;    enum AVDiscard skip_frame;    if (!frame)        return AVERROR(ENOMEM);    //如果decoder没有打开,或者没有decoder    if (!avcodec_is_open(avctx) &&        st->info->found_decoder <= 0 &&        (st->codecpar->codec_id != -st->info->found_decoder || !st->codecpar->codec_id)) {        AVDictionary *thread_opt = NULL;        //寻找decoder        codec = find_probe_decoder(s, st, st->codecpar->codec_id);        if (!codec) {            st->info->found_decoder = -st->codecpar->codec_id;            ret                     = -1;            goto fail;        }        /* Force thread count to 1 since the H.264 decoder will not extract         * SPS and PPS to extradata during multi-threaded decoding. */        av_dict_set(options ? options : &thread_opt, "threads", "1", 0);        if (s->codec_whitelist)            av_dict_set(options ? options : &thread_opt, "codec_whitelist", s->codec_whitelist, 0);        //打开decoder        ret = avcodec_open2(avctx, codec, options ? options : &thread_opt);        if (!options)            av_dict_free(&thread_opt);        if (ret < 0) {            st->info->found_decoder = -avctx->codec_id;            goto fail;        }        st->info->found_decoder = 1;    } else if (!st->info->found_decoder)        st->info->found_decoder = 1;    if (st->info->found_decoder < 0) {        ret = -1;        goto fail;    }    if (avpriv_codec_get_cap_skip_frame_fill_param(avctx->codec)) {        do_skip_frame = 1;        skip_frame = avctx->skip_frame;        avctx->skip_frame = AVDISCARD_ALL;    }    //如果pkt有内容,并且AVStream还没有获得足够数据,那么进行解码。    while ((pkt.size > 0 || (!pkt.data && got_picture)) &&           ret >= 0 &&           (!has_codec_parameters(st, NULL) || !has_decode_delay_been_guessed(st) ||            (!st->codec_info_nb_frames &&             (avctx->codec->capabilities & AV_CODEC_CAP_CHANNEL_CONF)))) {        got_picture = 0;        //如果是音频流或者视频流        if (avctx->codec_type == AVMEDIA_TYPE_VIDEO ||            avctx->codec_type == AVMEDIA_TYPE_AUDIO) {            ret = avcodec_send_packet(avctx, &pkt);            if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)                break;            if (ret >= 0)                pkt.size = 0;            ret = avcodec_receive_frame(avctx, frame);            if (ret >= 0)                got_picture = 1;            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)                ret = 0;        //如果是字幕流        } else if (avctx->codec_type == AVMEDIA_TYPE_SUBTITLE) {            ret = avcodec_decode_subtitle2(avctx, &subtitle,                                           &got_picture, &pkt);            if (ret >= 0)                pkt.size = 0;        }        if (ret >= 0) {            if (got_picture)                st->nb_decoded_frames++;            ret       = got_picture;        }    }    if (!pkt.data && !got_picture)        ret = -1;fail:    if (do_skip_frame) {        avctx->skip_frame = skip_frame;    }    av_frame_free(&frame);    return ret;}

    字幕流和音视频流的处理方案不同,不过每种流的解码处理我们后面再介绍。

 

estimate_timings

    estimate_timings()用于估算AVFormatContext以及AVStream的时长duration。

    从estimate_timings()的代码中可以看出,有3种估算方法:

(1)通过pts(显示时间戳)。该方法调用estimate_timings_from_pts()。它的基本思想就是读取视音频流中的结束位置AVPacket的PTS和起始位置AVPacket的PTS,两者相减得到时长信息。
(2)通过已知流的时长。该方法调用fill_all_stream_timings()。它的代码没有细看,但从函数的注释的意思来说,应该是当有些视音频流有时长信息的时候,直接赋值给其他视音频流。
(3)通过bitrate(码率)。该方法调用estimate_timings_from_bit_rate()。它的基本思想就是获得整个文件大小,以及整个文件的bitrate,两者相除之后得到时长信息。
 

结语

参考:https://blog.csdn.net/leixiaohua1020/article/details/44084321

转载于:https://my.oschina.net/zzxzzg/blog/1860244

你可能感兴趣的文章
我的友情链接
查看>>
DNS显性+隐性URL转发原理
查看>>
我的友情链接
查看>>
网易有道 IP地址、手机号码归属地和身份证 查询接口API
查看>>
鼠标停留在GridView某一行时行的颜色改变
查看>>
系列3:WAS Liberty Profile hello mysql jdbc
查看>>
基础知识:python模块的导入
查看>>
Android MVC之我的实现
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
关于批处理-1
查看>>
Tomcat部署Web应用方法总结
查看>>
Python3 django2.0 字段加密 解密 AES
查看>>
CCNA实验之:网络地址转换(NAT)实验
查看>>
计算机网络原理笔记-停止等待协议
查看>>
确定当前记录和下一条记录之间相差的天数
查看>>
sql语句返回主键SCOPE_IDENTITY()
查看>>
机器学习开源项目精选TOP30
查看>>
iOS开发-邮件发送
查看>>
/etc/resolv.conf文件详解
查看>>