当前位置: 首页 > news >正文

FFmpeg源码分析:avformat_open_input()打开媒体流

avformat_open_input()用于打开输入媒体流与读取头部信息,包括本地文件、网络流、自定义缓冲区。关键流程:打开avio、探测输入流的封装格式。对应的释放方法为avformat_close_input()。

1、打开输入媒体流

avformat_open_input方法位于libavformat/utils.c,流程包括分配AVFormatContext、设置options、初始化输入流、拷贝白名单与黑名单协议、读取ID3V2参数。具体方法如下:

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;
    // 分配AVFormatContext
    if (!s && !(s = avformat_alloc_context()))
        return AVERROR(ENOMEM);
    if (!s->av_class) {
        av_log(NULL, AV_LOG_ERROR, "Input context has not been properly allocated\n");
        return AVERROR(EINVAL);
    }
    if (fmt)
        s->iformat = fmt;
    // 拷贝options
    if (options)
        av_dict_copy(&tmp, *options, 0);
    // 如果已经设置AVIOContext,那么flag设为自定义IO
    if (s->pb) // must be before any goto fail
        s->flags |= AVFMT_FLAG_CUSTOM_IO;
    // 设置options
    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);
    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;
        }
    }
 
    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;
}

2、初始化输入媒体流

核心方法是init_input(),用于打开输入媒体流、探测封装格式,代码如下:

static int init_input(AVFormatContext *s, const char *filename,
                      AVDictionary **options)
{
    int ret;
    AVProbeData pd = { filename, NULL, 0 };
    int score = AVPROBE_SCORE_RETRY;
    // 探测输入缓冲区
    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);
        else if (s->iformat->flags & AVFMT_NOFILE)
            av_log(s, AV_LOG_WARNING, "Custom AVIOContext makes no sense.\n");
        return 0;
    }
    // 探测输入格式
    if ((s->iformat && s->iformat->flags & AVFMT_NOFILE) ||
        (!s->iformat && (s->iformat = av_probe_input_format2(&pd, 0, &score))))
        return score;
    // 打开avio
    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);
}

3、探测输入格式

av_probe_input_format2()位于libavformat/format.c,内部调用av_probe_input_format3(),而av_probe_input_format()会内部调用av_probe_input_format2()。具体调用关系如下:

ff_const59 AVInputFormat *av_probe_input_format2(ff_const59 AVProbeData *pd, int is_opened, int *score_max)
{
    int score_ret;
    ff_const59 AVInputFormat *fmt = av_probe_input_format3(pd, is_opened, &score_ret);
    if (score_ret > *score_max) {
        *score_max = score_ret;
        return fmt;
    } else
        return NULL;
}
 
ff_const59 AVInputFormat *av_probe_input_format(ff_const59 AVProbeData *pd, int is_opened)
{
    int score = 0;
    return av_probe_input_format2(pd, is_opened, &score);
}

3.1 探测过程

我们主要看下av_probe_input_format3方法,探测过程分为三步:read_probe、av_match_ext、av_match_name。每一步匹配结果有个score得分,取最高分数的作为format。具体探测匹配过程如下:

ff_const59 AVInputFormat *av_probe_input_format3(ff_const59 AVProbeData *pd, int is_opened,
                                      int *score_ret)
{
    AVProbeData lpd = *pd;
    const AVInputFormat *fmt1 = NULL;
    ff_const59 AVInputFormat *fmt = NULL;
    int score, score_max = 0;
    void *i = 0;
    const static uint8_t zerobuffer[AVPROBE_PADDING_SIZE];
    enum nodat {
        NO_ID3,
        ID3_ALMOST_GREATER_PROBE,
        ID3_GREATER_PROBE,
        ID3_GREATER_MAX_PROBE,
    } nodat = NO_ID3;
 
    if (!lpd.buf)
        lpd.buf = (unsigned char *) zerobuffer;
    // 判断是否匹配到ID3V2,存在于mp3、aac、wav、tta
    if (lpd.buf_size > 10 && ff_id3v2_match(lpd.buf, ID3v2_DEFAULT_MAGIC)) {
        int id3len = ff_id3v2_tag_len(lpd.buf);
        if (lpd.buf_size > id3len + 16) {
            if (lpd.buf_size < 2LL*id3len + 16)
                nodat = ID3_ALMOST_GREATER_PROBE;
            lpd.buf      += id3len;
            lpd.buf_size -= id3len;
        } else if (id3len >= PROBE_BUF_MAX) {
            nodat = ID3_GREATER_MAX_PROBE;
        } else
            nodat = ID3_GREATER_PROBE;
    }
 
    while ((fmt1 = av_demuxer_iterate(&i))) {
        if (!is_opened == !(fmt1->flags & AVFMT_NOFILE) && strcmp(fmt1->name, "image2"))
            continue;
        score = 0;
        if (fmt1->read_probe) {
			// 匹配格式的特定标识符,比如flv格式头部为"FLV"
            score = fmt1->read_probe(&lpd);
            if (score)
                av_log(NULL, AV_LOG_TRACE, "Probing %s score:%d size:%d\n", fmt1->name, score, lpd.buf_size);
            // 匹配外部格式
			if (fmt1->extensions && av_match_ext(lpd.filename, fmt1->extensions)) {
                switch (nodat) {
                case NO_ID3:
                    score = FFMAX(score, 1);
                    break;
                case ID3_GREATER_PROBE:
                case ID3_ALMOST_GREATER_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION / 2 - 1);
                    break;
                case ID3_GREATER_MAX_PROBE:
                    score = FFMAX(score, AVPROBE_SCORE_EXTENSION);
                    break;
                }
            }
        } else if (fmt1->extensions) {
            if (av_match_ext(lpd.filename, fmt1->extensions))
                score = AVPROBE_SCORE_EXTENSION;
        }
		// 匹配mimetype
        if (av_match_name(lpd.mime_type, fmt1->mime_type)) {
            if (AVPROBE_SCORE_MIME > score) {
                av_log(NULL, AV_LOG_DEBUG, "Probing %s score:%d increased to %d due to MIME type\n", fmt1->name, score, AVPROBE_SCORE_MIME);
                score = AVPROBE_SCORE_MIME;
            }
        }
        if (score > score_max) { // 取最高得分作为format
            score_max = score;
            fmt       = (AVInputFormat*)fmt1;
        } else if (score == score_max)
            fmt = NULL;
    }
    if (nodat == ID3_GREATER_PROBE)
        score_max = FFMIN(AVPROBE_SCORE_EXTENSION / 2 - 1, score_max);
    *score_ret = score_max;
 
    return fmt;
}

3.2 匹配分数

每步匹配有不同的分数,最高分数为100,retry为25,extension为50,mimetype为75.具体的AVProbeData结构体与得分规则如下:

typedef struct AVProbeData {
    const char *filename;
    unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
    int buf_size;       /**< Size of buf except extra allocated bytes */
    const char *mime_type; /**< mime_type, when known. */
} AVProbeData;
 
#define AVPROBE_SCORE_RETRY (AVPROBE_SCORE_MAX/4)
#define AVPROBE_SCORE_STREAM_RETRY (AVPROBE_SCORE_MAX/4-1)
 
#define AVPROBE_SCORE_EXTENSION  50 ///< score for file extension
#define AVPROBE_SCORE_MIME       75 ///< score for file mime type
#define AVPROBE_SCORE_MAX       100 ///< maximum score

3.3 探测mp4格式

mp4/mov/3gp格式都在libavformat/mov.c中,read_probe指向mov_probe方法。首先探测ftyp、moov、mdat等关键box的tag,然后是hdlr、mhlr、MPEG的tag,具体的探测方法如下:

static int mov_probe(const AVProbeData *p)
{
    int64_t offset = 0;
    uint32_t tag;
    int score = 0;
    int moov_offset = -1;
 
    for (;;) {
        tag = AV_RL32(p->buf + offset + 4);
        switch(tag) {
        case MKTAG('m','o','o','v'):
            moov_offset = offset + 4;
        case MKTAG('m','d','a','t'):
        case MKTAG('p','n','o','t'):
        case MKTAG('u','d','t','a'):
        case MKTAG('f','t','y','p'):
            if (tag == MKTAG('f','t','y','p') &&
                       (   AV_RL32(p->buf + offset + 8) == MKTAG('j','p','2',' ')
                        || AV_RL32(p->buf + offset + 8) == MKTAG('j','p','x',' ')
                    )) {
                score = FFMAX(score, 5);
            } else {
                score = AVPROBE_SCORE_MAX;
            }
            break;
        case MKTAG('e','d','i','w'): /* xdcam files have reverted first tags */
        case MKTAG('w','i','d','e'):
        case MKTAG('f','r','e','e'):
        case MKTAG('j','u','n','k'):
        case MKTAG('p','i','c','t'):
            score  = FFMAX(score, AVPROBE_SCORE_MAX - 5);
            break;
        case MKTAG(0x82,0x82,0x7f,0x7d):
        case MKTAG('s','k','i','p'):
        case MKTAG('u','u','i','d'):
        case MKTAG('p','r','f','l'):
            score  = FFMAX(score, AVPROBE_SCORE_EXTENSION);
            break;
        }
        if (size > INT64_MAX - offset)
            break;
        offset += size;
    }
    if (score > AVPROBE_SCORE_MAX - 50 && moov_offset != -1) {
        offset = moov_offset;
        while (offset < (p->buf_size - 16)) {
            if (AV_RL32(p->buf + offset     ) == MKTAG('h','d','l','r') &&
                AV_RL32(p->buf + offset +  8) == MKTAG('m','h','l','r') &&
                AV_RL32(p->buf + offset + 12) == MKTAG('M','P','E','G')) {
                return 5;
            } else {
                /* Keep looking */
                offset += 2;
            }
        }
    }
    return score;
}

4、打开avio
avio_open2方法会打开ffurl_open_whitelist()和fifo_fdopen()。具体调用链如下图所示:
在这里插入图片描述

4.1 open_whiltelist

其中位于avio.c的ffurl_open_whitelist()进行两步操作:ffurl_alloc和ffurl_connect,具体代码如下:

int ffurl_open_whitelist(URLContext **puc, const char *filename, int flags,
                         const AVIOInterruptCB *int_cb, AVDictionary **options,
                         const char *whitelist, const char* blacklist,
                         URLContext *parent)
{
    int ret = ffurl_alloc(puc, filename, flags, int_cb);
    if (ret < 0)
        return ret;
    ......
    ret = ffurl_connect(*puc, options);
    if (!ret)
        return 0;
fail:
    ffurl_closep(puc);
    return ret;
}

ffurl_alloc主要实现2个功能:查找协议和分配协议,查找过程是遍历协议数组根据scheme进行匹配。代码如下:

int ffurl_alloc(URLContext **puc, const char *filename, int flags,
                const AVIOInterruptCB *int_cb)
{
    const URLProtocol *p = NULL;
    p = url_find_protocol(filename);
    if (p)
       return url_alloc_for_protocol(puc, p, filename, flags, int_cb);
    *puc = NULL;
    return AVERROR_PROTOCOL_NOT_FOUND;
}

ffurl_connect主要是判断用url_oepn2还是url_open来打开:

int ffurl_connect(URLContext *uc, AVDictionary **options)
{
    ......
    int err =
        uc->prot->url_open2 ? uc->prot->url_open2(uc, uc->filename, uc->flags, options) :
        uc->prot->url_open(uc, uc->filename, uc->flags);
 
    ......
    return 0;
}

4.2 打开fifo

调用aviobuf.c的fifo_fdopen方法打开fifo,主要是分配buffer缓冲区、分配AVIOContext:

int ffio_fdopen(AVIOContext **s, URLContext *h)
{
    // malloc buffer
    buffer = av_malloc(buffer_size);
    if (!buffer)
        return AVERROR(ENOMEM);
    // malloc context
    *s = avio_alloc_context(buffer, buffer_size, h->flags & AVIO_FLAG_WRITE, h,
                            (int (*)(void *, uint8_t *, int))  ffurl_read,
                            (int (*)(void *, uint8_t *, int))  ffurl_write,
                            (int64_t (*)(void *, int64_t, int))ffurl_seek);
 
fail:
    av_freep(&buffer);
    return AVERROR(ENOMEM);
}

相关文章:

  • 深入理解关键字 一(auto,register,static,sizeof)
  • 基于Springboot+vue的停车场管理系统(Java毕业设计)
  • 详解CAN总线:CAN总线报文格式—数据帧
  • mysql进阶:canal实现mysql数据同步到redis|实现自定义canal客户端
  • React路由三种渲染方式、withRouter高阶组件、自定义导航组件
  • FaceNet-pytorch(fixing data imbalance-CASIA)
  • 【内核的设计与实现笔记】| 【01】初步了解内核
  • 【HDLBits 刷题】所有答案直达链接汇总
  • portswigger 目录遍历文件上传
  • 【微信小程序入门到精通】— swiper 超详细的属性值讲解!确定不来看看?
  • [单片机框架][device层] fuelgauge 电量计
  • 基于linux的web服务器(问题)
  • 【leetcode top100】两数相加。无重复字符的最长子串,盛水最多的容器,三数之和
  • PIE-Engine 教程:水稻面积提取1(宿迁市)
  • CMSC5707-高级人工智能之语音识别
  • [NodeJS] 关于Buffer
  • css系列之关于字体的事
  • ECMAScript入门(七)--Module语法
  • Hibernate最全面试题
  • JS字符串转数字方法总结
  • Linux各目录及每个目录的详细介绍
  • ng6--错误信息小结(持续更新)
  • Redis 中的布隆过滤器
  • SpiderData 2019年2月23日 DApp数据排行榜
  • ubuntu 下nginx安装 并支持https协议
  • vue2.0一起在懵逼的海洋里越陷越深(四)
  • webgl (原生)基础入门指南【一】
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 彻底搞懂浏览器Event-loop
  • 赢得Docker挑战最佳实践
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • FaaS 的简单实践
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • ​学习一下,什么是预包装食品?​
  • # 数论-逆元
  • # 学号 2017-2018-20172309 《程序设计与数据结构》实验三报告
  • (1)Android开发优化---------UI优化
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (附源码)apringboot计算机专业大学生就业指南 毕业设计061355
  • (附源码)springboot教学评价 毕业设计 641310
  • (六)Hibernate的二级缓存
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .net core 6 使用注解自动注入实例,无需构造注入 autowrite4net
  • .NET Core 项目指定SDK版本
  • .NET DataGridView数据绑定说明
  • .Net 垃圾回收机制原理(二)
  • .NET 同步与异步 之 原子操作和自旋锁(Interlocked、SpinLock)(九)
  • .NET/C# 编译期间能确定的相同字符串,在运行期间是相同的实例
  • .NET中两种OCR方式对比
  • .ui文件相关
  • /usr/bin/perl:bad interpreter:No such file or directory 的解决办法
  • @param注解什么意思_9000字,通俗易懂的讲解下Java注解