1. 程式人生 > >FFmpeg 音視訊處理

FFmpeg 音視訊處理

FFmpeg是一個用於音視訊處理的自由軟體,被廣泛用於音視訊開發。FFmpeg功能強大,本文主要介紹如何使用FFmpeg命令列工具進行簡單的視訊處理。

安裝FFmpeg可以在官網下載各平臺軟體包或者靜態編譯版本,也可以使用包管理工具安裝。

基本概念

容器

我們熟悉的mp4,rmvb,mkv,avi是多媒體容器檔案格式(或稱多媒體封裝格式),所謂容器是指將不同的資料流(視訊流,音訊流,字幕流等)封裝在一個檔案(載體)中。

播放時各種流分別進行解碼等處理後,然後輸出到顯示器和音響等裝置進行播放。多媒體容器格式不同於編碼格式,一個容器中可以封裝多種編碼格式的媒體流。

流封裝了實際的媒體資料,如視訊流,音訊流和字幕流等。一般情況下,流中的資料只能使用一種編碼格式。

幀率

幀率(frames per second, fps)是每秒畫面重新整理的次數,幀率越高視訊越流暢。一般來說30fps就是可以接受的,60fps則可以明顯提升互動感和逼真感,但是一般超過75fps一般就不容易察覺到有明顯的流暢度提升了。

解析度

解析度表示畫面的精細程度,通常用畫素密度來表示,常用的單位為ppi(畫素每英寸)。通常畫素密度越高畫面越精細,模糊程度越低。

對於視訊檔案而言,畫素密度是無法控制的(由播放器和顯示裝置決定)。我們通常用視訊的畫素數來表示它的解析度如1080x640, 640x320等。

位元率

位元率(bit rate)又稱位元速率,表示多媒體流每秒輸出的位元組數,單位為KB/s, Kbps等。同樣的壓縮演算法下,位元率越高音視訊的質量越好。

可變位元速率(Variable Bitrate, VBR)指的是編碼器的輸出位元速率可以根據輸入源訊號的複雜度進行自適應調整,以在輸出質量保持不變的條件下儘可能減少資料量。VBR適用於儲存,不太適用流式傳輸。

固定位元速率(Constant Bitrate, CBR)指的是編碼器輸出位元速率固定,CBR不適合儲存,對於複雜內容可能沒有足夠位元速率進行編碼,從而導致質量下降,同時會在簡單內容部分浪費一些位元速率。

取樣率

每秒鐘對音訊訊號的取樣次數,取樣頻率越高聲音還原度越高,聲音更加自然,單位是赫茲 Hz。

音訊檔案一般使用的取樣率是 44.1 kHz,也就是一秒鐘取樣44100次,實驗發現低於這個值就會有較明顯的損失,而高於這個值人的耳朵已經很難分辨,而且增大了數字音訊所佔用的空間。

視訊編碼

視訊流可以看做圖片的序列,我們把這個序列中的一張圖片稱為一幀。若儲存視訊中所有幀則會資料量過大,不便於儲存和傳輸。

所幸統計表明大多數視訊相鄰幀之間的區別並不大,所以對於一段變化不大的視訊,我們可以先完整編碼幀A,其後的B幀只需要編碼與A幀不同的部分,B幀後的C幀則只編碼與B幀的差異。如此遞推,將一段視訊編碼為一個序列。

當某個影象與之前的影象變化很大無法參考前面的幀來生成,我們就結束上一個序列將該幀完整編碼開始一個新的序列。

H264是目前流行的一種視訊編碼演算法,它定義了三種幀:完整編碼的I幀,參考I幀生成只包含差異的P幀,以及以及參考前後幀編碼的B幀。

H264採用的核心演算法是幀內壓縮和幀間壓縮,幀內壓縮是生成I幀的演算法,幀間壓縮是生成B幀和P幀的演算法。

通常,我們也把完整編碼的I幀稱為關鍵幀。因為解碼非關鍵幀需要解碼其參考的幀,因此在截圖等不需要全部解碼的操作中,經常擷取關鍵幀以提升效能。

獲得音視訊資訊

ffprobe是FFmpeg專案提供的用於分析視訊資訊的命令列工具。

隨意下載一個測試視訊testmp4, 然後終端中輸入指令:

ffprobe -v quiet -print_format json -show_format -show_streams test.mp4

可以獲得json格式輸出的視訊資訊:

{
    "streams": [ // 檔案中包含的流
        {
            "index": 0,  // 流的序號
            "codec_name": "h264", // 流的編碼格式
            "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", // 編碼格式的全名
            "profile": "High", 
            "codec_type": "video", // video表示這是一個視訊流
            "codec_time_base": "1/60",
            "codec_tag_string": "avc1",
            "codec_tag": "0x31637661",
            "width": 1080, // 視訊寬為1080畫素
            "height": 614, // 視訊高為614畫素
            "coded_width": 1080,
            "coded_height": 614,
            "has_b_frames": 2,
            "sample_aspect_ratio": "0:1",
            "display_aspect_ratio": "0:1",
            "pix_fmt": "yuv420p",
            "level": 31,
            "chroma_location": "left",
            "refs": 1,
            "is_avc": "true",
            "nal_length_size": "4",
            "r_frame_rate": "30/1", // 實際幀率
            "avg_frame_rate": "30/1",
            "time_base": "1/15360",
            "start_pts": 0,
            "start_time": "0.000000",
            "duration_ts": 153093,  
            "duration": "9.966992",  // 以秒為單位的視訊時間
            "bit_rate": "2077265",  // 視訊的位元率
            "bits_per_raw_sample": "8",
            "nb_frames": "299",
            "tags": {  // 流中的附加資訊,其中的欄位可能為空
                "rotate": 90, // 視訊旋轉的角度
                "language": "und",
                "handler_name": "VideoHandler"
            }
        },
        {
            "index": 1, // 流編號
            "codec_name": "aac", // 流的編碼格式
            "codec_long_name": "AAC (Advanced Audio Coding)", // 編碼格式的全名
            "profile": "LC",
            "codec_type": "audio", // 這是一個音訊流
            "codec_time_base": "1/44100", 
            "codec_tag_string": "mp4a",
            "codec_tag": "0x6134706d",
            "sample_fmt": "fltp",
            "sample_rate": "44100", // 取樣率
            "channels": 2, // 聲道數
            "channel_layout": "stereo", // 聲道佈局,stereo為立體雙聲道
            "bits_per_sample": 0,
            "r_frame_rate": "0/0",
            "avg_frame_rate": "0/0",
            "time_base": "1/44100",  // 每幀時長
            "start_pts": 0,
            "start_time": "0.000000", // 流開始播放時間
            "duration_ts": 442367,
            "duration": "10.030998",  // 流時長
            "bit_rate": "129341",  // 位元率
            "max_bit_rate": "129341",
            "nb_frames": "433",
            "tags": {
                "language": "und",
                "handler_name": "SoundHandler"
            }
        }
    ],
    "format": {  // 容器資訊
        "filename": "test.mp4",  // 檔名
        "nb_streams": 2,
        "nb_programs": 0,
        "format_name": "mov,mp4,m4a,3gp,3g2,mj2", // 封裝格式名
        "format_long_name": "QuickTime / MOV",
        "start_time": "0.000000",
        "duration": "10.055000",
        "size": "2762615",  // 檔案位元組數
        "bit_rate": "2198002", // 位元率
        "probe_score": 100,
        "tags": {
            "major_brand": "isom",
            "minor_version": "512",
            "compatible_brands": "isomiso2avc1mp41",
            "encoder": "Lavf57.71.100"
        }
    }
}

示例中使用-v quiet選項將日誌級別設為quiet避免日誌資訊汙染json,-show_format顯示檔案的容器資訊,-show_stream顯示容器中流的資訊,-show_frames則可以顯示視訊中每一幀的資訊。

更多關於ffprobe的內容可以參考官方文件

使用ffmpeg進行視訊處理

ffmpeg的命令格式:

ffmpeg \
    [global_options] \
    [input_file_options] -i input_url \
    [actions] \
    [output_file_options] output_url

我們可以將ffmpeg的選項分為全域性選項和區域性選項,區域性選項用於設定輸入輸出或者濾鏡等,通常位於被修飾的指令前面。

ffmpeg的基本流程為將容器中的各流進行解碼,然後重新編碼為指定的格式。在編碼之前,可以使用filter對視訊進行處理。

選項

選項的詳細內容請參考官方文件

-y / -n

-y/-n 為全域性選項, -y表示直接覆蓋已經存在的輸出檔案, -n表示若某個輸出檔案已經存在則退出。

若沒有設定-y-n選項,且某個輸出檔案已經存在ffmpeg會詢問是否要覆蓋輸出檔案。

ffmpeg -y -i test.mp4 test.mkv

-codec(-c)

指定輸入輸出的解碼編碼器, 可用的編解碼器可以參考官方文件:

fmpeg -y -i test.mp4 -c:v libx264 -c:a copy test.mov

codec指定為copy則將輸入流直接複製到輸出流不進行編解碼操作。

使用-c:STREAM_INDEX方式可以指定某一個流的解碼器,STREAM_INDEX為stream物件的index屬性。

-c:v-vcodec可以為所有視訊流指定編碼器,-c:v:1為第2個視訊流指定編解碼器。

-c:a-acodec可以為所有音訊流指定編碼器,-c:a:12為第13個視訊流指定編解碼器。

-ss

-ss選項用於設定流的開始時間,可以設定輸入輸出或者濾鏡。在開始時間之前的幀將被跳過不被處理(輸入不被解碼,輸出不被編碼,濾鏡不被處理)。

ffmpeg -ss 2 -t 10 -i test.mp4 test.mov

時長有兩種方式來表示:

  • 秒數: 如-t 10-t 23.167
  • 時分秒: 如-t 10:23-t 21:31:00.233

-t

-t選項用於用於設定輸入輸出,-t-i前可以限制輸入時長,-t在輸出檔案前可以限制輸出時長。

讀入test.mp4檔案2s開始10s內的資料,轉碼後輸出到test.mov:

ffmpeg -ss 2 -t 10 -i test.mp4 test.mov

讀入test.mp4全部資料,全部轉碼後輸出從第2s開始1min10s內的資料到test.mov:

ffmpeg -i test.mp4 -ss 2 -t 01:10 test.mov

-to

-to選項類似於-t選項,不同的是-to指定結束時刻,-t指定持續時間。

讀入test.mp4檔案2s到12s內的資料,轉碼後輸出到test.mov:

ffmpeg -ss 2 -to 12 -i test.mp4 test.mov

讀入test.mp4全部資料,全部轉碼後輸出從01:00到01:30內的資料到test.mov:

ffmpeg -i test.mp4 -ss 01:00 -to 01:30 test.mov

-f

強制設定輸入輸出的檔案格式,預設情況下ffmpeg會根據檔案字尾名判斷檔案格式。

ffmpeg -formats命令會顯示所有支援的編碼格式。

-filter / -filter_complex

使用過濾器對流進行處理,下文將簡要介紹filter的相關內容。

可以使用-vf代替-filter:v處理視訊流, -af代替-filter:a處理音訊流。

-vframes

設定輸出檔案中包含的總幀數:

ffmpeg -i test.mp4 -vframes 1 test.mov

-vn

不將視訊流寫到輸出檔案中

ffmpeg -i test.mp4 -vn -a:c copy out.mp3

-r

設定某個流的幀率:

ffmpeg -i test.mp4 -r:v 30 test.mov

-s

設定幀的大小:

ffmpeg -i test.mp4 -s 1080x680 out.mp4

-an

不將音訊流寫到輸出檔案中:

ffmpeg -i test.mp4 -v:c copy -an out.mp4

-threads

設定處理執行緒數:

ffmpeg -threads 8 -i test.mp4 out.mp4

可以設定處理

-shortest

當最短的輸入流結束後即停止編碼和輸出。

ffmpeg -i bgm.mp3 -i test.mp4 -shortest output.mp4

filter

過濾器會對已解碼的幀進行處理,處理後的幀會被重新編碼輸出,整個流程可以概括為:

Input -> DecodedFrames -> FilteredFrames -> EncodedData

簡單過濾器是單輸入單輸出的(只能處理一個流),而複雜過濾器(filter_complex)是多輸入多輸出的可以進行更復雜的操作。

ffmpeg支援的各種濾鏡可以參考官方文件-濾鏡

scale

ffmpeg -y -i test.mp4 -vf "scale=2*in_w:2*in_h" test.mov

scale濾鏡用於縮放視訊, in_win_h代表輸入的寬和高。

crop

ffmpeg -y -i test.mp4 -vf "crop=w=100:h=100:x=in_w/2:y=in_h,scale=400:400" test.mov

crop濾鏡用於擷取視訊中的一個區域。

overlay

ffmpeg -y -i test.mp4 -i logo.png -filter_complex 'overlay=10:main_h-overlay_h-10' out.mp4

overlay濾鏡將一個視訊疊放在另一個視訊上,可用於在視訊中新增水印和動畫等操作。

overlay的第一個輸入為底層視訊流,第二個輸入為疊加視訊流。main_wmain_h為底層視訊的寬和高,overlay_woverlay_h為疊加視訊的寬和高。

drawtext

ffmpeg -y -i test.mp4 -vf "drawtext=fontfile=CourierNew.ttf:text='hello world':x=100:y=50:fontsize=24" out.mp4

drawtext濾鏡用於在視訊上新增文字。

fade

ffmpeg -y -i test.mp4 -vf "fade=in:st=0:d=5" out.mp4

fade濾鏡可以製作淡入淡出效果

fps

ffmpeg -y -i test.mp4 -vf "fps=60" out.mp4

fps濾鏡通過刪除幀或者複製幀的方法強制設定幀率。

ffmpeg -y -i test.mp4 -vf "fps=1" img%3d.png
ffmpeg -y -i test.mp4 -r 1 img%3d.png

上面兩條指令都可以對視訊每秒擷取一幀影象,-r選項會擷取關鍵幀並不一定擷取0s、1s...處的幀,fps濾鏡處理的是已經解碼的幀因此可以精確的按照時間擷取。

因為fps濾鏡會解碼要截圖的視訊片段,因此這種方式截圖會慢很多。

應用示例

視訊轉碼

ffmpeg -y \
    -i test.mp4 \
    -vcodec copy \
    -acodec copy \
    out.mkv

這條指令將容器格式由MP4轉換到MKV,使用ffprobe檢查輸出檔案可以發現,視訊流沒有發生變化,但是封裝格式改變為mkv格式。

-vcodec是一個簡單過濾器用於處理視訊編碼,copy表示將視訊流複製到輸出檔案中。-acodec是處理音訊編碼的過濾器。

提取視訊流

ffmpeg -y \
    -i test.mp4 \
    -vcodec copy \
    -an \
    out.mp4

-an表示不保留音訊流。

提取音訊

ffmpeg -y \
    -i test.mp4 \
    -ar 44100 -ac 2 -ab 192 \
    -f mp3 \
    output.mp3

分析:

  • -ar: 指定輸出音訊取樣率
  • -ac: 指定輸出音訊通道(channel)數, 這裡設定為雙聲道
  • -ab: 指定輸出音訊位元率,單位kb/s

按幀擷取影象

擷取第2s開始的10幀影象, 伸縮為352x240:

ffmpeg -y \
    -ss 2 -i test.mp4 \
    -vframes 10 \
    -f image2 \
    -s 352x240 \
    img%03d.png

分析:

  • -ss 2 -i test.mp4ss為開始時間,用秒數或者hh:mm:ss[.xxx]格式表示。-i test.mp4表示輸入源
  • -vframes: 指定擷取的幀數, 這裡是擷取前10幀(從-ss指定開始時間算起)
  • -f: 指定輸出檔案的格式,如: image2, mjpeg, gif
  • -s: 對輸出畫面進行縮放
  • img%03d.png: 格式化輸出檔名,本示例中輸出img001.png, img002.png等。

-ss引數也可以放在vframes前:

ffmpeg -y \
    -i test.mp4 \
    -ss 2 -vframes 1 \
    -f image2 \
    -s 352x240 \
    img.png

-ss引數是區域性選項用於設定其後的一個命令,-ss 2 -i test.mp4表示從輸入視訊的第2s開始處理,忽略前兩秒的內容。

-ss 2 -vframes 1表示從第2s開始擷取,此時前2s的內容已經進行了解碼。

對不需要處理的部分進行解碼會浪費大量時間,因此建議使用-ss 2 -i test.mp4來表示截圖開始時間。

按時間擷取影象

從第2s到第12s內,每秒擷取1幀影象:

ffmpeg -y \
    -ss 2 -i test.mp4 \
    -r 1 -t 10 \
    -f image2 \
    -s 352x240 \
    img%03d.png

分析:

  • -t: 指定擷取時長,這裡擷取10s
  • -r 1-t的區域性選項設定每秒擷取的幀數(擷取幀率),若不設定則擷取全部幀

-vframe一樣-t的開始時間也有兩種設定方式,基於同樣的理由同樣建議將-ss放在輸入前。

擷取視訊片段

擷取視訊片段的方法與截圖方法類似,只是將輸出格式變為視訊:

按時間擷取:

 ffmpeg -y \
    -ss 2 -i test.mp4 \
    -r 20 -t 10 \
    -s 352x240 \
    clip.mp4

因為輸出為視訊,-r指定的擷取幀率即為輸出視訊幀率。

按幀數擷取:

ffmpeg -y \
    -ss 2 -i test.mp4 \
    -vframes 120 \
    -s 352x240 \
    clip.mp4

擷取視訊區域

擷取視訊區域:

ffmpeg -y \
    -ss 2 -i test.mp4 \
    -r 1   \
    -t 10 \
    -filter_complex "[0:v]crop=w=100:h=100:x=12:y=34,scale='400:400'[v]" \
    -map "[v]" \
    img%03d.png

crop濾鏡可以擷取視訊部分割槽域,[0:v]crop=w=100:h=100:x=12:y=34,scale='400:400'[v]截取了左上角在(12,34)處,寬為100,高為100的矩形框中的內容,並將截圖放大到400x400。

拼接視訊

ffmpeg -i "concat:1.mp4|2.mp4|3.mp4" -c copy output.mp4

將圖片合併為視訊

ffmpeg -i img%3d.png output.gif
ffmpeg -i img%3d.png output.mp4

新增音訊

ffmpeg -i bgm.mp3 -i test.mp4 output.mp4

新增水印

ffmpeg -y \
    -i test.mp4 \
    -i 1.png \
    -filter_complex "[1]scale=w=480:h=280[s];[0][s]overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10[ov]" \
    -map "[ov]" \
    output.mp4

使用filter_complex先將水印圖片(輸入1)放大到480x280, 然後使用overlay濾鏡將放大後的流[s]覆蓋到視訊(輸入0)上。

若不需要使用scale進行縮放,則可以簡化filter_complex表示式:

ffmpeg -y \
    -i test.mp4 \
    -i 1.png \
    -filter_complex "overlay=x=main_w-overlay_w-10:y=main_h-overlay_h-10" 
    output.mp4

新增動畫

ffmpeg -y -i test.mp4 -t 10 -loop 1 -framerate 6 -i ani%3d.png -filter_complex 'overlay=10:main_h-overlay_h-10' out.mp4

將多張圖片(ani001.png, ani002.png...)組成動畫, 然後將這個動畫疊加在視訊的左下角。-t 10 -loop 1會迴圈播放動畫,持續10s。

該方式也支援gif格式的動畫。

新增文字

ffmpeg -y -i test.mp4 -vf "drawtext=fontfile=CourierNew.ttf:text='hello world':x=100:y=50:fontsize=24" out.mp4

新增字幕

新增字幕有兩種方式:

  • 將字幕新增為獨立的流,mkv,avi等封裝格式支援此種方式,mp4格式不支援
  • 將字幕疊加到視訊中

新增字幕流:

ffprobe -show_streams -print_format json out.mp4

疊加字幕:

ffmpeg -i test.mp4  -i sub.srt -filter_complex "[0][1]overlay[v]" -map "[v]" out.mp4

旋轉視訊

旋轉視訊有兩種方式:

  • 在視訊元資訊中新增旋轉角度資訊,由播放器執行旋轉
  • 將每幀影象旋轉

新增元資訊:

ffmpeg -i test.mp4 -metadata:s:v rotate="90" -codec copy out.mp4

逐幀旋轉:

ffmpeg -i test.mp4 -vf "transpose=1" out.mp4

transpose濾鏡的文件