FFmpeg源码分析:avformat_open_input

一、函数功能介绍

  • 当前支持的媒体解封装协议在libavformat/demuxer_list.c文件的demuxer_list变量中定义
  • 当前支持的音视频采集格式在libavdevice/indev_list.c文件的indev_list变量中定义

用户可以自行查看av_find_input_format函数的具体逻辑,得知最新版本的ffmpeg支持情况。
注意:若进行音视频采集之前,则必须执行avdevice_register_all函数。

1.解析本地媒体文件

static void open_input_media_file(const char *filename)
{
    AVFormatContext *format_context = NULL;
    int ret = 0;

    ret = avformat_open_input(&format_context, filename, NULL, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input failed, error:%s", av_err2str(ret));
        return;
    }

    av_dump_format(format_context, 0, filename, 0);
    avformat_close_input(&format_context);
}

2.解析网络媒体协议

static void open_input_media_from_net(const char *url)
{
    AVFormatContext *format_context = NULL;
    int ret = 0;

    ret = avformat_open_input(&format_context, url, NULL, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input failed, error:%s", av_err2str(ret));
        return;
    }

    av_dump_format(format_context, 0, url, 0);
    avformat_close_input(&format_context);

}

从上述代码中可以看出,ffmpeg解析本地文件与解析网络媒体协议没有区别,这就是ffmpeg强大的地方。
ffmpeg将本地文件抽象成了一种特殊的网络协议,通过分析媒体格式,查找到对应的解封装器,并通过回调,将解封装器细节进行隔离,对用户屏蔽所有差别。

3.从音视频设备中采集数据

static void capture_media_data_from_input_device()
{
    AVFormatContext *format_context = NULL;
    AVInputFormat *input_format = NULL;
    int ret = 0;

    input_format = av_find_input_format("avfoundation");
    if (!input_format) {
        av_log(NULL, AV_LOG_ERROR, "av_find_input_format failed\n");
        return;
    }

    ret = avformat_open_input(&format_context, ":0", input_format, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input failed, error:%s", av_err2str(ret));
        return;
    }

    avformat_close_input(&format_context);

}

二、函数参数说明

  • ps用户提供的指针,类型为AVFormatContext,可以事先通过avformat_alloc_context()进行分配。
  • url 提供一个可打开的流。可以是本地文件,rtmp协议、rtp协议指定的流地址
  • fmt 如果该值不为NULL,则会强制使用该值作为输入流格式,否则将会进行自动检测。
  • options 包含AVFormatContext和demuxer私有选项的字典。

三、核心逻辑

1.申请内存并初始化AVFormatContext

if (!s && !(s = avformat_alloc_context()))
    return AVERROR(ENOMEM);

2.校验av_class是否为空

通过avformat_alloc_context函数进行内存分配并初始化,av_class值为av_format_context_class。

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);
}

3.设置iformat

fmt参数可以由用户自己在调用avformat_open_input函数之前,通过调用av_find_input_format函数获取。
关于av_find_input_format函数的讲解,详见:https://blog.csdn.net/chaisy971124568/article/details/109781833

if (fmt)
    s->iformat = fmt;

4.拷贝options参数到tmp变量中

此处逻辑的用意是避免污染options参数本身。

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

5.设置flags

s->pb可以通过avio_open2函数进行设置,pb是IO上下文,ffmpeg自己实现了一套缓存,并对用户隔绝了文件和网络的区别。
AVFMT_FLAG_CUSTOM_IO指定pb是由用户自己设置的,这样在调用avformat_free_context函数时,就不用通过avio_close函数进行关闭了,需要用户自己手动关闭。

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

6.设置AVFormatContext结构体成员的参数

可以通过AVDictionary结果,设置AVFormatContext结构体成员的值。
tmp变量中的各个key-val对需要与avformat_options变量中的参数对应上,否则该函数调用失败,并将无法识别的参数通过tmp回传。
这就是为什么要在步骤4中进行copy的原因。

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

7.初始化输入(最重要的逻辑)

  • 通过av_probe_input_format函数根据输入文件名探测iformat
  • 通过av_probe_input_buffer函数根据文件内容进行探测iformat

打开输入媒体并填充其AVInputFormat结构。
执行完此函数后,s->pb和s->iformat都已经指向了有效实例。
pb是用于读写数据的,它把媒体数据当做流来读写,不管是什么媒体格式。
iformat把pb读出来的流按某种媒体格式进行分析,也就是说pb在底层,iformat在上层。

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

8.执行黑白名单相关的逻辑

protocol_whitelist,protocol_blacklist,format_whitelist这三个均是通过options参数进行设置的。

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;
}

9.跳过文件头

skip_initial_bytes通过options进行设置,表示跳过文件开头的若干字节。

avio_skip(s->pb, s->skip_initial_bytes);

10.检查输入文件是否需要包含数字

这个逻辑在通过ffmpeg将多个图片合成视频流的时候有用。

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

11.分配私有数据

  • 将AVFormatContext结构体成员priv_data的值指向AVFormatContext结构体成员iformat->priv_class
  • 设置s->priv_data的默认值
  • 根据options参数指定的key-val设置s->priv_data的值

例如,当fmt值为ff_flv_demuxer时,av_opt_set_dict函数设置参数的模板为libavformat/flvdec.c文件中的options变量。

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

12.读取id3v2的源信息

ID3,一般是位于一个mp3文件的开头或末尾的若干字节内,附加了关于该mp3的歌手,标题,专辑名称,年代,风格等信息,该信息就被称为ID3信息。
ID3信息分为两个版本,v1和v2版。
v1版的ID3在mp3文件的末尾128字节,以TAG三个字符开头,后面跟上歌曲信息。
v2版一般位于mp3的开头,可以存储歌词,该专辑的图片等大容量的信息。

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

13.读取媒体数据头

在read_header函数中主要是做某种格式的初始化工作。
比如填充自己的私有结构,根据流的数量分配流结构并初始化,把文件指针指向数据区开始处等。
当s->iformat为ff_flv_demuxer时,read_header函数就是flv_read_header。

if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header)
    if ((ret = s->iformat->read_header(s)) < 0)
        goto fail;

14.设置媒体数据中真实数据开始的位置

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

你可能感兴趣的:(WebRTC,音视频,FFmpeg,ffmpeg)