# [FFmpeg] 調整解析度並維持顯示比例 ## 顯示比例、解析度、像素寬比 - Width: 寬度,即橫軸像素數量 - Height: 高度,即縱軸像素數量 - Display Aspect Ratio (DAR) : 顯示寬高比例 - Pixel Aspect Ratio (PAR): 像素寬高比例 - Sample Aspect Ratio (SAR): 取樣寬高比例 等同 PAR - Source Aspect Ratio (SAR): 原始寬高比例 即 Width/Height **顯示比例關係公式** ```math DisplayAspectRatio = Width / Height * PixelAspectRatio ``` ## 常見標準解析度 | 名稱 | 規格 | | ----------------- | -------------------------------- | | UHD, 4K | 3840x2160 [PAR 1:1, DAR 16:9] | | Full HD, HD 1080 | 1920x1080 [PAR 1:1, DAR 16:9] | | HD 1080 | 1440x1080 [PAR 4:3, DAR 16:9] | | HD 720 | 1280x720 [PAR 1:1, DAR 16:9] | | Full D1 NTSC 16:9 | 720x480 [PAR 40:33, DAR 20:11] | | Full D1 NTSC 4:3 | 720x480 [PAR 10:11, DAR 45:33] | | Full D1 PAL 16:9 | 720x576 [PAR 16:11, DAR 20:11] | | Full D1 PAL 4:3 | 720x576 [PAR 12:11, DAR 45:33] | | D1 NTSC 16:9 | 704x480 [PAR 40:33, DAR 16:9] | | D1 NTSC 4:3 | 704x480 [PAR 10:11, DAR 4:3] | | D1 PAL 16:9 | 704x576 [PAR 16:11, DAR 16:9] | | D1 PAL 4:3 | 704x576 [PAR 12:11, DAR 4:3] | 由以上可得知,處理 Full D1 片源時必須左右各裁切8像素寬度並強制顯示寬高比例 16:9 或 4:3,才可以得到正確沒有變形錯誤的畫面,前提是該片源確實遵守標準規範製作。DVDVideo 的標籤只有 16:9 與 4:3 兩種,播放器告訴你 DAR 16:9 與 PAR 32:27,那也是根據解析度與寬高比例標籤反推回來的,無法得知該片源是否依照標準製作,因為差異不大所以直接視為 16:9 即可。 常見標準比例為 4:3, 16:9, 2.39:1 (電影),由此可知若將 16:9 的電影裁去黑邊後應該會接近 2.39:1。而影片的 PAR 通常為 1:1 (正方形像素),部分影片的 並不是正方形像素,例如 某些 HDTV 就是使用的解析度為 1440x1080 [PAR 4:3, DAR 16:9]。 ## 非正方形像素影片與 PC 顯示器 非正方形象素可以節省大量資料量,以及降低播放端需求 - 影片1: 1920x1080 [PAR 1:1, DAR 16:9] 像素數量 (畫素): 2073600 - 影片2: 1440x1080 [PAR 4:3, DAR 16:9] 像素數量 (畫素): 1555200 如果顯示器是1920x1080, PAR 1:1 (正方形像素,PC 標準) 因為,螢幕是正方形像素,則播放影片2時會自動插補像素至 1920x1080 輸出以保持 DAR 為16:9,因為直接輸出為 1440x1080 會得到錯誤 DAR。 ## 調整解析度並維持顯示比例 令 DAR 不變 可以推導出以下公式 - 伸展 (Stretch): 指訂寬度、高度,令 DAR 不變,求 PAR $$ PAR = Height / Width * DAR $$ - 符合寬度 (Fit to Width): 指訂寬度、PAR,令 DAR 不變,求高度 $$ Height = Width * PAR / DAR $$ - 符合高度 (Fit to Height): 指訂高度、PAR,令 DAR 不變,求寬度 $$ Width = Height / PAR * DAR $$ - 符合寬度與高度 (Fit to Box): 指定PAR,限定高度寬度最大值,令 DAR 不變,求最大寬度與高度 $$ Width ≦ BoxWidth $$ $$ Height ≦ BoxHeight $$ $$ Width = Height / PAR * DAR $$ $$ Height = Width * PAR / DAR $$ 最大寬度與高度即: $$ Width = Min(BoxWidth, Height / PAR * DAR) $$ $$ Height = Min(BoxHeight, Width * PAR / DAR) $$ **範例:** 片源若為 1440x1080 [PAR 4:3, DAR 16:9],上下裁切各 140 像素高度,再將解析度寬縮放到 1280 (正方形像素)。 $$ DAR = 1440 / (1080 - 140 * 2) * 4 / 3 = 2.4 $$ $$ ScaleHeight = 1280 / 2.4 = 533.333... $$ 因為寬高必須是正偶數,所以取最接近的 533.3 偶數即 534。 如果輸入影片其實本身顯示比例就有誤差或是錯誤,這時你可以代入你認為正確的顯示比例來計算,例如 848x480 [PAR 1:1, DAR~= 1.76666:1],這可能是由於之前的縮放處裡造成比例變形錯誤,其原始 DAR 可能是16:9。 ### 2,4,8,16 modulus 寬與高並非只要是正整數都可以,通常為 mod16 x mod8 $$ NewNum = Fix(Num / Modulus + 0.5) * Modulus $$ 函數 Fix 功能為取整數(直接捨棄小數),例如 Fix(1.6) = 1。 所以 Fix(Num + 0.5) 功能為 Num 位四捨五入取整數,例如 Fix(1.6 + 0.5) = 2。 **範例:** 例如解析度 1280x533 修正為 mod16 x mod8 $$ Width = Fix(1280 / 16 + 0.5) * 16 ~= Fix(80.5) * 16 = 80 * 16 = 1280 $$ $$ Height = Fix(533 / 8 + 0.5) * 8 ~= Fix(67.125) * 8 = 67 * 8 = 536 $$ ### 變形錯誤率 由於寬高度取 2 or 4 or 8 or 16 倍數,將可能導致縮放前後 DAR 產生差異,使用以下公式即可求得變形錯誤率,若計算結果越小表示變形程度越低。通常控制縮放變形錯誤率在可接受範圍內即可,-1% ~ +1% 為理想範圍。 $$ DAR_Error = (InDAR - OutDAR) / InDAR * 100\% $$ **範例:** 假設縮放前後的 DAR 是 2.39 與 2.338 $$ DAR_Error ~= (2.39 - 2.338) / 2.39 * 100\% ~= 0.08\% $$ ## FFmpeg 篩選器說明 `crop`, `scale`, `pad` 可用參數: * ‘w’: 設定目標(裁切/縮放/填充後的)寬度。 * ‘h’: 設定目標(裁切/縮放/填充後的)高度。 crop 可用參數: * ‘x’: 設定左裁切/填充寬度。 * ‘y’: 設定上裁切/填充高度。 setsar 可用參數: * ‘r’: 設定目標 SAR (PAR)。 setdar 可用參數: * ‘r’: 設定目標 DAR。 ### 基本用法 ```powershell ffmpeg -i INPUT -vf "FILTER_GRAPHS" OUTPUT ``` **範例:** 片源: input.m2ts, 1920x1080 [PAR 1:1, DAR 16:9]。 上下裁切 140 像素高度然後縮放到 1280x534 並填充上黑邊 92、下黑邊 94。 篩選器流程圖 (Filter Graphs): ```powershell crop='w=1920:h=800:y=140',scale='w=1280:h=534',pad='w=1280:h=720:y=92' ``` 完整命令: ```powershell ffmpeg -i input.m2ts -vf "crop='w=1920:h=800:y=140',scale='w=1280:h=534',pad='w=1280:h=720:y=92'" output.mp4 ``` ### SAR (PAR) 若使用了 `scale` 篩選器來改變了解析度的寬或高,FFmpeg 會自動調整輸出影像的 SAR (PAR) 來維持顯示比例與輸入相同。若想要得到正方形像素 (PAR 1:1),不介意那些微的顯示比例失真,可以使用 `setsar` 篩選器強制設定 SAR 為 1:1。 **範例:** 片源:1920x1080 [PAR 1:1, DAR 16:9] 篩選器流程圖 (Filter Graphs): ```powershell crop=1920:800:0:140,scale=1280:536 ``` $$ DAR=1920/800*(1/1)=12/5=2.4 $$ $$ PAR=12/5/(1280/536)=201/200=1.005 $$ 輸出:1280x536 [PAR 201:200, DAR 12:5] 強制最終輸出影像為正方形像素: ```powershell crop=1920:800:0:140,scale=1280:536,setsar=1/1 ``` $$ DAR=1280/536*(1/1)=160/67≒2.4$$ 輸出: 1280x536 [PAR 1:1, DAR ~2.388:1] ### 自動寬或高 在 `scale` 篩選器中將寬或高其中之一設為負數,FFmpeg 將會並自動計算出維持顯示比例所需要的值 (輸出強制為方形像素),而此值將會被指定數值所整除。 **範例:** 指定縮放寬度,自動高度並維持顯示比例: ```powershell scale=1280:-1 ``` 承上,當輸入影像的 DAR 為 2.4:1 時,FFmpeg 會令輸出影像高度為 533 來維持 DAR 不變。如果要確保所得到的值能夠被 n 所整除,必須使用 -n 作為設定值。例如使用 -16 會"向上"取最接近的 16 的倍數,也就是說會取 544 而不是最接近的 528。 將影片寬度縮小到 1280 ,自動高度維持顯示比例 (高度取能被 16 整除的數): ```powershell scale=1280:-16 ``` ### 篩選器可用變數 `crop`, `scale`, `pad` 視訊篩選器可用變數 * ‘iw’: 輸入影像的寬度。 * ‘ih’: 輸入影像的高度。 * ‘sar’: 輸入影像的像素寬高比(PAR)。 * ‘dar’: 輸入影像的顯示寬高比例。 * ‘ow’: 輸出影像的寬度(也就是 w 所得到的值)。 * ‘oh’: 輸出影像的高度(也就是 h 所得到的值)。 **範例:** 半寬,高度與 DAR 不變: ```powershell scale='w=iw/2:h=ih' ``` 說明: 假設片源是 800x300 (PAR 1:1, DAR 8:3): 在 `scale` 中 `iw`、`ih`、`dar` 代表 800、300、8/3 $$ w=iw/2=800/2=400 $$ $$ h=ih=300 $$ 縮放後 FFmpeg 會自動改變 PAR(SAR) 來維持 DAR。 $$ dar = w/h*sar = 400/300 * sar = 8/3 $$ $$ sar = 8/3*300/400 = 2 $$ 最終會輸出: 400x300 [PAR 2:1, DAR 8:3] 使用 `ow` 與 `oh` 代表 `w` 與 `h` 的值: ```powershell scale='w=iw/2:h=ih',setsar='r=1/1',pad='w=iw+32:h=ih+16:x=(ow-iw)/2:y=(oh-ih)/2' ``` 說明: 假設片源是 800x300 (PAR 1:1, DAR 8:3): 在 `scale` 中 `iw`、`ih`、`dar` 代表 800、300、8/3 在 `pad` 中 `iw`、`ih`、`dar`、`ow`、`oh` 代表 400、300、4/3、432、316 最終會輸出: 432x316 [PAR 1:1, DAR 432:316] ### 自訂計算公式 在 `crop`, `scale`, `pad` filter 中 `w`、`h`、`x`、`y` 參數允許使用者使用自定計算公式作為設定值。 將影片寬度縮小到 1280,自動高度維持顯示比例 (高度取最接近的 8 的倍數): ```powershell scale=1280:'trunc(ow/dar/8+0.5)*8' ``` trunc(x): 對 x 取整數。 trunc(x+0.5): 對 x 四捨五入取整數。 trunc(x/16+0.5)*16: 取最接近x的16倍數。 承上,當寬度超過 1280 時縮放: ```powershell scale='min(1280,trunc(iw/8+0.5)*8):trunc(ow/dar/8+0.5)*8' ``` min(x, y): 回傳數值較大者。例如: min(10,100) = 100 ## 應用範例 使用 FFmpeg 做調整解析度時,不需要人工計算,只要賦予參數一個方程式即可。 ### Stretch 寬度 = 720, 高度 = 480, 自動 PAR 令 DAR 不變: ```powershell scale='w=720:h=480' ``` ### Fit to Width 寬度 = 1280, PAR = 1:1,維持 DAR,方程式求高度 (mod8): ```powershell scale='w=1280:h=trunc(ow/dar/8+0.5)*8',setsar='r=1/1' ``` ### Loose 承上,但微調 PAR 使 DAR_Error 為 0: ```powershell scale='w=1280:h=trunc(ow/dar/8+0.5)*8' ``` ### Fit to Height 高度 = 720, PAR = 1:1,維持 DAR,方程式求寬度 (mod16): ```powershell scale='w=trunc(oh*dar/16+0.5)*16:h=720',setsar='r=1/1' ``` ### Fit to Box 限制寬高小於等於1280x720 且 PAR 1:1 ,維持 DAR,方程式求寬度與高度 (mod2): ```powershell scale='w=min(1280,trunc(720*dar/2+0.5)*2):h=min(720,trunc(1280/dar/2+0.5)*2)',setsar='r=1/1' ``` ### Letterbox 限制寬高小於等於720x480 且 PAR = 40:33 ,維持 DAR,方程式求縮放寬度與縮放高度 (mod2) 再填充黑邊將縮放後的影像寬高擴展到720x480: ```powershell scale='w=min(720,trunc(480*33/40*dar/2+0.5)*2):h=min(480,trunc(720*40/33/dar/2+0.5)*2)',pad='w=720:h=480:x=(ow-iw)/2:y=(oh-ih)/2',setsar='r=40/33' ``` 承上,先填充黑邊再縮放: ```powershell pad='w=max(iw,trunc(ih/sar*720/480*40/33/2+0.5)*2):h=max(ih,trunc(iw*sar*480/720*33/40/2+0.5)*2):x=(ow-iw)/2:y=(oh-ih)/2',scale='w=720:h=480',setsar='r=40/33' ``` 承上,令左右邊界留 8 像素為不可用區域: ```powershell scale='w=min(704,trunc(480*33/40*dar/2+0.5)*2):h=min(480,trunc(704*40/33/dar/2+0.5)*2)',pad='w=720:h=480:x=(ow-iw)/2:y=(oh-ih)/2',setsar='r=40/33' ``` ###### tags: `ffmpeg`