2019獨角獸企業重金招聘Python工程師標準>>>
前言
眾所周知,FFmpeg 在解碼的時候,無論輸入文件是 MP4 文件還是 FLV 文件,或者其它文件格式,都能正確解封裝、解碼,而代碼不需要針對不同的格式做出任何改變,這是面向對象中很常見的多態特性,但 FFmpeg 是用 C 語言編寫的,那么它是如何使用 C 語言實現了多態特性的呢?
要解決這個問題,首先需要從函數 av_register_all 說起。
av_register_all
av_register_all 是幾乎所有 FFmpeg 程序中第一個被調用的函數,用于注冊在編譯 FFmpeg 時設置了 --enable 選項的封裝器、解封裝器、編碼HX器、解碼HX器等。源碼如下:
#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) ,它等效于:
extern AVInputFormat ff_aac_demuxer;
if(CONFIG_AAC_DEMUXER) av_register_input_format(&ff_aac_demuxer);
可以看出,編譯 ffmpeg 時類似于 "--enable-muxer=xxx" 這樣的選項在此時發揮了作用,它決定是否注冊某個格式對應的(解)封裝器,以便之后處理該格式的時候找到這個(解)封裝器。
av_register_input_format
av_register_input_format、av_register_output_format 源碼如下:
/** 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 NULLwhile(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 NULLwhile(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 等編解碼HX器的實現。
作者:zouzhiheng
鏈接:https://www.jianshu.com/p/c12e6888de10
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。