FFmpeg開發筆記(二十二)FFmpeg中SAR與DAR的顯示寬高比

aqi00發表於2024-05-19
《FFmpeg開發實戰:從零基礎到短影片上線》一書提到:通常情況下,在影片流解析之後,從AVCodecContext結構得到的寬高就是影片畫面的寬高。然而有的影片檔案並非如此,如果按照AVCodecContext設定的寬高展示影片,會發現畫面被壓扁或者拉長了。比如該書第10章原始碼playsync.c在播放meg.vob時的影片畫面如下圖所示:

可見按照現有方式展示的話,影片畫面被拉長了。這是因為影片尺寸有三種寬高概念,說明如下:

1、取樣寬高比,指的是攝像頭在採集畫面時,方格內部的寬度與高度的取樣點數量比例。取樣寬高比的英文叫做“Sample Aspect Ratio”,簡稱SAR。
2、畫素寬高比,指的是影片畫面儲存到檔案時,寬度和高度各佔據多少畫素。畫素寬高比的英文叫做“Pixel Aspect Ratio”,簡稱PAR。
3、顯示寬高比,指的是影片畫面渲染到螢幕時,顯示出來的寬度與高度比例。顯示寬高比的英文叫做“Display Aspect Ratio”,簡稱DAR。
取樣寬高比對應AVCodecParameters結構的sample_aspect_ratio欄位,該欄位為分數型別AVRational。
畫素寬高比對應AVCodecContext結構的width與height兩個欄位,比例值等於width/height。
顯示寬高比對應最終要顯示的畫面尺寸,該值需要額外計算。多數時候sample_aspect_ratio的num與den均為1,表示寬高兩個方向的取樣點比例為1:1,此時畫素寬高比等於顯示寬高比。
由此可見,當sample_aspect_ratio的num與den均為1時,表示畫素點是個正方形,此時AVCodecContext結構的寬高就是影片的寬高,無需另外處理。只有sample_aspect_ratio的num不等於den時,表示畫素點是個長方形,才需要另外計算顯示寬高比,並根據影片高度計算影片的實際寬度。
已知三個寬高比的轉換式子如下:

DAR = PAR x SAR

令DAR=實際寬度/實際高度,則代入具體的欄位,可得詳細的轉換式子如下:

實際寬度   width    sample_aspect_ratio.num
——————— = —————— X —————————————————————————
實際高度   height   sample_aspect_ratio.den

當實際高度為height時,表示保持原畫面尺寸,則實際的畫面寬度計算式子如下。

             sample_aspect_ratio.num
實際寬度 = width X —————————————————————————
             sample_aspect_ratio.den

假如已經求得DAR值並儲存在變數display_aspect_ratio中,那麼實際寬度 = 實際高度 * PAR = 實際高度 * display_aspect_ratio.num / display_aspect_ratio.den。

根據上述所列的幾個計算式子,編寫如下的寬高比以及實際寬度的求解程式碼如下所示。

int origin_width = video_decode_ctx->width;
int origin_height = video_decode_ctx->height;
AVRational aspect_ratio = src_video->codecpar->sample_aspect_ratio;
AVRational display_aspect_ratio;
av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
          origin_width  * aspect_ratio.num,
          origin_height * aspect_ratio.den,
          1024 * 1024);
av_log(NULL, AV_LOG_INFO, "origin size is %dx%d, SAR %d:%d, DAR %d:%d\n",
       origin_width, origin_height,
       aspect_ratio.num, aspect_ratio.den,
       display_aspect_ratio.num, display_aspect_ratio.den);
int real_width = origin_width;
// 第一種方式:根據SAR的取樣寬高比,由原始的寬度算出實際的寬度
if (aspect_ratio.num!=0 && aspect_ratio.den!=0 && aspect_ratio.num!=aspect_ratio.den) {
    real_width = origin_width * aspect_ratio.num / aspect_ratio.den;
}
int target_height = 270;
int target_width = target_height*origin_width/origin_height;
// 第二種方式:根據DAR的顯示寬高比,由目標的高度算出目標的寬度
if (aspect_ratio.num!=0 && aspect_ratio.den!=0 && aspect_ratio.num!=aspect_ratio.den) {
    target_width = target_height * display_aspect_ratio.num / display_aspect_ratio.den;
}
av_log(NULL, AV_LOG_INFO, "real size is %dx%d, target_width=%d, target_height=%d\n",
    real_width, origin_height, target_width, target_height);

上述修改後的程式碼已經附在了《FFmpeg開發實戰:從零基礎到短影片上線》一書第10章的原始碼chapter10/playsync2.c中,這個c程式碼是playsync.c的改進版,能夠根據sample_aspect_ratio的寬高比例調整目標影片的畫面尺寸。
接著執行下面的編譯命令。

gcc playsync2.c -o playsync2 -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -I/usr/local/sdl2/include -L/usr/local/sdl2/lib -lsdl2 -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lpostproc -lm

編譯完成後執行以下命令啟動測試程式,期望播放影片檔案meg.vob。

./playsync2 ../meg.vob

程式執行完畢,發現控制檯輸出以下的日誌資訊。

Success open input_file ../meg.vob.
origin size is 720x576, SAR 64:45, DAR 16:9
real size is 1024x576, target_width=480, target_height=270
……

同時彈出SDL視窗播放影片畫面,如下圖所示:

可見畫面尺寸符合該影片的實際寬高比例,表示上述程式碼正確實現了調整影片尺寸的功能。

相關文章