06 FFmpeg4.4源码分析--avformat_open_input()

本文介绍了FFmpeg中avformat_open_input函数的作用与实现原理,详细分析了其参数及内部核心函数init_input的工作流程。

一、简单介绍

avformat_open_input()的主要作用是:打开一个媒体文件或RTSP链接并进行解析。

函数原型为:

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options);

各参数含义如下:

  • ps: 媒体相关的上下文结构信息,需要注意的是:若该函数调用失败,则其将被系统主动释放。
  • filename: 媒体文件名或URL.  
  • fmt:将要打开的媒体格式的操作结构,因为是读,所以是AVInputFormat结构.对应ffmpeg命令行中的 -f xxx段,如果指定了它,在打开文件中就不会探测文件的实际格式了,以它为准了.  
  • options:一个AVFormatContext和 demuxer-private 选项的字典。该函数返回时,将被销毁,无需配置则设置为NULL。

二、源码走读

下面的源码已经添加注释,读者可以粗略走读一下,子函数详细内容会在下面继续解释:

int avformat_open_input(AVFormatContext **ps, const char *filename,
                        ff_const59 AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    int i, ret = 0;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;
 
    // 如果ps未指定,则在此处会进行默认分配
    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);

    // 通过avformat_alloc_context分配则该参数必不为NULL
    if (!s->av_class) {
        av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n");
        return AVERROR(EINVAL);
    }

    // 如果指定了解复用器,则使用它,否则就自行探测
    if (fmt)
        s->iformat = fmt;
    
    // 是否指定了其操作参数信息,支持的参数在/libavformat/options_table.h声明
    if (options)
        av_dict_copy(&tmp, *options, 0);

    // 如果要使用自定义IO,则需要人为设置pb值,否则为NULL,表示的是系统管理
    if (s->pb)
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
    
    // 设置参数
    if ((ret = av_opt_set_dict(s, &tmp)) < 0)
        goto fail;
 
    // 根据filename给url参数赋值
    if (!(s->url = av_strdup(filename ? filename : ""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }
 
#if FF_API_FORMAT_FILENAME
FF_DISABLE_DEPRECATION_WARNINGS
    av_strlcpy(s->filename, filename ? filename : "", sizeof(s->filename));
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    // 打开媒体文件或链接,探测其格式
    if ((ret = init_input(s, filename, &tmp)) < 0)
        goto fail;
    s->probe_score = ret;
 
    if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) {
        s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist);
        if (!s->protocol_whitelist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }
 
    if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) {
        s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist);
        if (!s->protocol_blacklist) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
    }
 
    if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ',') <= 0) {
        av_log(s, AV_LOG_ERROR, "Format not on whitelist \'%s\'\n", s->format_whitelist);
        ret = AVERROR(EINVAL);
        goto fail;
    }
 
    avio_skip(s->pb, s->skip_initial_bytes);
 
    /* 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;
 
    /* 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_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
 
#if FF_API_DEMUXER_OPEN
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
#else
    if (s->iformat->read_header)
#endif
        if ((ret = s->iformat->read_header(s)) < 0)
            goto fail;
 
    if (!s->metadata) {
        s->metadata = s->internal->id3v2_meta;
        s->internal->id3v2_meta = NULL;
    } else if (s->internal->id3v2_meta) {
        av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
        av_dict_free(&s->internal->id3v2_meta);
    }
 
    if (id3v2_extra_meta) {
        if (!strcmp(s->iformat->name, "mp3") || !strcmp(s->iformat->name, "aac") ||
            !strcmp(s->iformat->name, "tta") || !strcmp(s->iformat->name, "wav")) {
            if ((ret = ff_id3v2_parse_apic(s, id3v2_extra_meta)) < 0)
                goto close;
            if ((ret = ff_id3v2_parse_chapters(s, id3v2_extra_meta)) < 0)
                goto close;
            if ((ret = ff_id3v2_parse_priv(s, id3v2_extra_meta)) < 0)
                goto close;
        } else
            av_log(s, AV_LOG_DEBUG, "demuxer does not support additional id3 data, skipping\n");
    }
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
 
    if ((ret = avformat_queue_attached_pictures(s)) < 0)
        goto close;
 
#if FF_API_DEMUXER_OPEN
    if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset)
#else
    if (s->pb && !s->internal->data_offset)
#endif
        s->internal->data_offset = avio_tell(s->pb);
 
    s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
 
    update_stream_avctx(s);
 
    for (i = 0; i < s->nb_streams; i++)
        s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id;
 
    if (options) {
        av_dict_free(options);
        *options = tmp;
    }
    *ps = s;
    return 0;
 
close:
    if (s->iformat->read_close)
        s->iformat->read_close(s);
fail:
    ff_id3v2_free_extra_meta(&id3v2_extra_meta);
    av_dict_free(&tmp);
    if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO))
        avio_closep(&s->pb);
    avformat_free_context(s);
    *ps = NULL;
    return ret;
}

 avformat_open_input()函数的核心代码,其实是init_input() 函数,主要作用为探测媒体资源的类型。

在代码走读之前,先介绍一下AVFMT_NOFILE标志的的含义:大部分libformat或者libavdevice解复用器都有AVFMT_NOFILE标志,表示该解复用器有自己的I/O函数,将不需要调用avio_open和avio_close对I/O进行操作

/* Open input file and probe the format if necessary. */
static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    AVProbeData pd = { filename, NULL, 0 };
    int score = AVPROBE_SCORE_RETRY;
    // 指定了pb参数,使用自定义 IO
    if (s->pb) {
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
        // 没有指定解复用器,则自行探测
        if (!s->iformat)
            return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                         s, 0, s->format_probesize);
        // 如果指定了pb后,解复用器的flag还指定了AVFMT_NOFILE,则冲突,直接返回失败
        else if (s->iformat->flags & AVFMT_NOFILE)
            av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense and "
                                      "will be ignored with AVFMT_NOFILE format.\n");
        return 0;
    }
    // 1、如果指定了解复用器,且该解复用器的flag包含AVFMT_NOFILE,则直接返回AVPROBE_SCORE_RETRY分数
    // 2、如果没有指定解复用器,则自行探测
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
    // 
    if ((ret = s->io_open(s, &s->pb, filename, AVIO_FLAG_READ | s->avio_flags, options)) < 0)
        return ret;

    if (s->iformat)
        return 0;
    return av_probe_input_buffer2(s->pb, &s->iformat, filename,
                                 s, 0, s->format_probesize);
}

这个函数分三种情况,分别为:

1、指定了pb参数,使用自定义 IO

2、

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Chiang木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值