FFmpeg源代码简单分析-编码-avformat_alloc_output_context2()_MY CUP OF TEA的博客-程序员宝宝

技术标签: 音视频  

参考链接

avformat_alloc_output_context2()

  • 在基于FFmpeg的视音频编码器程序中,该函数通常是第一个调用的函数(除了组件注册函数av_register_all())
  • avformat_alloc_output_context2()函数可以初始化一个用于输出的AVFormatContext结构体
  • 它的声明位于libavformat\avformat.h,如下所示
/**
 * Allocate an AVFormatContext for an output format.
 * avformat_free_context() can be used to free the context and
 * everything allocated by the framework within it.
 *
 * @param *ctx is set to the created format context, or to NULL in
 * case of failure
 * @param oformat format to use for allocating the context, if NULL
 * format_name and filename are used instead
 * @param format_name the name of output format to use for allocating the
 * context, if NULL filename is used instead
 * @param filename the name of the filename to use for allocating the
 * context, may be NULL
 * @return >= 0 in case of success, a negative AVERROR code in case of
 * failure
 */
int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat,
                                   const char *format_name, const char *filename);
  • 参数介绍
    • ctx:函数调用成功之后创建的AVFormatContext结构体。
    • oformat:指定AVFormatContext中的AVOutputFormat,用于确定输出格式。如果指定为NULL,可以设定后两个参数(format_name或者filename)由FFmpeg猜测输出格式。
  • PS:使用该参数需要自己手动获取AVOutputFormat,相对于使用后两个参数来说要麻烦一些。
    • format_name:指定输出格式的名称。根据格式名称,FFmpeg会推测输出格式。输出格式可以是“flv”,“mkv”等等。
    • filename:指定输出文件的名称。根据文件名称,FFmpeg会推测输出格式。文件名称可以是“xx.flv”,“yy.mkv”等等。
  • 函数执行成功的话,其返回值大于等于0。 
  • avformat_alloc_output_context2()的函数定义。该函数的定义位于libavformat\mux.c中
int avformat_alloc_output_context2(AVFormatContext **avctx, const AVOutputFormat *oformat,
                                   const char *format, const char *filename)
{
    AVFormatContext *s = avformat_alloc_context();
    int ret = 0;

    *avctx = NULL;
    if (!s)
        goto nomem;

    if (!oformat) {
        if (format) {
            oformat = av_guess_format(format, NULL, NULL);
            if (!oformat) {
                av_log(s, AV_LOG_ERROR, "Requested output format '%s' is not a suitable output format\n", format);
                ret = AVERROR(EINVAL);
                goto error;
            }
        } else {
            oformat = av_guess_format(NULL, filename, NULL);
            if (!oformat) {
                ret = AVERROR(EINVAL);
                av_log(s, AV_LOG_ERROR, "Unable to find a suitable output format for '%s'\n",
                       filename);
                goto error;
            }
        }
    }

    s->oformat = oformat;
    if (s->oformat->priv_data_size > 0) {
        s->priv_data = av_mallocz(s->oformat->priv_data_size);
        if (!s->priv_data)
            goto nomem;
        if (s->oformat->priv_class) {
            *(const AVClass**)s->priv_data= s->oformat->priv_class;
            av_opt_set_defaults(s->priv_data);
        }
    } else
        s->priv_data = NULL;

    if (filename) {
        if (!(s->url = av_strdup(filename)))
            goto nomem;

    }
    *avctx = s;
    return 0;
nomem:
    av_log(s, AV_LOG_ERROR, "Out of memory\n");
    ret = AVERROR(ENOMEM);
error:
    avformat_free_context(s);
    return ret;
}
  • 从代码中可以看出,avformat_alloc_output_context2()的流程如要包含以下2步:
  • 1)  调用avformat_alloc_context()初始化一个默认的AVFormatContext。
  • 2) 如果指定了输入的AVOutputFormat,则直接将输入的AVOutputFormat赋值给AVOutputFormat的oformat。如果没有指定输入的AVOutputFormat,就需要根据文件格式名称或者文件名推测输出的AVOutputFormat。无论是通过文件格式名称还是文件名推测输出格式,都会调用一个函数av_guess_format()。 

函数调用结构图

  • 函数调用结构图如下所示

 

avformat_alloc_context()

  • avformat_alloc_context()的是一个FFmpeg的API,它的定义如下。
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;
    s->io_close = ff_format_io_close_default;
    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;
    }

    si->shortest_end = AV_NOPTS_VALUE;

    return s;
}
  • 从代码中可以看出,avformat_alloc_context()首先调用av_malloc()为AVFormatContext分配一块内存。
  • 然后调用了一个函数av_opt_set_defaults()用于给AVFormatContext设置默认值。
  • av_opt_set_defaults()的定义如下。 
void av_opt_set_defaults(void *s)
{
    av_opt_set_defaults2(s, 0, 0);
}

void av_opt_set_defaults2(void *s, int mask, int flags)
{
    const AVOption *opt = NULL;
    while ((opt = av_opt_next(s, opt))) {
        void *dst = ((uint8_t*)s) + opt->offset;

        if ((opt->flags & mask) != flags)
            continue;

        if (opt->flags & AV_OPT_FLAG_READONLY)
            continue;

        switch (opt->type) {
            case AV_OPT_TYPE_CONST:
                /* Nothing to be done here */
                break;
            case AV_OPT_TYPE_BOOL:
            case AV_OPT_TYPE_FLAGS:
            case AV_OPT_TYPE_INT:
            case AV_OPT_TYPE_INT64:
            case AV_OPT_TYPE_UINT64:
            case AV_OPT_TYPE_DURATION:
#if FF_API_OLD_CHANNEL_LAYOUT
FF_DISABLE_DEPRECATION_WARNINGS
            case AV_OPT_TYPE_CHANNEL_LAYOUT:
FF_ENABLE_DEPRECATION_WARNINGS
#endif
            case AV_OPT_TYPE_PIXEL_FMT:
            case AV_OPT_TYPE_SAMPLE_FMT:
                write_number(s, opt, dst, 1, 1, opt->default_val.i64);
                break;
            case AV_OPT_TYPE_DOUBLE:
            case AV_OPT_TYPE_FLOAT: {
                double val;
                val = opt->default_val.dbl;
                write_number(s, opt, dst, val, 1, 1);
            }
            break;
            case AV_OPT_TYPE_RATIONAL: {
                AVRational val;
                val = av_d2q(opt->default_val.dbl, INT_MAX);
                write_number(s, opt, dst, 1, val.den, val.num);
            }
            break;
            case AV_OPT_TYPE_COLOR:
                set_string_color(s, opt, opt->default_val.str, dst);
                break;
            case AV_OPT_TYPE_STRING:
                set_string(s, opt, opt->default_val.str, dst);
                break;
            case AV_OPT_TYPE_IMAGE_SIZE:
                set_string_image_size(s, opt, opt->default_val.str, dst);
                break;
            case AV_OPT_TYPE_VIDEO_RATE:
                set_string_video_rate(s, opt, opt->default_val.str, dst);
                break;
            case AV_OPT_TYPE_BINARY:
                set_string_binary(s, opt, opt->default_val.str, dst);
                break;
            case AV_OPT_TYPE_CHLAYOUT:
                set_string_channel_layout(s, opt, opt->default_val.str, dst);
                break;
            case AV_OPT_TYPE_DICT:
                set_string_dict(s, opt, opt->default_val.str, dst);
                break;
        default:
            av_log(s, AV_LOG_DEBUG, "AVOption type %d of option %s not implemented yet\n",
                   opt->type, opt->name);
        }
    }
}
  • 从代码中可以看出,avformat_alloc_context()首先调用memset()将AVFormatContext的内存置零
  • 然后指定它的AVClass(指定了AVClass之后,该结构体就支持和AVOption相关的功能)
  • 最后调用av_opt_set_defaults()给AVFormatContext的成员变量设置默认值(av_opt_set_defaults()就是和AVOption有关的一个函数,专门用于给指定的结构体设定默认值,此处暂不分析)。 

av_guess_format()

  • av_guess_format()是FFmpeg的一个API
  • 它的声明如下
/**
 * Return the output format in the list of registered output formats
 * which best matches the provided parameters, or return NULL if
 * there is no match.
 *
 * @param short_name if non-NULL checks if short_name matches with the
 * names of the registered formats
 * @param filename if non-NULL checks if filename terminates with the
 * extensions of the registered formats
 * @param mime_type if non-NULL checks if mime_type matches with the
 * MIME type of the registered formats
 */
const AVOutputFormat *av_guess_format(const char *short_name,
                                      const char *filename,
                                      const char *mime_type);
  • 拿中文简单解释一下参数。
    • short_name:格式的名称。
    • filename:文件的名称。
    • mime_type:MIME类型。
  • 返回最匹配的AVOutputFormat。如果没有很匹配的AVOutputFormat,则返回NULL。
  • av_guess_format()的代码如下所示。
const AVOutputFormat *av_guess_format(const char *short_name, const char *filename,
                                      const char *mime_type)
{
    const AVOutputFormat *fmt = NULL;
    const AVOutputFormat *fmt_found = NULL;
    void *i = 0;
    int score_max, score;

    /* specific test for image sequences */
#if CONFIG_IMAGE2_MUXER
    if (!short_name && filename &&
        av_filename_number_test(filename) &&
        ff_guess_image2_codec(filename) != AV_CODEC_ID_NONE) {
        return av_guess_format("image2", NULL, NULL);
    }
#endif
    /* Find the proper file type. */
    score_max = 0;
    while ((fmt = av_muxer_iterate(&i))) {
        score = 0;
        if (fmt->name && short_name && av_match_name(short_name, fmt->name))
            score += 100;
        if (fmt->mime_type && mime_type && !strcmp(fmt->mime_type, mime_type))
            score += 10;
        if (filename && fmt->extensions &&
            av_match_ext(filename, fmt->extensions)) {
            score += 5;
        }
        if (score > score_max) {
            score_max = score;
            fmt_found = fmt;
        }
    }
    return fmt_found;
}
  • 从代码中可以看出,av_guess_format()中使用一个整型变量score记录每种输出格式的匹配程度。
  • 函数中包含了一个while()循环,该循环利用函数av_muxer_iterate()遍历FFmpeg中所有的AVOutputFormat,并逐一计算每个输出格式的score。
  • 具体的计算过程分成如下几步:
    • 1)  如果封装格式名称匹配,score增加100。匹配中使用了函数av_match_name()。
    • 2)  如果mime类型匹配,score增加10。匹配直接使用字符串比较函数strcmp()。
    • 3)  如果文件名称的后缀匹配,score增加5。匹配中使用了函数av_match_ext()。
  • while()循环结束后,得到得分最高的格式,就是最匹配的格式。
  • 下面看一下一个AVOutputFormat的实例,就可以理解“封装格式名称”,“mine类型”,“文件名称后缀”这些概念了。
  • 下面是flv格式的视音频复用器(Muxer)对应的AVOutputFormat格式的变量ff_flv_muxer。
const AVOutputFormat ff_flv_muxer = {
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .mime_type      = "video/x-flv",
    .extensions     = "flv",
    .priv_data_size = sizeof(FLVContext),
    .audio_codec    = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    .video_codec    = AV_CODEC_ID_FLV1,
    .init           = flv_init,
    .write_header   = flv_write_header,
    .write_packet   = flv_write_packet,
    .write_trailer  = flv_write_trailer,
    .check_bitstream= flv_check_bitstream,
    .codec_tag      = (const AVCodecTag* const []) {
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .flags          = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
    .priv_class     = &flv_muxer_class,
};

av_muxer_iterate

  • 参数不为NULL的时候用于获得下一个AVOutputFormat
const AVOutputFormat *av_muxer_iterate(void **opaque)
{
    static const uintptr_t size = sizeof(muxer_list)/sizeof(muxer_list[0]) - 1;
    uintptr_t i = (uintptr_t)*opaque;
    const AVOutputFormat *f = NULL;
    uintptr_t tmp;

    if (i < size) {
        f = muxer_list[i];
    } else if (tmp = atomic_load_explicit(&outdev_list_intptr, memory_order_relaxed)) {
        const AVOutputFormat *const *outdev_list = (const AVOutputFormat *const *)tmp;
        f = outdev_list[i - size];
    }

    if (f)
        *opaque = (void*)(i + 1);
    return f;
}

av_match_name()

  • av_match_name()是一个API函数,定义如下所示。
  • av_match_name()用于比较两个格式的名称。简单地说就是比较字符串。
  • 注意该函数的字符串是不区分大小写的:字符都转换为小写进行比较。
int av_match_name(const char *name, const char *names)
{
    const char *p;
    int len, namelen;

    if (!name || !names)
        return 0;

    namelen = strlen(name);
    while (*names) {
        int negate = '-' == *names;
        p = strchr(names, ',');
        if (!p)
            p = names + strlen(names);
        names += negate;
        len = FFMAX(p - names, namelen);
        if (!av_strncasecmp(name, names, len) || !strncmp("ALL", names, FFMAX(3, p - names)))
            return !negate;
        names = p + (*p == ',');
    }
    return 0;
}
  • 上述函数还有一点需要注意,其中使用了一个while()循环,用于搜索“,”。
  • 这是因为FFmpeg中有些格式是对应多种格式名称的,例如MKV格式的解复用器(Demuxer)的定义如下。
const AVInputFormat ff_matroska_demuxer = {
    .name           = "matroska,webm",
    .long_name      = NULL_IF_CONFIG_SMALL("Matroska / WebM"),
    .extensions     = "mkv,mk3d,mka,mks,webm",
    .priv_data_size = sizeof(MatroskaDemuxContext),
    .flags_internal = FF_FMT_INIT_CLEANUP,
    .read_probe     = matroska_probe,
    .read_header    = matroska_read_header,
    .read_packet    = matroska_read_packet,
    .read_close     = matroska_read_close,
    .read_seek      = matroska_read_seek,
    .mime_type      = "audio/webm,audio/x-matroska,video/webm,video/x-matroska"
};
  • 从代码可以看出,ff_matroska_demuxer中的name字段对应“matroska,webm”。
  • av_match_name()函数对于这样的字符串,会把它按照“,”截断成一个个封装格式名称,然后一一进行比较。

av_match_ext()

  • av_match_ext()是一个API函数,声明如下所示。
/**
 * Return a positive value if the given filename has one of the given
 * extensions, 0 otherwise.
 *
 * @param filename   file name to check against the given extensions
 * @param extensions a comma-separated list of filename extensions
 */
int av_match_ext(const char *filename, const char *extensions);
  • av_match_ext()用于比较文件的后缀。
  • 该函数首先通过反向查找的方式找到输入文件名中的“.”,就可以通过获取“.”后面的字符串来得到该文件的后缀。
  • 然后调用av_match_name(),采用和比较格式名称的方法比较两个后缀。 
/**
 * @file
 * Format register and lookup
 */

int av_match_ext(const char *filename, const char *extensions)
{
    const char *ext;

    if (!filename)
        return 0;

    ext = strrchr(filename, '.');
    if (ext)
        return av_match_name(ext + 1, extensions);
    return 0;
}
  • 经过以上几步之后,av_guess_format()最终可以得到最合适的AVOutputFormat并且返回给avformat_alloc_output_context2()。
  • avformat_alloc_output_context2()接下来将获得的AVOutputFormat赋值给刚刚新建的AVFormatContext,即可完成初始化工作
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CHYabc123456hh/article/details/125407759

智能推荐

乱的笔记_weixin_34357962的博客-程序员宝宝

链接:https://pan.baidu.com/s/1jBlX2OoWALMaLuMkx21H7w 提取码:mzl4 复制这段内容后打开百度网盘手机App,操作更方便哦如果看的不舒服可以上百度网盘下载完整的第二本书一、Linux基本命令1.基础命令vi /etc/sysconfig/network-scripts/ifcfg-ens32DNBBOT=NO改成DNBBOT=YES//...

Longstick的学习周记——第六周_Longstick20cm的博客-程序员宝宝_megacmd使用

目录前言MEGAcmd的调试日志一、MEGAcmd的安装二、MEGAcmd简要使用流程三、调试过程前言放假了。。在家里呆了几天休息休息。看上去是跟紧张的学习生活告一段落了,但是实则是根本没有,甚至还要更加努力,不得不说,精神上还是有些许疲惫...不过也是,人类谋求发展的过程总是艰辛曲折,我还不至于到很折磨的地步(至少自己还没把自己逼的太死。好好学习,过几天去旅游去。MEGAcmd的调试日志周五开了个会,明确了一下项目源码分析的对象,然后分配到的任务是尝试用调试工具跟进

linux 共享文件拒绝访问权限,Samba共享文件夹拒绝访问_好想不取名的博客-程序员宝宝

今天用Samba共享CentOS里面的文件夹给Windows使用,无奈在配置正确,关闭防火墙,且文件属性均为wrx的情况下,依然被拒绝访问。弄这个问题花了我不少时间,终于让我找到了答案:没有共享文件给Samba!!!在CentOS中,系统处于安全的考虑,即使拥有着或者权限合法的情况下,也会拒绝某些进程对文件的访问,这时就需要使用chcon这个命令了。于是在我指定了一个目录后,使用下面的语句即可:c...

vs2019 第一个MFC应用程序_imxlw00的博客-程序员宝宝

创建项目项目的创建和之前一样,只是此次的源文件后缀为.cpp,因为MFC是由C++编写的,编写MFC程序需要包含**#include &lt;afxwin.h&gt;**头文件。编写代码mfc.h#pragma once#include &lt;afxwin.h&gt;class MyApp :public CWinApp//CWinApp应用程序类{public: //程序入口 virtual BOOL InitInstance();};class MyFrame :publ

css设置渐变色_吾已六的博客-程序员宝宝

css设置渐变色 [color=red] .anamorphism{ width:100%; height:200px; FILTER:progid:DXImageTransform.Microsoft.Gradient(gradientType=0,startColorStr=#7ECCE2,endColorStr=#D1EEF5);/*IE6*/ backg...

spring事务_weixin_34202952的博客-程序员宝宝

为什么80%的码农都做不了架构师?&gt;&gt;&gt; ...

随便推点

图解面试题:经典topN问题_猴子数据分析的博客-程序员宝宝

工作中会经常遇到这样的业务问题:如果找到每个类别下用户点击最多的5个商品是什么?这类问题其实就是常见的:每组最大的N条记录(topN)。【题目】现有“成绩表”,记录了每个学生各科的成绩。表内容如下:问题:查找每个学生成绩最高的2个科目【解题思路】1.看到问题中要查“每个”学生最高的成绩。还记得我们之前课程里讲过的吗?当有“每个”出现的时候,就要想到是要分组了。这里是“每个学生”,结合表的结构,是按...

手把手教你用NuGet_何新生-功夫熊猫的博客-程序员宝宝

手把手教你用NuGet      进行项目已经时间不短了,实践的流程、学习的东西也不少,这都应该及时的做总结,最近实践了一个Nuget过程,假如有一个早已封装好的dll,要让56个人使用,再修改后重新生成dll后,要传给这56个人,可不可以通过一个工具,让他来管理我的dll包,我在修改包后,其他人只需更新,摆脱粘贴复制的苦恼。NuGet就帮我解决了这个问题。

常用的WEB自动化测试工具_m0_49928665的博客-程序员宝宝_web自动化测试工具

主流自动化测试工具–QTPQTP是一个侧重于功能的回归自动化测试工具;提供了很多插件。QTP支持的脚本语言是VBScript,这对于测试人员来说,感觉要“舒服”得多。VBScript毕竟是一种松散的、非严格的、普及面很广的语言。QTP的高可用性:1.支持录制与回放2.支持lower level模式3.QTP的编辑器支持两种视图: Keyword模式和Expert模式与Selenium(WebDriver)比较优劣价格:selenium是开源的自动化测试工具,但是QTP是商业版的,而且

java绘制连续折线_java-在JMapViewer中绘制折线_果呀哎呀妈呀哦呀的博客-程序员宝宝

您可以创建自己的折线实现.下面是一个基于现有MapPolygonImpl的示例.这很hacky,但JMapViewer中似乎没有添加行的方法.import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.Point;import java.awt.geom.Path2D;import java.util.ArrayList;...

32位RedHat Enterprise Linux 5 Update 4下安装Oracle10.2.0.1_cizhu1437的博客-程序员宝宝

安装要求:CPU,1GMHz以上 内存,1G以上 硬盘,安装系统后建议10G空闲空间1、检查和安装必要的软件包: binutils-2.17.5...

【ANSYS 学习笔记】Case05_Basic Transient Sources and Circuit_逐梦小伙的博客-程序员宝宝

• Transient Setup – This workshop discusses basic setup details of 2D Magnetic Transient solver –本研讨会讨论2D电磁瞬态求解器的基本设置细节 – The transient setup is described with two different excit...

推荐文章

热门文章

相关标签