FFmpeg 是如何实现多态的?(2)

// Note, format could be added after the first 2 checks but that implies that *p is no longer NULL
    while(p != &format->next && !format->next && avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format))
        p = &(*p)->next;

if (!format->next)
        last_iformat = &format->next;
}

void av_register_output_format(AVOutputFormat *format)
{
    AVOutputFormat **p = last_oformat;

// Note, format could be added after the first 2 checks but that implies that *p is no longer NULL
    while(p != &format->next && !format->next && avpriv_atomic_ptr_cas((void * volatile *)p, NULL, format))
        p = &(*p)->next;

if (!format->next)
        last_oformat = &format->next;
}

从代码中可以看到,这两个注册方法会把指定的 AVInputFormat、AVOutputFormat 加到链表的尾部。

AVInputFormat

接着看 AVInputFormat 的定义:

typedef struct AVInputFormat {
    /**
    * A comma separated list of short names for the format. New names
    * may be appended with a minor bump.
    */
    const char *name;
   
    /**
    * Descriptive name for the format, meant to be more human-readable
    * than name. You should use the NULL_IF_CONFIG_SMALL() macro
    * to define it.
    */
    const char *long_name;
   
    /**
    * Can use flags: AVFMT_NOFILE, AVFMT_NEEDNUMBER, AVFMT_SHOW_IDS,
    * AVFMT_GENERIC_INDEX, AVFMT_TS_DISCONT, AVFMT_NOBINSEARCH,
    * AVFMT_NOGENSEARCH, AVFMT_NO_BYTE_SEEK, AVFMT_SEEK_TO_PTS.
    */
    int flags;

/**
    * If extensions are defined, then no probe is done. You should
    * usually not use extension format guessing because it is not
    * reliable enough
    */
    const char *extensions;
 ...

/**
    * Tell if a given file has a chance of being parsed as this format.
    * The buffer provided is guaranteed to be AVPROBE_PADDING_SIZE bytes
    * big so you do not have to check for that unless you need more.
    */
    int (*read_probe)(AVProbeData *);

/**
    * Read the format header and initialize the AVFormatContext
    * structure. Return 0 if OK. 'avformat_new_stream' should be
    * called to create new streams.
    */
    int (*read_header)(struct AVFormatContext *);

/**
    * Read one packet and put it in 'pkt'. pts and flags are also
    * set. 'avformat_new_stream' can be called only if the flag
    * AVFMTCTX_NOHEADER is used and only in the calling thread (not in a
    * background thread).
    * @return 0 on success, < 0 on error.
    *        When returning an error, pkt must not have been allocated
    *        or must be freed before returning
    */
    int (*read_packet)(struct AVFormatContext *, AVPacket *pkt);

...
} AVInputFormat;

可以看到,这个结构体除了 name 等变量外,还具备 read_probe、read_header 等函数指针。
以前面提到的 ff_aac_demuxer 为例,这里看一下它的实现:
AVInputFormat ff_aac_demuxer = {
    // 名称
    .name        = "aac",
    .long_name    = NULL_IF_CONFIG_SMALL("raw ADTS AAC (Advanced Audio Coding)"),
    // 把函数指针指向能够处理 aac 格式的函数实现
    .read_probe  = adts_aac_probe,
    .read_header  = adts_aac_read_header,
    .read_packet  = adts_aac_read_packet,
    .flags        = AVFMT_GENERIC_INDEX,
    .extensions  = "aac",
    .mime_type    = "audio/aac,audio/aacp,audio/x-aac",
    .raw_codec_id = AV_CODEC_ID_AAC,
};

总结

根据以上代码的分析,此时我们就能得出问题的答案了:
FFmpeg 之所以能够作为一个平台,无论是封装、解封装,还是编码、解码,在处理对应格式的文件/数据时,都能找到对应的库来实现,而不需要修改代码,主要就是通过结构体 + 函数指针实现的。具体实现方式是:首先设计一个结构体,然后创建该结构体的多个对象,每个对象都有着自己的成员属性及函数实现。这样就使得 FFmpeg 具备了类似于面向对象编程中的多态的效果。
PS:avcodec_register_all 也是一样的,有兴趣的可以看看 AVCodec 的声明以及 ff_libx264_encoder 等编解码器的实现。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/71d5d8e08b4b358f5a1d62e4ce55de4e.html