从学龄前开始解读FFMPEG代码 之 AVFormatContext以及avformat_alloc_context()函数

开始avformatcontext以及对应alloc函数学习前想说的一些话

AVFormatContext是用于打开/写入文件或者网络视频流等视频码流文件时使用的结构体,结构体内部有非常多的参数,所以会挑几个常见的或者主要的讲。而alloc函数内部则是有更深的底层函数,更深度的函数解读则需要另外花一些时间再开一篇文章…(大概率不会坑?)

从AVFormatContext结构体开始

在我们编写自己的编解码应用代码,使用ffmpeg打开一个文件或者网络流的时候,首先使用到的结构体就是AVFormatContext,其源代码在libavformat/avformat.h文件中。

该结构体的主要功能就是为了做好文件的输出输出,FFmpeg也非常直截了当,也确实没有跟我这种辣鸡小白比比赖赖很多啊,在代码中一开始的注释介绍里就指出AVFormatContext是一个“Format I/O context",源代码结构体声明前的注释说明:

/**
 * Format I/O context.
 * New fields can be added to the end with minor version bumps.
 * Removal, reordering and changes to existing fields require a major
 * version bump.
 * sizeof(AVFormatContext) must not be used outside libav*, use
 * avformat_alloc_context() to create an AVFormatContext.
 *
 * Fields can be accessed through AVOptions (av_opt*),
 * the name string used matches the associated command line parameter name and
 * can be found in libavformat/options_table.h.
 * The AVOption/command line parameter names differ in some cases from the C
 * structure field names for historic reasons or brevity.
 */

看一下,注释中告诉我们了,
1.首先,小版本更新会增加巴拉巴拉,大版本更新会增加巴拉巴拉,对我我这种菜鸡小白,不重要。
2.然后,sizeof(AVFormatContext)不可以用于libav*包含的的文件以外的地方,这一点在自己手动开发或者修改代码时要注意。
3.然后,请使用avformat_alloc_context()创建这个结构体对象。这个函数也是这篇文章需要讲解一下的,
4.我们可以用AVOption去访问结构体中一些字段。这个非常好,也是开发代码时要用到的。
5.由于一些历史问题,所以字段名称可能会与C结构体中存在一些不同。这个fine,问题不大。

该结构体具体的代码非常长,在这里就不贴出来了,可以自己git clone之后打开libavformat/avformat.h该文件,非常好找。在结构体代码里面有几个比较主要的成员,首先是第一个结构体:

const AVClass *av_class;

AVClass放第一个不是说它有多么重要(虽然确实挺重要的,非常重要),其实是FFmpeg规定了,在结构体中如果包含AVClass,出现的AVClass必须要放变量的第一个。其主要的功能是为了给AVFormatContext提供AVOption的支持,用AVClass来连接AVOption,AVClass中拥有一个.option数组,也就是说AVClass相当于一个桥梁的作用。在使用AVOption传参的时候,就可以使用该AVClass连接的AVOption来传各项设置参数到AVFormatContext或者其他使用了AVClass的结构体中。所以传参时,增加删除改动AVOption中的参数就可以实现了(具体怎么设置AVOption呢?学龄前课程不要着急,一步一步来学习)

/**
* The input container format.
* Demuxing only, set by avformat_open_input().
*/
ff_const59 struct AVInputFormat *iformat; /**
* The output container format.
* Muxing only, must be set by the caller before avformat_write_header().
*/
ff_const59 struct AVOutputFormat *oformat;

接下来是两个用于打开文件读入数据的format以及一个用于写文件输出数据的format,且在注释里说的很清楚,在打开文件读数据之前需要使用avformat_open_input(),在写文件输出数据之前必须要调用avformat_write_header()

/**
* I/O context.
* - demuxing: either set by the user before avformat_open_input() (then
* the user must close it manually) or set by avformat_open_input().
* - muxing: set by the user before avformat_write_header(). The caller must
* take care of closing / freeing the IO context.**/

AVIOContext *pb;

pb则是用于数据缓存的结构体,在解复用的时候,用户手动设置的话需要在avformat_open_input()调用之前设置好,复用的时候则是需要在avformat_write_header()前进行手动设置。

在pb中有四个主要的成员变量,指向缓存地址的buffer,标明buffer长度的buffer_size,指向当前已经读取/已经写入地址位置的buf_ptr,以及指向buffer数据结尾的buf_end。其具体定义在avio.h中

unsigned int nb_streams;

/**
* A list of all streams in the file. New streams are created with
* avformat_new_stream().

**/

AVStream **streams;

stream则是一个stream*的数组,存储着文件中存放的所有音、视频流的码流数据,音频流和视频流都以AVStream的方式存储在其中,而nb_streams则是说明该数组到底有多大,即文件中存储了几路音视频流。(所以才会有打开文件后在所有流中找视频或音频流的操作和对应函数)

/**
* input or output filename
*
* - demuxing: set by avformat_open_input()
* - muxing: may be set by the caller before avformat_write_header()

*/

char filename[1024];

/**
* input or output URL. Unlike the old filename field, this field has no
* length restriction.
*/

char *url;

这两个变量是用于设置打开文件名或者写入文件名。原有的filename变量已经声明为deprecated了(明显现在用的变量url更合理一些,这个改动河南拔智齿),url不限制长度,同时在用ffmpeg打开网络流视频的时候,也用url来代替网络视频流的地址。

/**
* Duration of the stream, in AV_TIME_BASE fractional seconds. Only set this value if you know none of the individual stream durations and also do not set any of them. This is deduced from the
* AVStream values if not set.
* Demuxing only, set by libavformat.
*/
int64_t duration;

stream的时长,在解码时才会用到的数据,时长的单位量很小,1000000 duration才是一秒钟。

/**
* Total stream bitrate in bit/s, 0 if not available. Never set it directly if the file_size and the duration are known as FFmpeg can compute it automatically.
*/
int64_t bit_rate;

比特率,可以被ffmpeg自己根据file_size以及duration计算出来的。对编码来说这个也是很重要的属性了。

/**Forced video codec_id.
* Demuxing: Set by user.*/
enum AVCodecID video_codec_id;
/** Forced audio codec_id.
* Demuxing: Set by user.*/
enum AVCodecID audio_codec_id;
/** Forced subtitle codec_id.
* Demuxing: Set by user. */
enum AVCodecID subtitle_codec_id;

分别是视频,音频,字幕的codecID

/**
* An opaque field for libavformat internal usage.
* Must not be accessed in any way by callers.
*/
AVFormatInternal *internal;

AVFormat的内部不透明字段,这些字段是给ffmpeg自身去使用的,在一般情况下不允许调用者去设置和访问这些字段的内容。(后续可以研究一下这些internal变量,看一看葫芦里卖的什么药)

/** Metadata that applies to the whole file.
* - demuxing: set by libavformat in avformat_open_input()
* - muxing: may be set by the caller before avformat_write_header()
* Freed by libavformat in avformat_free_context(). */
AVDictionary *metadata;

用于整个编解码文件的元数据,解复用和复用时分别由avformat_open_input()以及avformat_write_header()去设置。

以上是一些比较重要的结构体变量,在源代码中,每个结构体变量上面都会带一些比较详细的注释说明。当然,这些仅仅是冰山一角,可以结合源代码慢慢学习,书可以读百遍,代码也当然是常读常新。

avformat_alloc_context()做了啥

avformat_alloc_context()函数的头文件定义也在avformat.h文件当中,而他的函数实现实际上在libavformat/options.c文件当中,代码非常短,只有十几行

AVFormatContext *avformat_alloc_context(void)
{
    AVFormatContext *ic;
    ic = av_malloc(sizeof(AVFormatContext));
    if (!ic) return ic;
    avformat_get_context_defaults(ic);


    ic->internal = av_mallocz(sizeof(*ic->internal));
    if (!ic->internal) {
        avformat_free_context(ic);
        return NULL;
    }
    ic->internal->offset = AV_NOPTS_VALUE;
    ic->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
    ic->internal->shortest_end = AV_NOPTS_VALUE;
    return ic;
}

函数功能很简单,创建一个AVFormatontext,申请内存空间,并初始化一些初始变量。默认赋值函数为avformat_get_context_defaults(ic); 该函数也在options.c文件中

在完成初始化分配之后,再将自己的internal变量分配内存。如果分配失败。那么则返回空指针表示函数运行失败。我坏了。

之后,自动将internal中包含的offset,raw_packet_buffer_remaining_size,shortest_end参数设置为默认值,并返回创建完成的formatcontext,之后就表示完事儿了,我好了。
那么可以继续研究下去的函数对象就是avformat_get_context_defaults()了。

avformat_get_context_defaults()又干了啥

avformat_get_context_defaults()函数直接上源代码,它的位置时avformat_alloc_context()上方,就是个邻居。

static void avformat_get_context_defaults(AVFormatContext *s)
{
    memset(s, 0, sizeof(AVFormatContext));
    s->av_class = &av_format_context_class;
    s->io_open = io_open_default;
    s->io_close = io_close_default;
    av_opt_set_defaults(s);
}

该设置函数为传入的AVFormatContext清干净内存,之后将av_class设置为options中填好的AVClass 对象:

static const AVClass av_format_context_class = {
	.class_name = "AVFormatContext",
	.item_name = format_to_name,
	.option = avformat_options,
	.version = LIBAVUTIL_VERSION_INT,
	.child_next = format_child_next,
	.child_class_next = format_child_class_next,
	.category = AV_CLASS_CATEGORY_MUXER,
	.get_category = get_category,
};

将io_open和io_close设置为默认的打开io和关闭io的函数(两个io函数都在options.c中定义了),并使用av_opt_set_default(s)设置其他的变量数值。而这个函数实际上又是翻跟头跳转到av_opt_set_defaults2()函数中:

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:
            case AV_OPT_TYPE_CHANNEL_LAYOUT:
            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_DICT:
/* Cannot set defaults for these types */
               break;
           default:
               av_log(s, AV_LOG_DEBUG, "AVOption type %d of option %s not implemented yet\n",
opt->type, opt->name);
        }
    }
}

该函数的功能是根据传入的flag以及mask,依次设置好opt并根据opt的类型分类,使用write_number函数做好设置,如果是字符串类型,那么是用set_string*的函数来设置好opt中的各项属性。总体来说还是对AVOption的设置,到这里感觉有点远离AVFormat的范围了,反而是到了AVOption的设置说明了(对于学龄前来说这河里吗)

文章结尾的一些话

以上是avformat_alloc_context()表面的一些学龄前代码学习认识。(看累了,剩下的函数看不动了),后续的话还可以更新一下研究av_format_context_class 以及 av_opt_set_defaults2 这个结构体和函数做好的准备工作。(大概率…不会鸽吧)

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
avformat_alloc_output_context2是FFmpeg中一个函数,用于分配一个输出格式的AVFormatContext结构体,并将其与指定的输出格式相关联。该函数的原型如下: ```c int avformat_alloc_output_context2(AVFormatContext **avctx, AVOutputFormat *oformat, const char *format_name, const char *filename); ``` 其中,参数解释如下: - avctx:指向指针的指针,该指针将存储分配的AVFormatContext结构体的地址。 - oformat:指向AVOutputFormat结构体的指针,该结构体指定了要使用的输出格式。如果为NULL,则由FFmpeg自动选择输出格式。 - format_name:输出格式名称。如果oformat为NULL,则可以通过该参数指定要使用的输出格式的名称。如果不需要,则可以将其设置为NULL。 - filename:输出文件名。如果为NULL,则可以在稍后使用avio_open2函数打开输出文件。 该函数返回0表示成功,否则表示失败。 以下是一个示例代码,演示如何使用avformat_alloc_output_context2函数创建一个AVFormatContext结构体,以及如何将其与输出文件相关联: ```c #include <libavformat/avformat.h> int main(int argc, char *argv[]) { AVFormatContext *out_ctx = NULL; AVOutputFormat *out_fmt = NULL; const char *out_filename = "output.mp4"; int ret; // 初始化FFmpegav_register_all(); // 查找输出格式 out_fmt = av_guess_format("mp4", NULL, NULL); if (!out_fmt) { fprintf(stderr, "Could not find output format.\n"); return -1; } // 分配AVFormatContext结构体 ret = avformat_alloc_output_context2(&out_ctx, out_fmt, NULL, out_filename); if (ret < 0) { fprintf(stderr, "Could not allocate output context.\n"); return -1; } // 打开输出文件 ret = avio_open2(&out_ctx->pb, out_filename, AVIO_FLAG_WRITE, NULL, NULL); if (ret < 0) { fprintf(stderr, "Could not open output file: %s.\n", av_err2str(ret)); return -1; } // 设置AVFormatContext的输出格式 out_ctx->oformat = out_fmt; // 输出文件头 ret = avformat_write_header(out_ctx, NULL); if (ret < 0) { fprintf(stderr, "Error writing header: %s.\n", av_err2str(ret)); return -1; } // TODO: 写入媒体数据 // 输出文件尾 ret = av_write_trailer(out_ctx); if (ret < 0) { fprintf(stderr, "Error writing trailer: %s.\n", av_err2str(ret)); return -1; } // 释放AVFormatContext结构体 avformat_free_context(out_ctx); return 0; } ``` 在上面的示例代码中,首先通过av_guess_format函数查找要使用的输出格式(这里是mp4格式)。然后,使用avformat_alloc_output_context2函数分配一个AVFormatContext结构体,并将其与输出文件相关联。接着,使用avio_open2函数打开输出文件,并将其与AVFormatContextAVIOContext相关联。然后,设置AVFormatContext的输出格式,并调用avformat_write_header函数输出文件头。在此之后,可以向文件中写入媒体数据。最后,调用av_write_trailer函数输出文件尾,并释放AVFormatContext结构体。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值