初探視頻原理和FFmpeg

本文做者郭文濤,奇舞團前端開發工程師。html

本文導讀

閱讀本文你將得到如下知識:前端

瞭解視頻的基本原理。node

瞭解 FFmpeg 是什麼,和一些經常使用的用法。nginx

用 FFmpeg 搭建簡單的視頻直播推流。git

FFmpeg 在 NodeJS 中的一些用法。程序員

背景

短視頻大行其道的年代,做爲程序員勢必須要了解:視頻編輯背後的原理和技術。本文簡略的描述了視頻的組成原理和經常使用的視頻編輯工具,以及在 NodeJS 中的用法。github

想要了解視頻原理,首先應該從圖像原理開始提及。算法

圖像基礎

1. 像素

圖像畫面由一個數字序列表示的圖像中的一個最小單位色塊,被稱之像素(pixel/px)。docker

注意:像素只有位圖纔會有,是用來記錄位圖圖像的。shell

咱們所說的圖像大小爲1920*1080,指的就是長寬各有 1920 和 1080 的像素點,那麼一張1920*1080的圖片總共有的像素點爲:1920*1080 = 2073600個像素點。

圖像的大小如何計算?

圖像的大小:像素數量 * 像素大小 = 圖片大小,而 像素大小像素深度有關係。RGB表示的真彩色能表示256×256×256=16,777,216,就是咱們常見的1600萬色,是人眼可見的所有色彩,超出沒有意義。RGB的像素深度有1bit、4bit、8bit、16bit、24bit、32bit,如在ps中下圖在新建一張畫布選擇8bit(指 rgb 每種顏色佔 8bit),那這樣1 px = 3 * 8bit = 24bit,俗稱24 位圖。根據以上公式就能算出以下圖圖像的大小:500 * 378 * 24 / 8 = 567000Byte = 567000Byte / 1024 = 553.7109375 Kb,和 ps 顯示的圖像大小同樣。

但每每真實的圖片大小遠比以上計算的結果小不少, 這是由於導出的圖片都通過壓縮的,關於圖片壓縮技術可自行搜索學習。

視頻基礎

1. 視頻和圖像的關係?

視頻就是圖片一幀一幀連起來的產物,連起來的越快看着越流暢,用 幀率(就是每秒播放圖片的數量 FPS)來衡量視頻的流暢度。那麼根據圖片大小的算法就能算出視頻的大小。

視頻的大小 = 時長(秒) * 幀率(FPS)* 圖片大小

那麼1920×1280分辨率, 30FPS,時長 1 秒的視頻的大小就是:1920 * 1280 * 24 / 8 * 30 / 1024 / 1024 = 210.9375 M,那麼 1 小時的影片須要:210.9 * 60 * 60 / 1024 = 741.4453125 G,不由產生疑問,爲啥我下載的大片才 1G 多?莫慌,視頻要是這麼簡單,那咱們就太天真了,因此就有了下文 「視頻編碼」

2. 視頻是怎麼來的?

幾個概念

  • 幀(Frame):就是一張靜止的畫面, 是視頻的最小單位。

  • 幀速率(FPS):每秒播放圖片的數量。

  • 碼率(Bit Rate):視頻文件在單位時間內使用的數據流量,決定視頻的質量和大小,單位是 kb/s 或者 Mb/s。通常來講一樣分辨率下,視頻文件的碼流越大,壓縮比就越小,畫面質量就越高。碼流越大,說明單位時間內取樣率越大,數據流,精度就越高,處理出來的文件就越接近原始文件,圖像質量越好,畫質越清晰,要求播放設備的解碼能力也越高。

    碼率的常見三種模式:
    - CBR
      - 全程碼率恆定
      - 文件大小可預測
      - 編碼壓力小,直播經常使用
    - VBR
      - 碼率可變
      - 簡單場景碼率低,複雜場景碼率高
    - CRF
      - 固定質量模式
      - CRF值越低,視頻看起來質量越高
    複製代碼

視頻構成

視頻和音頻就像是飯和菜,封裝格式就至關於碗。

注意: 下文全部視頻均表明包含音頻的視頻

1. 視頻封裝格式

常見封裝格式有 MP四、AVI、FLV、mov、RMVB、MKV、WMV、3GP、ASF 等。

2. 編碼格式

視頻編碼是對採用視頻壓縮算法將一種視頻格式轉換成另外一種視頻格式的描述,音頻編碼同理。

常見的視頻編碼格式有:AC-1MPEG2/H.262VP8MPEG4VP9H.261H.263H.264H.265 等。

常見的音頻編碼格式有:WMAMP3AC-3AACAPEFLACWAV 等。

視頻壓縮原理

主要是將視頻像素數據(RGB,YUV 等)壓縮成爲視頻碼流,從而下降視頻的數據量,也就是處理像素。

YUV:RGB同樣是一種顏色編碼格式,相比RGB更利於壓縮。其中"Y"表示明亮度(Lumina nce 或 Luma),也就是灰階值;而"U"和"V"表示的則是色度(Chrominance 或 Chroma),做用是描述影像色彩及飽和度,用於指定像素的顏色。

視頻壓縮分爲下面兩種類型

1. 幀內壓縮

也叫空間壓縮,相似於圖像壓縮,屬於有損壓縮算法,達不到很高的壓縮比。

2. 幀間壓縮

主要是經過記錄關鍵幀,經過壓縮關鍵幀之間連續幀的冗餘信息(連續幀內相同的像素區域)的過程。

爲了記錄關鍵幀,將視頻的畫面幀分爲三類:

  • I 幀:幀內編碼幀(intra picture),能展現最完整的畫面, 可壓縮的空間小,編碼過程屬於幀內編碼。
  • P 幀:前向預測編碼幀(predictive-frame),須要參考前面的 I 幀或者 P 幀來找出不一樣部分進行編碼,壓縮比比較高。
  • B 幀 雙向預測,也就是 B 幀記錄的是本幀與先後幀的差異。也就是說要解碼 B 幀,不只要取得以前的緩存畫面,還要解碼以後的畫面,經過先後畫面的與本幀數據的疊加取得最終的畫面。B 幀壓縮率高,可是對解碼性能要求較高。

GOP(Group of Pictures)值

編碼器將多張圖像進行編碼後生產成一段一段的 GOP ,每一組 IPB 幀的序列包含多少幀,也就是一個 I 幀結束後須要通過多少幀才能出現下一個 I 幀。因此同碼率下 GOP 值越大,B 幀和 P 幀越多,視頻質量越高。

在壓縮或者解壓縮視頻的過程用到編解碼器(Codec)。總的過程能夠:

視頻的編碼的過程:

下圖來源於即時通信網

視頻解碼的過程:

音頻壓縮原理

音頻壓縮是在保證信號在聽覺方面不產生失真的前提下,對音頻數據信號進行儘量大的壓縮, 去除冗餘信息。冗餘信號包含人耳聽覺範圍外的音頻信號以及被掩蔽掉的音頻信號等。例如,人耳所能察覺的聲音信號的頻率範圍爲 20Hz ~ 20KHz,除此以外的其它頻率人耳沒法察覺,均可視爲冗餘信號。此外,根據人耳聽覺的生理和心理聲學現象,當一個強音信號與一個弱音信號同時存在時,弱音信號將被強音信號所掩蔽而聽不見,這樣弱音信號就能夠視爲冗餘信號而不用傳送。

音頻壓縮不是今天的主角,想深刻學習可參考以下連接:

baike.baidu.com/item/%E9%9F…

www.kamilet.cn/how-audio-c…

FFmpeg

1. FFmpeg 什麼?

FFmpeg is a collection of libraries and tools to process multimedia content such as audio, video, subtitles and related metadata.

簡單說就是一個跨平臺的視頻處理的程序。

2. FFmpeg 的原理

整個過程基本能夠說成:解複用 => 解碼 => 編碼 => 複用器。

_______              ______________
|       |            |              |
| input |  demuxer   | encoded data |   decoder
| file  | ---------> | packets      | -----+
|_______|            |______________|      |
                                           v
                                      _________
                                     |         |
                                     | decoded |
                                     | frames  |
                                     |_________|
 ________             ______________       |
|        |           |              |      |
| output | <-------- | encoded data | <----+
| file   |   muxer   | packets      |   encoder
|________|           |______________|
複製代碼

3.FFmpeg 安裝

FFmpeg 分爲 3 個版本:Static、 Shared、 Dev

Mac 安裝:

brew install ffmpeg
複製代碼

其餘安裝請參考官網

4. FFmpeg 用法

它能分別對視頻的的各個組成進行編碼,它對音視頻的編碼格式支持也比較全面。例如:對視頻容器的轉換、音視頻的壓縮、視頻截取、截圖、濾鏡、音頻提取等等,很是強大。

命令行語法:

ffmpeg [全局參數] [輸入文件參數] -i [輸入文件] [輸出文件參數] [輸出文件]

複製代碼

視頻信息:

// 獲取視頻信息
ffmpeg -i input.mp4

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'input2.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2mp41
    encoder         : Lavf58.29.100
    description     : Packed by Bilibili XCoder v2.0.2
  Duration: 00:08:24.45, start: 0.000000, bitrate: 2180 kb/s   // 時長,碼率
    Stream #0:0(und): Video: hevc (Main) (hev1 / 0x31766568), yuv420p(tv), 1920x1080 [SAR 1:1 DAR 16:9], 2046 kb/s, 25 fps, 25 tbr, 16k tbn, 25 tbc (default)  // 第一個流是視頻流,編碼格式是hevc(封裝格式爲hev1),每一幀表示爲yuv420p,分辨率1920*1080,碼率2046kb/s, fps爲25。
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default) // 第二個流是音頻流,編碼格式是aac(封裝格式爲mp4a)採樣率是44100 Hz,聲道是立體聲,碼率92Kbit/s。
    Metadata:
      handler_name    : SoundHandler
複製代碼

碼率的轉換:

ffmpeg -i input.mp4 -b:v 64k -bufsize 64k output.mp4
複製代碼

幀率轉換:

ffmpeg -i input.mp4 -r 5 output.mp4
複製代碼

分辨率轉換:

ffmpeg -i input.mp4 -vf scale=480:-1 output.mp4 // 1080p 轉爲 480p
複製代碼

視頻倍速:

ffmpeg -i test1 "setpts=PTS/5" test4.mp4 // 視頻5倍速轉換
fmpeg -i input.mp4 -filter:a "atempo=2.0" 4s.mp4 // 音頻2倍速播放
ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" -vn 4s.mp4 // 音視頻同時2倍速
複製代碼

提取音視頻:

ffmpeg -i input.mp4 -an output.mp4 //提取視頻
ffmpeg -i input.mp4 -vn output.mp3 //提取音頻
複製代碼

視頻比例轉換:

ffmpeg -i input.mp4 -aspect 21:9 output.mp4
複製代碼

視頻容器轉換:

ffmpeg -i input.mp4 output.avi
複製代碼

視頻截圖:

ffmpeg -ss 00:00:05 -i input.mp4 -vframes 1 -q:v 5 -f image2 pic-%03d.jpeg
// -ss 00:00:05 從第五秒開始  -vframes 1 只截取1幀  -q:v 5 圖片質量1-5
複製代碼

視頻截取:

ffmpeg -ss 00:00:02 -i input.mp4 -t 6.5 -c copy cut.mp4
ffmpeg -ss 00:00:02 -i input.mp4 -to 00:00:10 -c copy cut.mp4
複製代碼

連續圖片或視頻生成 gif 圖:

ffmpeg -i output.mp4 -to 10 -r 30 -vf scale=100:-1 gg.gif // 截取視頻某個部分生成gif  100:-1 指定寬度,高度保持原始比例

ffmpeg -r 5 -i pic-%03d.jpeg 11.gif   // 多圖生成gif

// 圖片還可生成視頻
ffmpeg -r 20 -i pic-%03d.jpeg gif.mp4

ffmpeg -f concat -i "concat:part1.mp4|part2.mp4|3.mp4|part4.mp4" -c copy output.mp4 // 多個視頻拼接成一個
複製代碼

圖片或視頻加濾鏡:

// 模糊濾鏡
ffmpeg -y -i pic-012.jpeg -vf boxblur=7 blur.jpeg
// 變色
ffmpeg -i pic-012.jpeg -vf colorbalance=rm=1 colorbalance1.jpg // 調整rgb某個維度的權重實現變色。
ffmpeg -i pic-012.jpeg -vf colorchannelmixer=.3:.4:.3:0:.3:.4:.3:0:.3:.4:.3 colorchannelmixer1.jpg // 對rgba四個通道進行從新計算,並分別給定權重比例。
ffmpeg -i pic-012.jpeg -vf hue=h=30:s=1 hue1.jpg // 改變色調,至關在調色板上調色
ffmpeg -i pic-012.jpeg -vf lutyuv="y=negval:u=negval:v=negval" lutyuv1.jpg // lutyuv用於yuv顏色空間
ffmpeg -i pic-012.jpeg -vf negate=0 negate1.jpg // 反轉
ffmpeg -i pic-012.jpeg -vf swapuv swapuv1.jpg  // UV 互換
ffmpeg -i pic-012.jpeg -vf crop=w=200:h=300:x=500:y=800 crop1.jpg // 裁剪

複製代碼

添加水印:

ffmpeg -i input.mp4 -i pic-012.jpeg -filter_complex "[1:v] scale=176:144 [logo];[0:v][logo]overlay=x=0:y=0" out.mp4 //給視頻添加圖片水印

ffmpeg -i input.mp4 -vf "drawtext=fontsize=100:fontcolor=white:alpha=0.3:text='%{localtime\:%Y\-%m\-%d %H-%M-%S}':y=h-line_h-100:x=(w-text_w)/2" output22.mp4// 添加文字水印

ffmpeg -i input.mp4 -i pic-012.jpeg -filter_complex "[1:v] scale=176:144 [logo];[0:v][logo]overlay=x=0:y=0" out.mp4

ffmpeg -i input.mp4 -vf drawtext="fontsize=100:text='我是水印':fontcolor=green:enable=lt(mod(t\,3)\,1)" interval-sy.mp4
// t 時間,s
// mod(t\,2) 計算t%2
// lt(mod(t\,2)\,1) 若是mod(t\,2)<1,返回1,不然返回0
// enable=lt(mod(t\,2)\,1) 每隔1s顯示一次水印,enable=lt(mod(t\,3)\,1) 每隔3s.
複製代碼

添加字幕:

// 第一步 用you-get下載B站視頻
// 第二步 用 danmaku2ass.py 轉換彈幕 https://github.com/m13253/danmaku2ass
// 第三步 能夠用ffmpeg轉換彈幕
ffpmeg -i input.ass input.srt

// 第四步 給視頻添加字幕或彈幕 字幕可添加多個
ffmpeg -i input.mp4 -vf subtitles=input.ass output.mp4
複製代碼

爲音頻添加封面:

ffmpeg -loop 1 -i cover.jpg -i input.mp3 -c:v libx264 -c:a aac -b:a 192k -shortest output.mp4

// -loop 1表示一直循環, -shortest 音頻結束視頻輸出就結束
複製代碼

視頻畫中畫:

ffmpeg -re -i input.mp4 -vf "movie=output.mp4,scale = 480*320[test]; [in][test] overlay [out]" -vcodec libx264 videoInvideo.mp4
複製代碼

多宮格:

ffmpeg -y -i input.mp4 -i input.mp4 \
-i input.mp4 -i input.mp4 \
-filter_complex "nullsrc=size=640x480[base]; \
[0:v]scale=320x240[topleft]; \
[1:v]scale=320x240[topright]; \
[2:v]scale=320x240[bottomleft]; \
[3:v]scale=320x240[bottomright]; \
[base][topleft]overlay=shortest=1[tmp1]; \
[tmp1][topright]overlay=shortest=1:x=320[tmp2]; \
[tmp2][bottomleft]overlay=shortest=1:y=240[tmp3]; \
[tmp3][bottomright]overlay=shortest=1:x=320:y=240" \
-vcodec libx264 9_video_filtered.flv
// nullsrc建立畫布
複製代碼

視頻壓縮:

ffmpeg -i input.mp3 -ab 128 output.mp3 // 壓縮音頻

ffmpeg -i input.mp4 -vf scale=1280:-1 -c:v libx264 -preset veryslow -crf 24 output.mp4 // 壓縮視頻
複製代碼

視頻直播推流:

// 錄製視頻保存在本地
ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -f h264 -r 30 ~/Downloads/test.h264

// 推送已下載在文件夾的視頻
ffmpeg -re -i ~/Downloads/xxx.mp4  -vcodec libx264 -acodec aac -strict -2 -f flv rtmp://localhost:1935/live

// 錄製桌面
ffmpeg -f avfoundation -i "1" -vcodec libx264 -preset ultrafast -acodec libfaac -f flv rtmp://localhost:1935/rtmplive/room

// 錄製桌面和麥克風
ffmpeg -f avfoundation -i "1:0" -vcodec libx264 -preset ultrafast -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:1935/live/room

// 錄製桌面和麥克風,並打開攝像頭拍攝
ffmpeg -f avfoundation -framerate 30 -i "1:0" \-f avfoundation -framerate 30 -video_size 640x480 -i "0" \-c:v libx264 -preset ultrafast \-filter_complex 'overlay=main_w-overlay_w-10:main_h-overlay_h-10' -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:2016/rtmplive/room

複製代碼

直播 DEMO:

  1. 安裝支持rtmp的 docker 鏡像:docker pull tiangolo/nginx-rtmp

  2. 啓動tiangolo/nginx-rtmp容器:docker run -d -p 1935:1935 --name nginx-rtmp tiangolo/nginx-rtmp 查看nginx配置: docker exec -it nginx-rtmp /bin/bash

    推流地址:rtmp://10.17.8.189:1935/live

  3. Ffmpeg 推流:ffmpeg -f avfoundation -i "1:0" -vcodec libx264 -preset ultrafast -acodec libmp3lame -ar 44100 -ac 1 -f flv rtmp://localhost:1935/live/room

  4. 用支持支持rtmp的播放器(IINA)或者ffplay打開: ffplay rtmp://10.17.8.189:1935/live

一個簡單的直播 demo 就跑起來了。


5. FFmpeg 在 Node 中的用法

Fluent-ffmpeg

Fluent-ffmpeg 是將複雜的 ffmpeg 命令抽象成 NodeJS 的模塊,前提是系統已安裝 FFmpeg

一些簡單的用法

// 視頻信息
ffmpeg.ffprobe(input, function (err, metadata) {
  console.dir(metadata);
});

// 提取音頻
ffmpeg(input)
  .audioCodec("libmp3lame")
  .on("error", function (err) {
    console.log("發生錯誤: " + err.message);
  })
  .on("end", function () {
    console.log("提取音頻完成 🍻🍻!");
  })
  .save(resOut);

// 提取視頻
ffmpeg(input)
  .noAudio()
  .on("error", function (err) {
    console.log("發生錯誤: " + err.message);
  })
  .on("end", function () {
    console.log("提取視頻完成 🍻🍻!");
  })
  .save(resOut);
複製代碼

總結

回顧一下,廣義上的視頻是由:音頻視頻兩部分組成,它們分別有對應的各自的編碼規範視頻容器是將不一樣編碼格式的音、視頻組合在一塊兒的一種封裝格式。視頻編碼格式主要是對視頻的大小進行壓縮,分爲幀內壓縮幀間壓縮,幀間壓縮主要是經過記錄關鍵幀形式來進行壓縮。

FFmpeg 是處理音視頻編碼的一種程序,主要原理:demuxer => decoder => encoder => muxer

Fluent-ffmpeg 是將複雜的 ffmpeg 命令抽象成 nodeJs 的模塊,前提是系統已安裝 FFmpeg,這對於前端工程師來講,能夠用它處理衆多音視頻操做。

挖坑

下一篇預告:FFmpegwasm 在瀏覽器中的碰撞

本文參考文章

  1. 即時通信網-史上最通俗視頻編碼技術入門:www.52im.net/thread-2840…
  2. 簡書-音視頻基礎知識:www.jianshu.com/p/614b3e6e6…
  3. 濾鏡實現各類圖片效果 | Video-Filters | avfilter | 變色:www.geek-share.com/detail/2763…
  4. 阮一峯:FFmpeg 視頻處理入門教程:www.ruanyifeng.com/blog/2020/0…
相關文章
相關標籤/搜索