【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】

相关推荐
deli00713 分钟前
32K 上下文塞满后,AI 最先忘掉什么?120 轮对话实测
前端
deli0071 小时前
手机拍灯为什么有一条条黑纹?PWM 240Hz 实测
前端
wangruofeng6 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
wangruofeng6 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈9 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技9 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户479492835691510 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
风骏时光牛马12 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng12 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
清晨Coding13 小时前
为什么用 React Developer Tools 查看组件树这么乱?
前端·react.js