衆所周知,FFmpeg 在解碼的時候,不管輸入文件是 MP4 文件仍是 FLV 文件,或者其它文件格式,都能正確解封裝、解碼,而代碼不須要針對不一樣的格式作出任何改變,這是面向對象中很常見的多態特性,但 FFmpeg 是用 C 語言編寫的,那麼它是如何使用 C 語言實現了多態特性的呢?web
要解決這個問題,首先須要從函數 av_register_all 提及。編程
av_register_all 是幾乎全部 FFmpeg 程序中第一個被調用的函數,用於註冊在編譯 FFmpeg 時設置了 --enable 選項的封裝器、解封裝器、編碼HX器、解碼HX器等。源碼以下:app
#define REGISTER_MUXER(X, x) \ { \ extern AVOutputFormat ff_##x##_muxer; \ if (CONFIG_##X##_MUXER) \ av_register_output_format(&ff_##x##_muxer); \ } #define REGISTER_DEMUXER(X, x) \ { \ extern AVInputFormat ff_##x##_demuxer; \ if (CONFIG_##X##_DEMUXER) \ av_register_input_format(&ff_##x##_demuxer); \ } #define REGISTER_MUXDEMUX(X, x) REGISTER_MUXER(X, x); REGISTER_DEMUXER(X, x) static void register_all(void) { // 註冊編解碼HX器 avcodec_register_all(); // 註冊封裝器、解封裝器 /* (de)muxers */ REGISTER_MUXER (A64, a64); REGISTER_DEMUXER (AA, aa); REGISTER_DEMUXER (AAC, aac); REGISTER_MUXDEMUX(AC3, ac3); REGISTER_MUXDEMUX(FLV, flv); REGISTER_MUXDEMUX(GIF, gif); ... /* image demuxers */ REGISTER_DEMUXER (IMAGE_BMP_PIPE, image_bmp_pipe); REGISTER_DEMUXER (IMAGE_JPEG_PIPE, image_jpeg_pipe); REGISTER_DEMUXER (IMAGE_SVG_PIPE, image_svg_pipe); REGISTER_DEMUXER (IMAGE_WEBP_PIPE, image_webp_pipe); REGISTER_DEMUXER (IMAGE_PNG_PIPE, image_png_pipe); ... /* external libraries */ REGISTER_MUXER (CHROMAPRINT, chromaprint); ... } void av_register_all(void) { static AVOnce control = AV_ONCE_INIT; ff_thread_once(&control, register_all); }
define 裏的 ## 用於拼接兩個字符串,好比 REGISTER_DEMUXER(AAC, aac) ,它等效於:less
extern AVInputFormat ff_aac_demuxer; if(CONFIG_AAC_DEMUXER) av_register_input_format(&ff_aac_demuxer);
能夠看出,編譯 ffmpeg 時相似於 "--enable-muxer=xxx" 這樣的選項在此時發揮了做用,它決定是否註冊某個格式對應的(解)封裝器,以便以後處理該格式的時候找到這個(解)封裝器。ide
av_register_input_format、av_register_output_format 源碼以下:svg
/** head of registered input format linked list */ static AVInputFormat *first_iformat = NULL; /** head of registered output format linked list */ static AVOutputFormat *first_oformat = NULL; static AVInputFormat **last_iformat = &first_iformat; static AVOutputFormat **last_oformat = &first_oformat; void av_register_input_format(AVInputFormat *format) { AVInputFormat **p = last_iformat; // 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 的定義:this
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 爲例,這裏看一下它的實現:atom
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 等編解碼HX器的實現。
做者:zouzhiheng 連接:https://www.jianshu.com/p/c12e6888de10 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。