ffmpeg入門篇-濾鏡的基本使用

轉發自白狼棧:查看原文

濾鏡

什麼是濾鏡?百度百科介紹說「濾鏡主要是用來實現圖像的各種特殊效果……」。

我們最早在ffmpeg是如何轉碼的一文中了解過濾鏡,來回顧下當時的轉碼流程圖。

從圖中可以看到濾鏡前後畫的是虛線,表示可有可無,在術語中,濾鏡指的是在編碼之前針對解碼器解碼出來的原始數據(即音視頻幀)進行處理的動作,我們還可以稱它為過濾器。

ffmpeg內置了大概近400種濾鏡,我們可以用 ffmpeg -filters 命令查看所有的濾鏡,也可以用命令 ffmpeg -h filter=xxx 或者查看官方文檔了解每一種濾鏡。

實際在大部分音視頻的處理過程中都離不開濾鏡,所以你應該能明白其重要性。

多個濾鏡可以結合在一起使用形成濾鏡鏈或者濾鏡圖,在每一個濾鏡中,不僅可以對輸入源進行處理,A濾鏡處理好的結果還可以作為B濾鏡的輸入參數,通過B濾鏡繼續處理。

針對濾鏡的處理,ffmpeg提供了兩種處理方式,簡單濾鏡和複雜濾鏡。

簡單濾鏡

簡單濾鏡指的是只有一個輸入和輸出,而且保證輸入和輸出的流類型相同。

比如我們在上篇文章流的操作(二)如何選擇流?末尾提到的把原視頻 r3.mp4 等比例縮放一倍

ffmpeg -i r3.mp4 -vf scale=272:480 -y filter.mp4

 

-vf 是 -filter:v 的簡寫,類似的我們還可以使用 -filter:a 或者 -af 針對音頻流做處理。

-filter的語法規則:-filter[:stream_specifier] filtergraph (output,per-stream)
stream_specifier流的類型我們一般用a表示音頻,v表示視頻,filtergraph表示具體的濾鏡,這裡用的是scale濾鏡

scale濾鏡用於調整視頻的大小,比如等比例縮放、等比例放大,不做等比例操作輸出就變形了,變形結果我們一般不考慮。

因為我們知道原視頻 r1ori.mp4 的分辨率是 544×960,所以等比例縮放一倍,上面的命令直接指定了 272×480,scale濾鏡自帶很多參數,我們介紹幾個常用的。

in_w in_h 或者 iw ih 表示輸入視頻的寬高
out_w out_h 或者 ow oh 表示輸出視頻的寬高

當然不一定是視頻,輸入輸出也可以是圖片。

所以原視頻縮放一倍我們還可以這樣寫:

ffmpeg -i r3.mp4 -vf scale=iw/2:ih/2 -y filter.mp4

 

問題一:如果我們要把原視頻的寬度調整為300且保持原分辨率,怎麼辦?

列一個方程 544/960 = 300/x ,x=300×960/540,很麻煩,結果還不一定能整除,為此我們可以直接指定高度等於-1,它會自動做等比例處理。

ffmpeg -i r1ori.mp4 -vf scale=300:-1 -y filter.mp4

 

結果發現轉碼失敗了,提示

[libx264 @ 0x7ff509053a00] height not divisible by 2 (300x529) 
Error initializing output stream 0:0 -- 
Error while opening encoder for output stream #0:0 - 
maybe incorrect parameters such as bit_rate, rate, width or height [aac @ 0x7ff50904e200] 
Qavg: 28010.410 [aac @ 0x7ff50904e200] 2 frames left in the queue on closing

 

提示我們 height not divisible by 2 (300×529)即高度529不能被2整除。這是因為一些編解碼器要求很多視頻的寬高必須是n的倍數(這裡n是2),所以我們寫腳本處理視頻或者圖片寬高的時候,切記不要使用-1,正確的用法是使用-2。

ffmpeg -i r1ori.mp4 -vf scale=300:-2 -y filter.mp4 輸出結果視頻的分辨率是 300 × 530

  

問題二:老闆為了刁難你,提出了一個新的要求:「我想要所有輸出視頻的分辨率是 300×500且不能變形」,怎麼辦?

我們知道3:5的寬高比是很少見的,現在常見的分辨率是16:9、4:3,也就是說原視頻我們必須要經過一番處理才可以滿足老闆的變態需求。

針對原視頻 r1ori.mp4,如果保證寬度是300,等比例縮放後高度是530,強制設置高度為500就會變形,也就是說我們只能讓高度等於500,盡量縮小寬度試試。

ffmpeg -i r1ori.mp4 -vf scale=-2:500 -y filter.mp4 輸出的結果視頻的分辨率是284x500

如上圖,藍色框表示視頻的真實寬高,紅色框表示目標寬高,有些像html中的css一樣,可以給空出來的部分填充顏色即內邊距不就可以了?

查閱了文檔我們發現pad濾鏡可以解決我們的問題。

pad濾鏡的語法規則:-pad=width[:height[:x[:y[:color]]]]

1、ffmpeg -i r1ori.mp4 -vf "scale=-2:500,pad=300:500:(300-iw)/2:0" -y filter2.mp4 
2、ffmpeg -i r1ori.mp4 -vf scale=-2:500,pad=300:500:-1:0 -y filter.mp4 
3、ffmpeg -i r1ori.mp4 -vf scale=-2:500,pad=300:500:-1:0:black -y filter.mp4 
4、ffmpeg -i r1ori.mp4 -vf "scale=-2:500,pad=300:ih:(ow-iw)/2:0:green" -y filter.mp4

 

上面提供4中寫法,我們以方法4做個簡單介紹。

scale=-2:500,指原視頻按照等比例縮放,高度等於500,就是上面大家看到的284×500。

pad=300:ih:(ow-iw)/2:0:green,300:ih即300:500就是紅色框的寬高(ow-iw)/2,指的是紅色框和藍色框差值的一半,即兩邊各需要填充的範圍;最後一個參數表示需要填充的顏色,默認是黑色 black,為了調試方便我們把顏色設為green。

現在我們保證了當前視頻一定會按照300×500的比例輸出且不會變形,但是請注意老闆說的「所有輸出視頻」,也就是說輸入視頻的分辨率可能是200×300、544×960、500×400、200×800等等各種比例都要保證按照300×500輸出,很顯然,上面的寫法不完全通用,怎麼辦?

現在我們已知原輸入視頻的寬高和想要的寬高,針對這種情況,我們制定一套處理規則即可解決:

  1. 寬高都偏小,不拉伸,不縮放
  2. 寬高都偏大,等比例縮小,以高度為準
  3. 寬超出範圍,等比例縮小,以寬為準
  4. 高超出範圍,等比例縮小,以高為準

在實際的開發過程中,我們要跟代碼打交道,平時在命令行中的實現都是練習,所以基於該規則,我們有了下面一段代碼

<?php
declare(strict_types=1);

class CalculatorService
{
    /**
     * 用戶視頻分辨率轉換
     * 規則:
     *  寬高都偏小,不拉伸,不縮放
     *  寬高都偏大,等比例縮小,以高度為準
     *  寬超出範圍,等比例縮小,以寬為準
     *  高超出範圍,等比例縮小,以高為準
     * @param int $inputWidth 輸入視頻的寬度
     * @param int $inputHeight 輸入視頻的高度
     * @param int $outWidth 輸出視頻的寬高
     * @param int $outHeight 輸出視頻的高度
     * @return string scale
     */
    public function getSize(int $inputWidth, int $inputHeight, int $outWidth, int $outHeight): string
    {
        $scale = "";
        if ($inputWidth <= $outWidth && $inputHeight <= $outHeight) {
            $scale = "scale={$inputWidth}:{$inputHeight},pad={$outWidth}:{$outHeight}:-1:-1:green";
        } elseif (($inputWidth > $outWidth && $inputHeight > $outHeight)
            || ($inputHeight > $outHeight)
        ) {
            $scale = "scale=-2:{$outHeight},pad={$outWidth}:{$outHeight}:-1:0:green";
        } elseif ($inputWidth > $outWidth) {
            $scale = "scale={$outWidth}:-2,pad={$outWidth}:{$outHeight}:0:-1:green";
        }

        return $scale;
    }
}

$calculatorService = new CalculatorService();
var_dump($calculatorService->getSize(200, 300, 300, 500));
var_dump($calculatorService->getSize(544, 960, 300, 500));
var_dump($calculatorService->getSize(500, 400, 300, 500));
var_dump($calculatorService->getSize(200, 600, 300, 500));

// 結果
string(37) "scale=200:300,pad=300:500:-1:-1:green"
string(35) "scale=-2:500,pad=300:500:-1:0:green"
string(35) "scale=300:-2,pad=300:500:0:-1:green"
string(35) "scale=-2:500,pad=300:500:-1:0:green"

 

為了方便理解,大家可以參考下面的圖一一對應。

複雜濾鏡

相對於簡單濾鏡,複雜濾鏡是可以處理任意數量輸入和輸出效果的濾鏡圖,它幾乎無所不能。

複雜濾鏡用命令 -filter_complex 表示,它還有一個別名 -lavfi。

上篇文章介紹到流和濾鏡結合是一種最重要、最常用的方法。依然是將輸入視頻 r3.mp4 等比例縮放一倍,我們以手動選擇流的方式為例。

ffmpeg -i r3.mp4 -filter_complex "[0]scale=272:480[out]" -map 0:a -map "[out]" -y filter.mp4

 

簡單分析如下:

  1. 命令 “[0]scale=272:480[out]” 中的[0]表示第一個輸入的視頻,因為要對視頻做處理,所以也可以用[0:v]表示,如果要對音頻單獨處理,就需要用 [0:a] 了;
  2. [0] 結合scale濾鏡,表示的就是把第一個輸入的視頻作為scale濾鏡的參數輸入;
  3. [out] 中括號是必須要的,out是自定義的一個別名,結合scale濾鏡,表示的是把scale濾鏡輸出的結果命名為[out],但並非是最終輸出的結果,只能作為中間過程輸出的一個結果;
  4. -map “[out]” 就是直接選擇[out] 流作為輸出

我們說過,一個濾鏡的輸出作為另一個濾鏡的輸入,這樣就極大的避免了寫多條命令反覆編解碼操作,我們的原則只有一個,能用一條命令處理的絕不用兩條命令。

有損編解碼器反覆編解碼操作會降低原視頻質量。

比如現在要把原視頻 r1ori.mp4 的中間部分裁剪出來,但仍保持原視頻的分辨率544×960,如何做呢?

ffmpeg -i r1ori.mp4 -filter_complex "nullsrc=s=544x960[background]; \
crop=iw:(ih/2 - 110):0:250[middle]; \
[background][middle]overlay=shortest=1:x=(main_w-overlay_w)/2:y=(main_h-overlay_h)/2[out]" \
-map "[out]" 
-map 0:a 
-movflags +faststart 
-y fc.mp4

 

這個命令就顯得稍微長了一些,在這條命令中使用了nullsrccropoverlay三種常見濾鏡。

nullsrc濾鏡用於創建一個空的視頻,簡單的說就是一個空的畫布或者說是綠布,因為默認創建的顏色是綠色的。s用於指定畫布的大小,默認是320×240,這裡表示我們創建一個544×960的畫布,並命名為background;

關於nullsrc還有很多種不同的用戶,比如使用nullsrc和CIQRCodeGenerator創建一個「白狼棧」首頁的二維碼

ffmpeg -f lavfi -i nullsrc=s=200x200,coreimage=filter=CIQRCodeGenerator@inputMessage=\ 
http\\\\\://manks.top/@inputCorrectionLevel=H -frames:v 1 manks.png

 

crop濾鏡用於裁剪視頻,也就是說視頻的任意區域任意大小,我們都可以裁剪出來。crop=iw:(ih/2 – 110):0:250[middle]; 這裡我們裁剪原視頻的中間部分並命名為middle;

overlay濾鏡表示兩個視頻相互疊加,shortest官網是這麼介紹的:「If set to 1, force the output to terminate when the shortest input terminates. Default value is 0.」,因為我們使用nullsrc創建了一個沒有時間軸的畫布,所以這裡需要以middle的視頻時間為最終時間,故設置為1。main_w和main_h表示主視頻的寬高,overlay_w和overlay_h表示疊加視頻的寬高。如果要把A視頻疊加到B視頻上,則main_w和main_h表示B視頻的寬高,overlay_w和overlay_h表示A視頻的寬高。合起來便是把middle疊加到background之上且置於background的中間(相當於有個疊加層的概念);

最後一個參數是-movflags,它跟mp4的元數據有關,設為faststart表示會將moov移動到mdat的前面,在線播放的時候會稍微快一些。

作業:我們在音視頻合成案例一文中介紹了兩個案例,快去試試你能不能一條命令解決?

關於濾鏡的基本介紹我們就介紹到這裡,有任何問題可以下方留言。