音影片編解碼 -- 編碼引數 CRF

語言: CN / TW / HK

之前多多少少接觸過一些編解碼引數,CRF 引數也用過,但是最近在和朋友們聊天時,說到使用 FFMPEG 過程中碰到 CRF 引數,以及具體作用流程,這個之前一直沒有跟蹤過,也沒有詳細記錄過,所以吊起了自己的好奇心,於是決定搞清楚一下,便開始了這次 CRF 的神奇之旅。CRF 簡介:

恆定速率因子(CRF,Constant Rate Factor)是一種編碼模式,可以向上或向下調整檔案資料速率以達到選定的質量級別,而不是特定的資料速率。

如果要保持最佳質量,而又不怎麼擔心檔案大小,這時候就可以使用 CRF 速率控制模式。 這是大多數情況下建議的速率控制模式。當輸出檔案的大小不太重要時,此方法允許編碼器嘗試為整個檔案實現期望目標影片質量的檔案輸出,即所謂的一次編碼便可在預期影片質量下獲得最大的影片壓縮效率。CRF 模式主要原理是在編碼過程中通過動態調整每幀影片的 QP 值,以便可以獲得保持所需影片質量水平位元率。

但是 CRF 缺點是不能告知編碼器期望獲得特定大小的檔案或不超過特定大小或位元率。同時需要注意的是採用 CRF 時不建議直接用來編碼影片以進行流媒體傳輸。

通常建議一般使用兩種速率控制模式:恆定速率因子(CRF)或 2-pass ABR。 速率控制決定每個幀將使用多少位。 這將確定檔案大小以及質量分配方式。CRF 實操演示

通過 FFMPEG 二進位制檔案嘗試用引數 CRF 進行壓縮,如下圖所示:

FFMPEG 採用 CRF 分別為 18、24 進行壓縮,以及和原始檔的比較。

ffmpeg -i test.mp4 -c:v libx264 -crf 18 test18.mp4

實際轉碼中

轉碼結束後,會顯示具體的編碼相關資訊,包括 ref,crf 值,qp 量化步長等,以及 I 幀、P 幀、B 幀所佔比重。還包含了音訊相關資訊如下圖:

用命令 ffmpeg -i test.mp4 -c:v libx264 -crf 24 test24.mp4,進行 CRF=24 的轉碼,轉碼結果如下圖所示:

轉碼後分別對三個檔案進行引數檢視,並形成對比,其結果如下圖所示:

上述引數只能大概瞭解三個影片基本資訊,之後通過 Elecard eye 專業工具檢視該變化產生原因的直觀圖,三個檔案碼流分析結果:

三個檔案對比情況總結如下:

可以看出:CRF 引數的使用,I 幀數量急劇減少、同時引入 B 幀;熵編碼採用了 CABAC 方式,這樣壓縮率就提升很多,檔案大小變小。同時隨著 CRF 值變大,P 幀和 B 幀壓縮率也變大,檔案更小。CRF 程式碼走讀

雖然之前走讀過 FFMPEG 程式碼,但是具體 CRF 引數的品讀還沒完全注意到過。為了不是一知半解的明白該問題,還是強迫自己走一遍程式碼,增強印象,深刻認識,也為關心該引數的小夥伴鋪墊一下基礎。*•CRF 定義

首先在 X264 中可以看到該值的定義:

typedef struct X264Context {
    AVClass        *class;
    x264_param_t    params;
    ......

    float crf;

    ......
    }

在 AVOption 具體定義如下:

static const AVOption options[] = {
    { "preset",        "Set the encoding preset (cf. x264 --fullhelp)",   OFFSET(preset),        AV_OPT_TYPE_STRING, { .str = "medium" }, 0, 0, VE},
    { "tune",          "Tune the encoding params (cf. x264 --fullhelp)",  OFFSET(tune),          AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE},
    { "profile",       "Set profile restrictions (cf. x264 --fullhelp) ", OFFSET(profile),       AV_OPT_TYPE_STRING, { 0 }, 0, 0, VE},
......
    {"x264opts", "x264 options", OFFSET(x264opts), AV_OPT_TYPE_STRING, {.str=NULL}, 0, 0, VE},
    { "crf",           "Select the quality for constant quality mode",    OFFSET(crf),           AV_OPT_TYPE_FLOAT,  {.dbl = -1 }, -1, FLT_MAX, VE },
    { "crf_max",       "In CRF mode, prevents VBV from lowering quality beyond this point.",OFFSET(crf_max), AV_OPT_TYPE_FLOAT, {.dbl = -1 }, -1, FLT_MAX, VE },
......
}

CRF 仍然屬於 Rate control 的一中,所以可以看到其 RC 相關定義如下:

#define X264_RC_CQP                  0
#define X264_RC_CRF                  1
#define X264_RC_ABR                  2

•FFMPEG 介面梳理

涉及到 FFMPEG 程式碼走讀的部分太多了,在此只是簡述 CRF 對應的部分,其他編解碼流程大家可以根據網上其他大神的程式碼走讀流程完成即可。此篇文章預設大家有足夠基礎:X264 的編解碼入口符合 FFMPEG 介面定義,對應關係如下圖所示:

此處借用雷神的一張圖說明: (https://blog.csdn.net/leixiaohua1020/article/details/45960409)

  • X264_init()

X264_init 函式主要作用就是將之前賦值和初始化的 option 值依次傳遞到 libx264 模組中,進行 X264 引數初始化,以及 RC 引數賦值。這些值是從 AVCodecContext 傳遞過來,以及 X264Context 的預設值。熟悉 FFMPEG 的人都瞭解,AVCodecContext 中包含輸入命令列中編解碼選項值,以及 FFMPEG 命令中包含的 option 值,而 X264Context 包含 x264 的相關選項,兩者結合構成完整的 x264 編解碼選項值。

在 X264_init 的最後,進行 X264Codec 的 OPEN 動作,以及編碼全域性 header 的動作。

  • x264_param_default

x264_param_default 設定預設引數,包括其他的選項值,在此只關心 CRF 相關選項。x264_param_default 中將 CRF 預設開啟,同時設定 CRF 選項 f_rf_constant 置為 23,這也是其他很多文章中講到的預設值 23 的原因。

同時注意,觀察到在 x264_param_default 預設引數中 B 幀是再次設定並置位的,而且 cabac 預設開啟。所以如果用 FFMPEG bin 檔案進行轉碼出來的檔案中 cabac 是預設開啟的,這也是工具端檢視時會出現 CABAC 以及增加 B 幀的根本原因了。

  • x264_encoder_open

在初始化具體引數後,init 函式接下來進行 x264_encoder_open(相關程式碼位於 encoder\encoder.c)的操作,這時會具體開啟到 x264 中 h264 相關編碼器。

之後在 x264_encoder_open 中主要用於開啟編碼器,其中校驗、初始化了 libx264 編碼所需要的各種變數,並完成 sps、pps、qm 初始化。

  • validate_parameters

呼叫 validate_parameters 會進行輸入引數的校驗,防止輸入引數異常導致編碼失敗。此函式中完成 CRF 相關引數校驗、更新和賦值。

其他流程部分可以參考其他大神的文章,再次不再累述。(雷神的解析非常詳盡了,敬請膜拜即可 x264原始碼簡單分析:編碼器主幹部分-1_雷霄驊(leixiaohua1020)的專欄-CSDN部落格

  • x264_ratecontrol_new

x264_encoder_open 最後會呼叫 x264_ratecontrol_new 完成位元速率控制相關變數初始化。

x264_ratecontrol_new,主要設定位元速率控制的核心引數,需要對 x264 位元速率控制比較瞭解才能真正明白,否則會容易看暈。

x264_ratecontrol_new 函式中依據傳入引數是 CRF 模式,以及 b_stat_read 預設值為 0 即可將 b_abr 引數的置位為 1,同時 b_2pass 置位為 0,也就是說 CRF 模式在 rate_control 中按照 abr、非 2-pass 進行處理的。

在 x264_ratecontrol_init_reconfigurable 函式中會進行 VBV 引數初始化,以及 CRF 相關引數 base_cplx、rate_factor_constant 的更新。

同時 x264_ratecontrol_init_reconfigurable 中設定被呼叫時,傳入 b_init=1 的引數,這時 CRF 置位了 VBV 模式,為後續的 rate_control 做了鋪墊。

  • X264_frame

X264_frame()用於依據傳入 packet 資料進行一幀影片資料的完整編碼。該函式部分定義如下所示。

  • reconfig_encoder

reconfig_encoder 主要作用就是將 RC 相關的引數和 AVCodecContext 中引數進行比較,如果不一致,則重新配置編碼器。比如 CRF 值初始設定為 24,但是命令列中設定為 18,這時兩個值不一致,則需要按照命令列中值進行賦值並重新配置編碼器,以便最終符合使用者預期。具體配置大家簡單看一下就好,這裡不再展開。

  • x264_encoder_encode

x264_encoder_encode 是真正編碼的開始,在 x264_encoder_encode 這個函式裡面將一幀完整 YUV 影象編碼成 H264 影片流,這個過程可以參考雷神的文章,解析非常好, https://blog.csdn.net/leixiaohua1020/article/details/45644367

這邊關心的是 CRF 中涉及到的部分內容,在 x264_encoder_encode 中和位元速率控制相關的內容主要是一下介面:

x264_thread_sync_ratecontrol():

x264_ratecontrol_zone_init():

x264_ratecontrol_start():開啟位元速率控制,針對每一幀進行位元速率控制。在 x264_ratecontrol_start 中會根據位元速率控制模式的不同,選擇不同的 qp 進行壓縮。之前分析可知,CRF 是屬於 abr 模式,同時增加了 B 幀,所以導致每幀影象的 qp 都是不同的,這樣壓縮後相同質量的條件下編碼後文件大小就不能確定了。

x264_ratecontrol_qp():

位元速率控制是一個大塊內容,設計的演算法也比較複雜,該文只關注瞭如何將 crf 模式轉換到 vbv 模式,以及對影響編碼的部分引數,整個過程下一篇文章我們再進行分析和跟蹤。

以上是個人的一些看法,可能有不正確的地方,歡迎大家一起討論學習。

如果該文章對您有幫忙,歡迎點贊,收藏,轉發、關注,在下持續更新音影片相關內容。