avformat_alloc_context

AVFormatContext:统领全局的基本结构体。主要用于处理封装格式(FLV/MKV/RMVB等)。
AVIOContext:输入输出对应的结构体,用于输入输出(读写文件,RTMP协议等)。
AVStream,AVCodecContext:视音频流对应的结构体,用于视音频编解码。
AVFrame:存储非压缩的数据(视频对应RGB/YUV像素数据,音频对应PCM采样数据)
AVPacket:存储压缩数据(视频对应H.264等码流数据,音频对应AAC/MP3等码流数据)

avformat_alloc_context_第1张图片

函数用来申请AVFormatContext类型变量并初始化默认参数。
该函数用于分配空间创建一个AVFormatContext对象,并且强调使用avformat_free_context方法来清理并释放该对象的空间。

  • 分配空间,创建一个 AVFormatContext对象。
  • avformat_free_context() 可用于释放上下文和所有内容
  • 由其中的框架分配。
/**
 * Allocate an AVFormatContext.
 * avformat_free_context() can be used to free the context and everything
 * allocated by the framework within it.
 */
AVFormatContext *avformat_alloc_context(void);

源码:avformat_alloc_context
主要完成AVFormatContext的空间分配,注意分配在堆上;
给AVFormatContext的成员赋默认值;
完成AVFormatContext内部使用对象AVFormatInternal结构体的空间分配及其部分成员字段的赋值。

AVFormatContext *avformat_alloc_context(void)
{
    AVFormatContext *ic;	//创建一个AVFormatContext对象为ic
    ic = av_malloc(sizeof(AVFormatContext));	//使用av_malloc分配空间,分配空间的作用是存储数据
    if (!ic) return ic;		//判断ic是否为NILL,为空返回ic
    avformat_get_context_defaults(ic);	//用于设置AVFormatContext的字段的默认值
    
    //给internal字段分配内存,ffmpeg内部使用
    ic->internal = av_mallocz(sizeof(*ic->internal));
    //判断ic->internal是否为NILL,为NILL,释放上下文和内容,返回NULL
    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;
}

从代码中可以看出,avformat_alloc_context()调用av_malloc()为AVFormatContext结构体分配了内存,而且同时也给AVFormatContext中的internal字段分配内存(这个字段是FFmpeg内部使用的,先不分析)。此外调用了一个avformat_get_context_defaults()函数。该函数用于设置AVFormatContext的字段的默认值。它的定义也位于libavformat\options.c,确切的说就位于avformat_alloc_context()上面。

av_malloc:是libavutil文件中的函数
该函数作用在于给对象分配内存块,并且是内存对齐的

  • libavutil文件的功能:
    加密和哈希
    数学
    字符串操作
    内存管理
    数据结构
    视频相关
    音频相关
    错误代码
    记录设施
    其他
  • 分配一个适合所有内存访问的对齐方式的内存块
  • (包括矢量,如果在 CPU 上可用)。
  • @param size 要分配的内存块的大小(以字节为单位)
  • @return 指向已分配块的指针,如果块不能,则为NULL
  • 被分配
  • @参见 av_mallocz()
/**
 * Allocate a memory block with alignment suitable for all memory accesses
 * (including vectors if available on the CPU).
 * @param size Size in bytes for the memory block to be allocated
 * @return Pointer to the allocated block, or `NULL` if the block cannot
 *         be allocated
 * @see av_mallocz()
 */
void *av_malloc(size_t size) av_malloc_attrib av_alloc_size(1);

av_malloc_attrib是一个宏定义,如果是在编译器GCC3.1及以上版本的情况下,给方法av_malloc增加属性__attribute__((malloc)),该属性指示编译器按照malloc函数来对待,并且可以对其实施相应的优化措施。后续在研究ffmpeg中内存分配与管理的源码之后,会再尝试写一篇文章详细介绍。此时,先给出几个网上关于该属性的描述相关网址:http://www.keil.com/support/man/docs/armcc/armcc_chr1359124975555.htm;
https://stackoverflow.com/questions/18485447/gcc-attribute-malloc;
https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bmalloc_007d-function-attribute-3251

  * @def av_malloc_attrib
  * 函数属性表示类似 malloc 的函数。
  * @see <a href="https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007bmalloc_007d-function-attribute-3251">GCC 文档中的函数属性 `malloc`< /a>
/**
 * @def av_malloc_attrib
 * Function attribute denoting a malloc-like function.
 * @see Function attribute `malloc` in GCC's documentation
 */
#if AV_GCC_VERSION_AT_LEAST(3,1)
    #define av_malloc_attrib __attribute__((__malloc__))
#else
    #define av_malloc_attrib
#endif

av_alloc_size(1)也是一个宏定义,如果是在编译器GCC4.3及以上版本的情况下,给方法增加一个属性__attribute__((alloc_size(1))),告知编译器av_malloc(size_t size)方法的第一个参数,也即size是要分配的空间大小,关于__attribute__((alloc_size(VA_ARGS)))属性的详细描述可以见:https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-g_t_0040code_007balloc_005fsize_007d-function-attribute-3220

av_malloc() :
av_malloc()是FFmpeg中最常见的内存分配函数。它的定义如下。

void *av_malloc(size_t size)
{
    void *ptr = NULL;

    /* let's disallow possibly ambiguous cases */
    if (size > (max_alloc_size - 32))
        return NULL;

#if HAVE_POSIX_MEMALIGN
    if (size) //OS X on SDK 10.6 has a broken posix_memalign implementation
    if (posix_memalign(&ptr, ALIGN, size))
        ptr = NULL;
#elif HAVE_ALIGNED_MALLOC
    ptr = _aligned_malloc(size, ALIGN);
#elif HAVE_MEMALIGN
#ifndef __DJGPP__
    ptr = memalign(ALIGN, size);
#else
    ptr = memalign(size, ALIGN);
#endif
    /* Why 64?
     * Indeed, we should align it:
     *   on  4 for 386
     *   on 16 for 486
     *   on 32 for 586, PPro - K6-III
     *   on 64 for K7 (maybe for P3 too).
     * Because L1 and L2 caches are aligned on those values.
     * But I don't want to code such logic here!
     */
    /* Why 32?
     * For AVX ASM. SSE / NEON needs only 16.
     * Why not larger? Because I did not see a difference in benchmarks ...
     */
    /* benchmarks with P3
     * memalign(64) + 1          3071, 3051, 3032
     * memalign(64) + 2          3051, 3032, 3041
     * memalign(64) + 4          2911, 2896, 2915
     * memalign(64) + 8          2545, 2554, 2550
     * memalign(64) + 16         2543, 2572, 2563
     * memalign(64) + 32         2546, 2545, 2571
     * memalign(64) + 64         2570, 2533, 2558
     *
     * BTW, malloc seems to do 8-byte alignment by default here.
     */
#else
    ptr = malloc(size);
#endif
    if(!ptr && !size) {
        size = 1;
        ptr= av_malloc(1);
    }
#if CONFIG_MEMORY_POISONING
    if (ptr)
        memset(ptr, FF_MEMORY_POISON, size);
#endif
    return ptr;
}

malloc()

malloc() 函数用来动态地分配内存空间(如果你不了解动态内存分配,请查看:C语言动态内存分配及变量存储类别),其原型为:
void* malloc (size_t size);

【参数说明】size 为需要分配的内存空间的大小,以字节(Byte)计。

【函数说明】malloc() 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。如果希望在分配内存的同时进行初始化,请使用 calloc() 函数。

【返回值】分配成功返回指向该内存的地址,失败则返回 NULL。

由于申请内存空间时可能有也可能没有,所以需要自行判断是否申请成功,再进行后续操作。

如果 size 的值为 0,那么返回值会因标准库实现的不同而不同,可能是 NULL,也可能不是,但返回的指针不应该再次被引用。

注意:函数的返回值类型是 void *void 并不是说没有返回值或者返回空指针,而是返回的指针类型未知。所以在使用 malloc() 时通常需要进行强制类型转换,将 void 指针转换成我们希望的类型,例如:
char *ptr = (char *)malloc(10);  // 分配10个字节的内存空间,用来存放字符

动态内存分配举例:
#include   /* printf, scanf, NULL */
#include   /* malloc, free, rand, system */
int main ()
{
    int i,n;
    char * buffer;
    printf ("输入字符串的长度:");
    scanf ("%d", &i);
    buffer = (char*)malloc(i+1);  // 字符串最后包含 \0
    if(buffer==NULL) exit(1);  // 判断是否分配成功
    // 随机生成字符串
    for(n=0; n<i; n++)
        buffer[n] = rand()%26+'a';
    buffer[i]='\0';
    printf ("随机生成的字符串为:%s\n",buffer);
    free(buffer);  // 释放内存空间
    system("pause");
    return 0;
}
运行结果:
输入字符串的长度:20
随机生成的字符串为:phqghumeaylnlfdxfirc

该程序生成一个指定长度的字符串,并用随机生成的字符填充。字符串的长度仅受限于可用内存的长度。

关于size_t
size _t 这个类型在FFmpeg中多次出现,简单解释一下其作用。size _t是为了增强程序的可移植性而定义的。不同系统上,定义size_t可能不一样。它实际上就是unsigned int。

posix_memalign():在大多数情况下,编译器和C库透明地帮你处理对齐问题。POSIX 标明了通过malloc( ), calloc( ), 和 realloc( ) 返回的地址对于任何的C类型来说都是对齐的。在Linux中,这些函数返回的地址在32位系统是以8字节为边界对齐,在64位系统是以16字节为边界对齐的。有时候,对于更大的边界,例如页面,程序员需要动态的对齐。虽然动机是多种多样的,但最常见的是直接块I/O的缓存的对齐或者其它的软件对硬件的交互,因此,POSIX 1003.1d提供一个叫做posix_memalign( )的函数:
_aligned_malloc():在指定的对齐边界上分配内存。
内存对齐:
什么是内存对齐:还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。
//32位系统
#include
struct{
int x;
char y;
}s;
int main()
{
printf("%d\n",sizeof(s); // 输出8
return 0;
}
现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
为什么要内存对齐
尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.
现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。
1,平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2,性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的联系四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器。这需要做很多工作。
现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。
内存对齐规则:
1.基本类型的对齐值就是其sizeof值;
2.结构体的对齐值是其成员的最大对齐值;
3.编译器可以设置一个最大对齐值,怎么类型的实际对齐值是该类型的对齐值与默认对齐值取最小值得来。

av_mallocz()

  * 分配一个适合所有内存访问的对齐方式的内存块
  *(包括向量,如果在 CPU 上可用)并将所有字节归零
  * 堵塞。
  * @param size 要分配的内存块的大小(以字节为单位)
  * @return 指向已分配块的指针,如果无法分配则为`NULL`
  * @参见 av_malloc()
/**
 * Allocate a memory block with alignment suitable for all memory accesses
 * (including vectors if available on the CPU) and zero all the bytes of the
 * block.
 *
 * @param size Size in bytes for the memory block to be allocated
 * @return Pointer to the allocated block, or `NULL` if it cannot be allocated
 * @see av_malloc()
 */
void *av_mallocz(size_t size) av_malloc_attrib av_alloc_size(1);

av_mallocz() 源码
av_mallocz()可以理解为av_malloc()+zeromemory。代码如下。
从源代码可以看出av_mallocz()中调用了av_malloc()之后,又调用memset()将分配的内存设置为0。

void *av_mallocz(size_t size)
{
    void *ptr = av_malloc(size);	//使用av_malloc分配内存
    if (ptr)
        memset(ptr, 0, size);		//将分配的内存的所有字节置为0
    return ptr;
}

/**
  * 释放 AVFormatContext 及其所有流。
  * @param s 要释放的上下文
  */
/**
 * Free an AVFormatContext and all its streams.
 * @param s context to free
 */
void avformat_free_context(AVFormatContext *s);

avformat_free_context()源码

void avformat_free_context(AVFormatContext *s)
{
    int i;

    if (!s)
        return;

    av_opt_free(s);
    if (s->iformat && s->iformat->priv_class && s->priv_data)
        av_opt_free(s->priv_data);
    if (s->oformat && s->oformat->priv_class && s->priv_data)
        av_opt_free(s->priv_data);

    for (i = s->nb_streams - 1; i >= 0; i--)
        ff_free_stream(s, s->streams[i]);


    for (i = s->nb_programs - 1; i >= 0; i--) {
        av_dict_free(&s->programs[i]->metadata);
        av_freep(&s->programs[i]->stream_index);
        av_freep(&s->programs[i]);
    }
    av_freep(&s->programs);
    av_freep(&s->priv_data);
    while (s->nb_chapters--) {
        av_dict_free(&s->chapters[s->nb_chapters]->metadata);
        av_freep(&s->chapters[s->nb_chapters]);
    }
    av_freep(&s->chapters);
    av_dict_free(&s->metadata);
    av_dict_free(&s->internal->id3v2_meta);
    av_freep(&s->streams);
    flush_packet_queue(s);
    av_freep(&s->internal);
    av_freep(&s->url);
    av_free(s);
}

从代码中可以看出,avformat_free_context()调用了各式各样的销毁函数:av_opt_free(),av_freep(),av_dict_free()。这些函数分别用于释放不同种类的变量,在这里不再详细讨论。在这里看一个释放AVStream的函数ff_free_stream()。该函数的定义位于libavformat\options.c(其实就在avformat_free_context()上方)。

static void free_stream(AVStream **pst)
{
    AVStream *st = *pst;
    int i;

    if (!st)
        return;

    for (i = 0; i < st->nb_side_data; i++)
        av_freep(&st->side_data[i].data);
    av_freep(&st->side_data);

    if (st->parser)
        av_parser_close(st->parser);

    if (st->attached_pic.data)
        av_packet_unref(&st->attached_pic);

    if (st->internal) {
        avcodec_free_context(&st->internal->avctx);
        for (i = 0; i < st->internal->nb_bsfcs; i++) {
            av_bsf_free(&st->internal->bsfcs[i]);
            av_freep(&st->internal->bsfcs);
        }
        av_freep(&st->internal->priv_pts);
        av_bsf_free(&st->internal->extract_extradata.bsf);
        av_packet_free(&st->internal->extract_extradata.pkt);
    }
    av_freep(&st->internal);

    av_dict_free(&st->metadata);
    avcodec_parameters_free(&st->codecpar);
    av_freep(&st->probe_data.buf);
    av_freep(&st->index_entries);
#if FF_API_LAVF_AVCTX
FF_DISABLE_DEPRECATION_WARNINGS
    avcodec_free_context(&st->codec);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    av_freep(&st->priv_data);
    if (st->info)
        av_freep(&st->info->duration_error);
    av_freep(&st->info);
#if FF_API_LAVF_FFSERVER
FF_DISABLE_DEPRECATION_WARNINGS
    av_freep(&st->recommended_encoder_configuration);
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    av_freep(pst);
}

void ff_free_stream(AVFormatContext *s, AVStream *st)
{
    av_assert0(s->nb_streams>0);
    av_assert0(s->streams[ s->nb_streams - 1 ] == st);

    free_stream(&s->streams[ --s->nb_streams ]);
}
ff_free_stream()函数中调用了free_stream()
从代码中可以看出,与释放AVFormatContext类似,释放AVStream的时候,也是调用了av_freep()av_dict_free()这些函数释放有关的字段。如果使用了parser的话,会调用av_parser_close()关闭该parser。

memset()函数介绍
首先来看函数原型
void *memset(void *str, int c, size_t n)
解释:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
作用:是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法
头文件:C中#include,C++中#include
看着介绍其实函数作用非常简单,就是用于初始化,但是需要注意的是memset赋值的时候是按字节赋值,是将参数化成二进制之后填入一个字节。就比如前面的例子中,想要通过memset(a,100,sizeof a)给int类型的数组赋值,你给第一个字节的是一百,转成二进制就是0110 0100,而int有四个字节,也就是说,一个int被赋值为
0110 0100,0110 0100,0110 0100,0110 0100,对应的十进制是1684300900,根本不是你想要赋的值100,这也就解释了为什么数组中的元素的值都为1684300900。
memset赋值时只能赋值为0?
答案肯定不是,比如任意字符都是可以的,初始化成0是最常用的。int类型的一般都是赋值0或-1,其他的值都不行。
结论
为地址str开始的n个字节赋值c,注意:是逐个字节赋值,str开始的n个字节中的每个字节都赋值为c。
(1) 若str指向char型地址,value可为任意字符值;
(2) 若str指向非char型,如int型地址,要想赋值正确,value的值只能是-1或0,因为-1和0转化成二进制后每一位都是一样的,设int型占4个字节,则-1=0XFFFFFFFF, 0=0X00000000。
举例:给数组赋值-1
int A[2];
memset(A, -1, sizeof A);

static void avformat_get_context_defaults(AVFormatContext *s)
{
    memset(s, 0, sizeof(AVFormatContext));	//memset()将AVFormatContext的所有字段置0

    s->av_class = &av_format_context_class;

    s->io_open  = io_open_default;
    s->io_close = io_close_default;

    av_opt_set_defaults(s);		//设置默认值
}

1,将AVFormatContext对象内存块全置为0;
2,将静态的AVClass对象av_format_context_class赋值给AVFormatContext.av_class;
3,将静态函数io_open_default,io_close_default赋值给AVFormatContext.io_open,AVFormatContext.io_close
4,使用av_opt_set_defaults(s)将根据赋值后的AVClass.option选项信息来填充AVFormatContext的成员字段

使用static的就是静态函数 C语言中使用静态函数的好处:
静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。

av_format_context_class结构体
该静态类位于libavformat/options.c中,是AVClass对象,AVClass对象是定义在libavutil库中的对象,可以想象下,肯定是提供某种功能的工具类,

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

1,AVClass.class_name::AVClass类名称,通常与其关联的上下文对象的结构体类型名相同。此处关联的是AVFormatContext上下文对象,因此AVClass的名称也即为"AVFormatContext";

2,AVClass.item_name:这是一个函数指针,该函数返回关联的上下文实例名称。此处的format_to_name是libavformat/options.c文件中的静态函数,返回的是AVFormatContext中已经保存好的输入文件格式名称或者是输出格式名称,若是解封装过程还没有探测到文件格式或者是封装过程中用户还未设置输出文件格式,那么将返回NULL。源码如下:

static const char* format_to_name(void* ptr)
{
    AVFormatContext* fc = (AVFormatContext*) ptr;
    if(fc->iformat) return fc->iformat->name;
    else if(fc->oformat) return fc->oformat->name;	
    else return "NULL";
}

3, AVClass.option:指向的是某个选项列表数组的第一个选项,这个是非常重要的一个参数,提供了以“以字符串来设置结构体成员字段的方式”,option字段则指向一个元素个数很多的静态数组avformat_options。该数组单独定义于libavformat\options_table.h中。其中包含了AVFormatContext支持的所有的AVOption,如下所示。此处的avformat_options是定义在libavformat/options_table.h文件中定义的静态常量结构体,源码如下:

static const AVOption avformat_options[] = {
{"avioflags", NULL, OFFSET(avio_flags), AV_OPT_TYPE_FLAGS, {.i64 = DEFAULT }, INT_MIN, INT_MAX, D|E, "avioflags"},
{"direct", "reduce buffering", 0, AV_OPT_TYPE_CONST, {.i64 = AVIO_FLAG_DIRECT }, INT_MIN, INT_MAX, D|E, "avioflags"},
{"probesize", "set probing size", OFFSET(probesize), AV_OPT_TYPE_INT64, {.i64 = 5000000 }, 32, INT64_MAX, D},
此处省略几十行....
{NULL},
};

4,AVClass.version:表示的是libavutil库的当前版本,该版本号有宏LIBAVUTIL_VERSION_INT制定,该宏定义在libavutil/version.h文件中,如下源码所示:

* @defgroup lavu_ver 版本和构建诊断 
* 在编译时和运行时检查有用的宏和函数 
* 正在使用哪个版本的 libavutil。
/**
 * @defgroup lavu_ver Version and Build diagnostics
 * Macros and function useful to check at compiletime and at runtime
 * which version of libavutil is in use.
 */

#define LIBAVUTIL_VERSION_MAJOR  56
#define LIBAVUTIL_VERSION_MINOR  31
#define LIBAVUTIL_VERSION_MICRO 100

#define LIBAVUTIL_VERSION_INT   AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \
                                               LIBAVUTIL_VERSION_MINOR, \
                                               LIBAVUTIL_VERSION_MICRO)
#define LIBAVUTIL_VERSION       AV_VERSION(LIBAVUTIL_VERSION_MAJOR,     \
                                           LIBAVUTIL_VERSION_MINOR,     \
                                           LIBAVUTIL_VERSION_MICRO)
#define LIBAVUTIL_BUILD         LIBAVUTIL_VERSION_INT

#define LIBAVUTIL_IDENT         "Lavu" AV_STRINGIFY(LIBAVUTIL_VERSION)

5, AVClass.child_next:该字段是一个函数指针,仅限于字面意思,也就是返回输入对象中包含的子对象,此处关联的format_child_next是位于libavformat/options.c文件中的静态函数,源码如下:可知要么是AVFormatContext的priv_data存在且有效的情况返回这个字段的对象

static void *format_child_next(void *obj, void *prev)
{
    AVFormatContext *s = obj;
    if (!prev && s->priv_data &&
        ((s->iformat && s->iformat->priv_class) ||
          s->oformat && s->oformat->priv_class))
        return s->priv_data;
    if (s->pb && s->pb->av_class && prev != s->pb)
        return s->pb;
    return NULL;
}

6,AVClass.child_class_next:该字段也是一个函数指针,英文注释是“Return an AVClass corresponding to the next potential AVOptions-enabled child”,与上一个函数一样,对其作用的理解还不透彻,字面意思是返回返回一个AVClass对象,该对象与下一个可能的子对象相关,与上个函数的区别在于“上个函数迭代并返回的是已经存在的对象,而本函数在所有可能的对象中迭代并返回”。此处关联的format_child_class_next是位于libavformat/options.c中的静态函数,源码如下:分析见源码注释

static const AVClass *format_child_class_next(const AVClass *prev)
{
    AVInputFormat  *ifmt = NULL;
    AVOutputFormat *ofmt = NULL;
	// 查找输入为空,直接返回ff_avio_class对象,该对象的源码见后面
    if (!prev)
        return &ff_avio_class;
    //迭代查询AVInputFormat
    //判断prev是否是某个输入格式
    while ((ifmt = av_iformat_next(ifmt)))
        if (ifmt->priv_class == prev)
            break;
	// 迭代查询串起来的AVOutputFormat对象,看prev是否是某个输出格式
    if (!ifmt)
        while ((ofmt = av_oformat_next(ofmt)))
            if (ofmt->priv_class == prev)
                break;
    // 如果不是某个输出格式,那么取出下一个AVInputFormat.priv_class存在的输入格式,并返回
    // AVInputFormat.priv_class
    if (!ofmt)
        while (ifmt = av_iformat_next(ifmt))
            if (ifmt->priv_class)
                return ifmt->priv_class;
	// 如果所有的AVInputFormat.priv_class均不存在的情况下,那么取出下一个
    // AVOutputFormat.priv_class存在的输入格式,并返回 AVOutputFormat.priv_class
    while (ofmt = av_oformat_next(ofmt))
        if (ofmt->priv_class)
            return ofmt->priv_class;
	// 如果输入prev不为空,且所有的AVInputFormat.priv_class以及AVOutputFormat.priv_class
    // 均未空的情况下,则返回空
    return NULL;
}

const AVClass ff_avio_class = {
    .class_name = "AVIOContext",
    .item_name  = av_default_item_name,
    .version    = LIBAVUTIL_VERSION_INT,
    .option     = ff_avio_options,
    .child_next = ff_avio_child_next,
    .child_class_next = ff_avio_child_class_next,
};

7,AVClass.category:该字段是一个AVClassCategory枚举类型,作用是“Category used for visualization (like color)”,用于控制视觉特性,比如颜色,在ffmpeg日志系统中将使用该值。此处设置的值为AV_CLASS_CATEGORY_MUXER,具体的AVClassCategory分类信息如下源码所示

typedef enum {
    AV_CLASS_CATEGORY_NA = 0,	//AV 类别 NA
    AV_CLASS_CATEGORY_INPUT,	//AV 类别输入
    AV_CLASS_CATEGORY_OUTPUT,	//AV 类别输出
    AV_CLASS_CATEGORY_MUXER,	//AV 类别多路复用器
    AV_CLASS_CATEGORY_DEMUXER,	//AV 类别分路器
    AV_CLASS_CATEGORY_ENCODER,	//AV 类别编码器
    AV_CLASS_CATEGORY_DECODER,	//AV 类别解码器
    AV_CLASS_CATEGORY_FILTER,	//AV 类别过滤器
    AV_CLASS_CATEGORY_BITSTREAM_FILTER,	//AV 类类别比特流过滤器
    AV_CLASS_CATEGORY_SWSCALER,		// 对图像进行缩放或者改变图像格式
    AV_CLASS_CATEGORY_SWRESAMPLER,	// 软件重采样
    AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,	//AV 类别设备视频输出
    AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,	//AV 类别设备视频输入
    AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,	//AV 类别设备音频输出
    AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,	//AV 类别设备音频输入
    AV_CLASS_CATEGORY_DEVICE_OUTPUT,	//AV 类别设备输出
    AV_CLASS_CATEGORY_DEVICE_INPUT,	//AV 类别设备输入
    AV_CLASS_CATEGORY_NB  ///< not part of ABI/API
}AVClassCategory;

AVClass.get_category:该字段是个函数指针,返回输入对象的AVClassCategory枚举类别。此处get_category是libavformat/options.c中的静态函数,源码如下所示:如果AVFormatContext的输入文件格式存在,那么AVClassCategory类别为解封装类目AV_CLASS_CATEGORY_DEMUXER,若不存在那么为封装类目AV_CLASS_CATEGORY_MUXER

static AVClassCategory get_category(void *ptr)
{
    AVFormatContext* s = ptr;
    if(s->iformat) return AV_CLASS_CATEGORY_DEMUXER;
    else           return AV_CLASS_CATEGORY_MUXER;
}

io_open_default() && io_close_default() 源码:

io_open_default: 主要作用是首先打印一条日志,告知现在要打开某个URL了,用于读还是用于写;然后会根据URL来创建文件读写的AVIOContext对象,如果旧接口还未被淘汰并且用户设置了打开IO的回调函数open_cb,那么该工作由用户设置的s->open_sb函数来完成; 如果旧接口已经被淘汰,那么该工作由ffio_open_whitelist()来完成。
io_close_default: 主要作用是调用avio_close()方法来正确的关闭AVIOContext这个IO上下文对象。

static int io_open_default(AVFormatContext *s, AVIOContext **pb,
                           const char *url, int flags, AVDictionary **options)
{
    int loglevel;

    if (!strcmp(url, s->url) ||
        s->iformat && !strcmp(s->iformat->name, "image2") ||
        s->oformat && !strcmp(s->oformat->name, "image2")
    ) {
        loglevel = AV_LOG_DEBUG;
    } else
        loglevel = AV_LOG_INFO;
    av_log(s, loglevel, "Opening \'%s\' for %s\n", url, flags & AVIO_FLAG_WRITE ? "writing" : "reading");
#if FF_API_OLD_OPEN_CALLBACKS
FF_DISABLE_DEPRECATION_WARNINGS
    if (s->open_cb)
        return s->open_cb(s, pb, url, flags, &s->interrupt_callback, options);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
    return ffio_open_whitelist(pb, url, flags, &s->interrupt_callback, options, s->protocol_whitelist, s->protocol_blacklist);
}

av_opt_set_defaults()

“Set the values of all AVOption fields to their default values.”,注意这个入参的说明,入参对象的第一个成员必须是AVClass对象的指针。
作用:一言以蔽之,就是不断取出对应的AVOption对象,将AVOption对象中存储的初始值赋值给s对象(此处为AVFormatContext)的对应的成员
av_opt_set_defaults() 直接调用了 av_opt_set_defaults2(),而该函数逻辑也很简单:

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))) { // 获取AVOption对象
        // s->av_class->option与s的成员是对应关系;
        // AVOption的offset属性定义了s对象对应的成员相对于s对象首地址的偏移offset,
        // 从而求得该AVOption所映射的s对象成员的地址dst
        void *dst = ((uint8_t*)s) + opt->offset; 
 
        // mask和flags用以控制需要修改设置哪些参数
        if ((opt->flags & mask) != flags) 
            continue;
 
        // 如果属性是只读,那么不设置
        if (opt->flags & AV_OPT_FLAG_READONLY)
            continue;
 
        // 根据AVOption对象的type属性可以映射的s对象的成员的类型
        // 根据不同类型,调用不同的函数将AVOption的default_val属性提供的默认值
        // 来设置s的对象的对应成员。
        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);
        }
    }
}

我们已经分析了AVFormatContext->AVClass->option被初始化指向了一个静态的AVOption数组,av_opt_next() 作用就是获取下AVFormatContext->AVClass->option数组中,由last指定的AVOption对象的下一个。具体av_opt_next() 如何实现这一点的,见下面源码以及注释:

const AVOption *av_opt_next(const void *obj, const AVOption *last)
{
    const AVClass *class;
    // 若obj对象为空,自然option也肯定不存在,返回空
    if (!obj) 
        return NULL;
 
    // 这个是重点:
    // obj是一个对象的指针,其指向的对象实体的第一个参数是一个指向AVClass对象的指针
    // 如下的强制转换就是取出obj第一个参数值,也即指向obj对象关联的AVClass对象的地址
    // 赋值给class
    class = *(const AVClass**)obj; 
 
    // 如果last为空,也即没有指定AVOption对象
    // 并且class不为空,也即obj这个对象第一个参数确实是AVClass对象指针
    // 并且AVClass->option不为空,也即指向有效的AVOption数组
    // 并且class->option[0].name不为空,也即AVOption数组的第一项的name成员不为空
    if (!last && class && class->option && class->option[0].name)     
        return class->option;         // 返回AVOption数组的第一个成员
 
    // 如果last不为空,即已被指定
    // 并且last[1].name不为空,即注意理解这个last[1]的数组操作:last指向的AVOption的下一个AVOption的name成员不为空。
    if (last && last[1].name)
        return ++last;               // 返回AVOtion数组的last的下一个AVOption,注意一般AVOption数组的最后一项是NULL。
 
    // 若之前没找到合适的AVOption,那么返回NULL
    return NULL;
}

avformat_alloc_context_第2张图片
avformat_alloc_context()函数主要干了哪些事儿:分配AVFormatContext、设置av_class(关联AVOptions)、设置io_open、设置io_close、avformat_options初始化AVFormatContext。

你可能感兴趣的:(音视频)