# [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`