0x00 前置信息

版本:ffmpeg2.2.0。

0x01 代码

int avformat_open_input(AVFormatContext **ps, const char *filename,
                                        AVInputFormat *fmt, AVDictionary **options)
{
        AVFormatContext *s = *ps;
        int ret = 0;
        AVDictionary *tmp = NULL;
        ID3v2ExtraMeta *id3v2_extra_meta = NULL;

        if (!s && !(s = avformat_alloc_context()))
                return AVERROR(ENOMEM);
        if (fmt)
                s->iformat = fmt;

        if (options)
                av_dict_copy(&tmp, *options, 0);

        if ((ret = av_opt_set_dict(s, &tmp)) < 0)
                goto fail;

        if ((ret = init_input(s, filename, &tmp)) < 0)
                goto fail;

        /* Check filename in case an image number is expected. */
        if (s->iformat->flags & AVFMT_NEEDNUMBER) {
                if (!av_filename_number_test(filename)) {
                        ret = AVERROR(EINVAL);
                        goto fail;
                }
        }

        s->duration = s->start_time = AV_NOPTS_VALUE;
        av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));

        /* Allocate private data. */
        if (s->iformat->priv_data_size > 0) {
                if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) {
                        ret = AVERROR(ENOMEM);
                        goto fail;
                }
                if (s->iformat->priv_class) {
                        *(const AVClass **) s->priv_data = s->iformat->priv_class;
                        av_opt_set_defaults(s->priv_data);
                        if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0)
                                goto fail;
                }
        }

        /* e.g. AVFMT_NOFILE formats will not have a AVIOContext */
        if (s->pb)
                ff_id3v2_read(s, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

        if (s->iformat->read_header)
                if ((ret = s->iformat->read_header(s)) < 0)
                        goto fail;

        if (id3v2_extra_meta &&
                (ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0)
                goto fail;
        ff_id3v2_free_extra_meta(&id3v2_extra_meta);

        if ((ret = queue_attached_pictures(s)) < 0)
                goto fail;

        if (s->pb && !s->data_offset)
                s->data_offset = avio_tell(s->pb);

        s->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;

        if (options) {
                av_dict_free(options);
                *options = tmp;
        }
        *ps = s;
        return 0;

fail:
        ff_id3v2_free_extra_meta(&id3v2_extra_meta);
        av_dict_free(&tmp);
        if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
                avio_close(s->pb);
        avformat_free_context(s);
        *ps = NULL;
        return ret;
}

0x02 接口功能

  1. 创建AVFormatContext结构并填充其中的关键字段
  2. 打开一个指定URI,初始化输入模块。
  3. 解析多媒体文件或流的头信息,为每个流创建AVStream结构。

0x03 流程分析

  1. 如果s为空,则调用avformat_alloc_context方法为AVFormatContext结构体(以下简称s)分配内存,并使用默认值设置,具体的默认设置是av_format_context_class。

    注:AVFormatContext这个结构体描述了一个媒体文件或媒体流的构成和基本信息。
    
  2. 如果传入的AVInputFormat类型形参fmt不为空,即判断输入格式是否在调用该方法之前已经探明,

    如果是则直接设置s的iformat成员为该指针。如果不是则需要在后续步骤中分析输入格式。
    
  3. AVDictionary相关设置暂不分析。
  4. 调用init_input,初始化输入。
    4.1 判断s的pb成员是否为空,pb成员是AVIOContext类型的,AVIOContext是用于IO操作的结构体,其内部还包含缓冲区。

       这里可以这么理解,如果该指针不为空,说明可以直接通过该指针进一步调用IO方法,而如果该指针为空,对于一个流输入来说,说明尚未建立网络层连接,则无法进行IO操作。

    4.2 如果pb成员不为空,那么判断iformat成员是否为空,不为空则调用av_probe_input_buffer去分析输入格式,不为空该方法调用返回。

4.3 如果pb成员为空,通过avio_open2去建立连接,并设置pb的读写方法指针。

URLProtocol ff_librtmp_protocol = {
                 .name                = "rtmp",
                 .url_open            = rtmp_open,
                 .url_read            = rtmp_read,
                 .url_write           = rtmp_write,    
                 .url_close           = rtmp_close,    
                 .url_read_pause      = rtmp_read_pause,    
                 .url_read_seek       = rtmp_read_seek,    
                 .url_get_file_handle = rtmp_get_file_handle,   
                 .priv_data_size      = sizeof(LibRTMPContext),    
                 .priv_data_class     = &librtmp_class,    
                 .flags               = URL_PROTOCOL_FLAG_NETWORK,
};  

4.4 如果iformat成员为空,调用av_probe_input_buffer方法去初始化iformat,流程如下:

   4.4.1 通过avio_read进一步调用pb的read方法从网络层读取数据。
         例如:接收一个RTMP协议的输入,read方法实际指向rtmp_read,读取第一帧数据到pb的buffer中。仔细看librtmp中RTMP_Read接口,它会判断是否是第一帧,如果是第一帧数据,会向返回的数据中添加一个flv头。
   4.4.2 根据读到数据探测输入的格式。具体的猜测方法是这样的,通过av_iformat_next接口不断获取已注册的fmt结构体指针,如果该fmt存在read_probe方法,则调用该方法,分析文件格式。
         例如:判断flv格式的方法是flv_probe,它就是比对前三个字节。
   4.4.3 判断得到文件格式后,s的iformat成员就不会空了,例如flv:
AVInputFormat ff_flv_demuxer = {
                   .name           = "flv",    
                   .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),    
                   .priv_data_size = sizeof(FLVContext),    
                   .read_probe     = flv_probe,   
                   .read_header    = flv_read_header,    
                   .read_packet    = flv_read_packet,    
                   .read_seek      = flv_read_seek,    
                   .read_close     = flv_read_close,    
                   .extensions     = "flv",    
                   .priv_class     = &class,
};
  1. 设置duration和start_time。
  2. 根据需要,为iformat成员的priv_data分配内存。
  3. 读入一些数据分析该输入是否包含ID3(MP3相关)的头信息。

    如果不包含ID3数据,并不会改变pb->buffer的buf_ptr指针,即数据开始指针。
    
  4. 如果read_header方法指针不为空,则调用该方法,具体以flv_read_header方法分析:

    8.1 根据flv格式,跳过前4个字节,读取8 bit的流信息位。
    8.2 如果存在视频,则创建视频流。即s的streams指针。并设置codec_type,设置pts info。音频类似。
    8.3 最后移动缓冲区指针,相当于跳过了flv的前13(9+4)个字节。
    
  5. ID3内容处理。
  6. queue_attached_pictures尚未弄懂。

0x04 流程图

avformat_open_input.png

0x05 总结

还有一些内容尚未细致分析,后期会逐步完善。

参考:

  1. http://blog.csdn.net/woshinia/article/details/8131773