【FFMpeg学习系列一】avformat_open_input函数源码分析

本文主要参考文章: blog.csdn.net/u011913612/... 但是该文章是2016年的,分析的代码已经比较老,笔者用的是"ffmpeg version 6.0 Copyright (c) 2000-2023 the FFmpeg developers"

结合笔者自己的debug过程,主要分析avformat_open_input函数的具体实现。 先上一个大致的函数调用流程图。

其源代码如下:

c 复制代码
int avformat_open_input(AVFormatContext **ps, const char *filename,
                        const AVInputFormat *fmt, AVDictionary **options)
{
    AVFormatContext *s = *ps;
    FFFormatContext *si;
    AVDictionary *tmp = NULL;
    ID3v2ExtraMeta *id3v2_extra_meta = NULL;
    int ret = 0;

    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);
    si = ffformatcontext(s);
    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;

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

    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;

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

    if (!(s->url = av_strdup(filename ? filename : ""))) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    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 an AVIOContext */
    if (s->pb)
        ff_id3v2_read_dict(s->pb, &si->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);

    if (s->iformat->read_header)
        if ((ret = s->iformat->read_header(s)) < 0) {
            if (s->iformat->flags_internal & FF_FMT_INIT_CLEANUP)
                goto close;
            goto fail;
        }

    if (!s->metadata) {
        s->metadata    = si->id3v2_meta;
        si->id3v2_meta = NULL;
    } else if (si->id3v2_meta) {
        av_log(s, AV_LOG_WARNING, "Discarding ID3 tags because more suitable tags were found.\n");
        av_dict_free(&si->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 (s->pb && !si->data_offset)
        si->data_offset = avio_tell(s->pb);

    si->raw_packet_buffer_size = 0;

    update_stream_avctx(s);

    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"函数在ffplay中的整体位置。

这个函数的作用是:打开一个输入流并且读它的头部信息,编解码器不会被打开,这个方法调用结束后,一定要调用avformat_close_input()关闭流。

使用要这个函数要注意一点:如果fmt参数非空,也就是人为的指定了一个AVInputFormat的实例,那么这个函数就不会再检测输入文件的格式了,相反,如果fmt为空,那么这个函数就会自动探测输入文件的格式等信息。

从源码来看,这个函数主要做了下面几件事:

1.分配一个AVFormatContext的实例。 这是通过第10行"if (!s && !(s = avformat_alloc_context()))" 完成,。但是如果入参中s已经实例化,那么这里是不会执行的。从流程图可以看到,在read_thread的第三步中,它已经完成了"avformat_alloc_context"的调用。(笔者是ffplay一个本地的mp4文件,从debug看,入参已经被实例化。)

2.调用init_input函数初始化输入流的信息。这里会初始化AVInputFormat。

3.根据上一步初始化好的AVInputFormat的类型,调用它的read_header方法,读取文件头。

它最终的目的,是为了完成下面一些数据结构的初始化:

【1】avformat_alloc_context的实现

作用 复制代码
AVFormatContext *avformat_alloc_context(void)
{
    FFFormatContext *const si = av_mallocz(sizeof(*si));
    AVFormatContext *s;

    if (!si)
        return NULL;

    s = &si->pub;
    s->av_class = &av_format_context_class;
    s->io_open  = io_open_default;
#if FF_API_AVFORMAT_IO_CLOSE
FF_DISABLE_DEPRECATION_WARNINGS
    s->io_close = ff_format_io_close_default;
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    s->io_close2= io_close2_default;

    av_opt_set_defaults(s);

    si->pkt = av_packet_alloc();
    si->parse_pkt = av_packet_alloc();
    if (!si->pkt || !si->parse_pkt) {
        avformat_free_context(s);
        return NULL;
    }

#if FF_API_LAVF_SHORTEST
    si->shortest_end = AV_NOPTS_VALUE;
#endif

    return s;
}

它主要完成av_class的初始化,以及io_open 和io_close赋值,它们分别会用在第二步的init_input中,结合流程图可以看到io_open调用的地方。

总的来说,"avformat_alloc_context"的主要作用就是,创建AVFormatContext结构体并初始化它的AVClass,AVOption成员。再给它的io_open赋值为io_open_default 函数,io_close 赋值为io_close_default函数。构建pkt,parse_pkt

【2】init_input函数 它有两个功能:打开输入文件;探测输入文件的格式;

io_open -> io_open_default -> ffio_open_whitelist -> ffurl_open_whitelist -> ffurl_alloc(url_alloc_for_protocol) -> ffurl_connect -> file_open -> avpriv_open-> open

到了这里,调用open打开文件后返回文件描述符,并把得到的文件描述符赋值给FileContext结构体的fd成员。 返回到ffio_open_whitelist之后,继续调用ffio_fdopen去做如下事情:

  • 分配一快buffer内存,其大小有这里定义:#define IO_BUFFER_SIZE 32768,buffer的大小本来取决于URLContext的max_packet_size,前面在url_alloc_for_protocol函数中初始化URLContext的时候将其初始化为0了,因此,这里分配的大小为32768。
  • 分配一个AVIOContext结构体,这个结构体也很重要,分配完以后我们就有AVIOContext结构体的实例了,分配完成后做了一些初始化工作。

这要注意seekable 是AVIO_SEEKABLE_NORMAL,因为is_streamed 为0,这在url_alloc_for_protocol函数中初始化URLContext的时候将其初始化。

打开输入文件之后,我们就需要探测文件的格式,它是通过函数av_probe_input_buffer2来实现的。它会不断的读文件,然后分析,直到确定了文件的格式位置;这个函数的for循环每次读PROBE_BUF_MIN个字节的内容,其实就是2048了,然后调用av_probe_input_format2函数来猜测文件的格式。 av_probe_input_buffer2 -> av_probe_input_format2 -> av_probe_input_format3.

"av_probe_input_format3"会根据文件名的后缀,遍历AVInputFormat的全局链表,比如我们输入的xxx.mp4,那么匹配到ff_mov_demuxer

c 复制代码
const AVInputFormat ff_mov_demuxer = {
    .name           = "mov,mp4,m4a,3gp,3g2,mj2",
    .long_name      = NULL_IF_CONFIG_SMALL("QuickTime / MOV"),
    .priv_class     = &mov_class,
    .priv_data_size = sizeof(MOVContext),
    .extensions     = "mov,mp4,m4a,3gp,3g2,mj2,psp,m4b,ism,ismv,isma,f4v,avif",
    .flags_internal = FF_FMT_INIT_CLEANUP,
    .read_probe     = mov_probe,
    .read_header    = mov_read_header,
    .read_packet    = mov_read_packet,
    .read_close     = mov_read_close,
    .read_seek      = mov_read_seek,
    .flags          = AVFMT_NO_BYTE_SEEK | AVFMT_SEEK_TO_PTS | AVFMT_SHOW_IDS,
};

【Q1】AVInputFormat的全局链表是什么?作用? 【A1】xxxxx

找到对应的AVInputFormat的实例后,我们的AVFormatContext中的iformat成员就有内容了。这样AVFormatContext中的初始化就完成了:

c 复制代码
const AVClass *av_class;
struct AVInputFormat *iformat;
void *priv_data;
AVIOContext *pb;

【3】read_header解析文件 ff_mov_demuxer中定义的read_header是mov_read_header 【TBD】

相关推荐
恋猫de小郭1 小时前
Flutter 3.35 发布,快来看看有什么更新吧
android·前端·flutter
chinahcp20082 小时前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip3 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip3 小时前
运行时模块批量导入
前端·javascript
hyy27952276844 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅4 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus4 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。4 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子4 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言5 小时前
待办事项小程序开发
前端·javascript