編碼格式與編碼器的困惑

問題的起源

在學習音影片的知識時,一直對 “編碼格式” 和“編碼器”兩個概念有疑惑,總是感覺它們是同一個東西。今天在讀阮一峰的《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 來建立它們的關聯。

編碼格式與編碼器的困惑

總結

這樣來看,編碼格式和編碼器其實是一個概念的兩種呈現方式:

編碼格式負責形,編碼器負責體

形體共同作用,完成音影片的編解碼工作。