問題的起源
在學習音影片的知識時,一直對 “編碼格式” 和“編碼器”兩個概念有疑惑,總是感覺它們是同一個東西。今天在讀阮一峰的《FFmpeg 影片處理入門教程》時,才明白,“編碼格式“ 和 “編碼器” 是兩個不同的概念:
編碼格式
影片和音訊都需要經過編碼,才能儲存成檔案。不同的編碼格式(CODEC),有不同的壓縮率,會導致檔案大小和清晰度的差異。
查詢命令
$ ffmpeg -codecs
編碼器
編碼器(encoders)是實現某種編碼格式的庫檔案。只有安裝了某種格式的編碼器,才能實現該格式影片/音訊的編碼和解碼。
查詢命令
$ ffmpeg -encoders
概念的澄清
原來,編碼格式是音影片資料的位元流格式,它定義了音影片資料壓縮/解壓的方式,以及是如何儲存到檔案中的等等一系列問題。而編碼器是一個工具庫,它用來具體完成資料的壓縮/解壓等一系列操作。打個比方,編碼格式就是菜譜,而編碼器就是廚師,他們兩個配合才能真正做出一道美味的菜餚。
下面,詳細查看了一下 FFmpeg 的程式碼,來驗證我的理解是否正確:
編碼格式的程式碼
int show_codecs(void *optctx, const char *opt, const char *arg){ const AVCodecDescriptor **codecs; unsigned i, nb_codecs = get_codecs_sorted(&codecs); 。。。 省略非重要程式碼 。。。}static unsigned get_codecs_sorted(const AVCodecDescriptor ***rcodecs){ 。。。 省略非重要程式碼 。。。 while ((desc = avcodec_descriptor_next(desc))) nb_codecs++; 。。。 省略非重要程式碼 。。。}const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev){ if (!prev) return &codec_descriptors[0]; 。。。 省略非重要程式碼 。。。}
由程式碼可以看出,編碼格式都存放到了 codec_description 的全域性變數中。
static const AVCodecDescriptor codec_descriptors[] = { /* video codecs */ { 。id = AV_CODEC_ID_MPEG1VIDEO, 。type = AVMEDIA_TYPE_VIDEO, 。name = ”mpeg1video“, 。long_name = NULL_IF_CONFIG_SMALL(”MPEG-1 video“), 。props = AV_CODEC_PROP_LOSSY | AV_CODEC_PROP_REORDER, }, 。。。 省略非重要程式碼 。。。}
編碼器的程式碼
int show_decoders(void *optctx, const char *opt, const char *arg){ print_codecs(0); return 0;}static void print_codecs(int encoder){ const AVCodecDescriptor **codecs; unsigned i, nb_codecs = get_codecs_sorted(&codecs); 。。。 省略非重要程式碼 。。。}static unsigned get_codecs_sorted(const AVCodecDescriptor ***rcodecs){ 。。。 省略非重要程式碼 。。。 while ((desc = avcodec_descriptor_next(desc))) nb_codecs++; 。。。 省略非重要程式碼 。。。}const AVCodecDescriptor *avcodec_descriptor_next(const AVCodecDescriptor *prev){ if (!prev) return &codec_descriptors[0]; 。。。 省略非重要程式碼 。。。}
這樣看,編碼器也存放在
codec_description
這個全域性變數中。
由此咱們可以得出,在 FFmpeg 中,編碼格式和編碼器是同一個東西。
背後的關係
不對呀,不對呀。 這和阮一峰老師的結論不一致呀?難道阮一峰老師的解釋有問題嗎?
阮老師是沒有問題。咱們再來更深入地檢視一下 FFmpeg 的程式碼。其實在 FFmpeg 中,編碼格式和編碼器被定義成了不同的結構
AVCodecDescriptor 為編碼格式
typedef struct AVCodecDescriptor { enum AVCodecID id; enum AVMediaType type; const char *name; const char *long_name; int props; const char *const *mime_types; const struct AVProfile *profiles;} AVCodecDescriptor;
AVCodec 為編碼器
typedef struct AVCodec { const char *name; const char *long_name; enum AVMediaType type; enum AVCodecID id; int capabilities; uint8_t max_lowres; const AVRational *supported_framerates; const enum AVPixelFormat *pix_fmts; const int *supported_samplerates; const enum AVSampleFormat *sample_fmts; const uint64_t *channel_layouts; const AVClass *priv_class; const AVProfile *profiles; const char *wrapper_name; int caps_internal; int priv_data_size; int (*update_thread_context)(struct AVCodecContext *dst, const struct AVCodecContext *src); int (*update_thread_context_for_user)(struct AVCodecContext *dst, const struct AVCodecContext *src); const AVCodecDefault *defaults; void (*init_static_data)(struct AVCodec *codec); int (*init)(struct AVCodecContext *); int (*encode_sub)(struct AVCodecContext *, uint8_t *buf, int buf_size, const struct AVSubtitle *sub); int (*encode2)(struct AVCodecContext *avctx, struct AVPacket *avpkt, const struct AVFrame *frame, int *got_packet_ptr); int (*decode)(struct AVCodecContext *avctx, void *outdata, int *got_frame_ptr, struct AVPacket *avpkt); int (*close)(struct AVCodecContext *); int (*receive_packet)(struct AVCodecContext *avctx, struct AVPacket *avpkt); int (*receive_frame)(struct AVCodecContext *avctx, struct AVFrame *frame); void (*flush)(struct AVCodecContext *); const char *bsfs; const struct AVCodecHWConfigInternal *const *hw_configs; const uint32_t *codec_tags;} AVCodec;
只不過,編碼格式和編碼器是一對一的關係,FFmpeg 透過 AVCodecID id 來建立它們的關聯。
總結
這樣來看,編碼格式和編碼器其實是一個概念的兩種呈現方式:
編碼格式負責形,編碼器負責體
形體共同作用,完成音影片的編解碼工作。