圖像編碼#
一張圖片可以使用一個二維矩陣表示,矩陣中的每一個點被稱為像素。每個像素的顏色使用三原色來表示,即紅、綠、藍。
每個像素可以用不同的數據位數來表示,常用的量化位數有 16 位、24 位、32 位等。
- 24 位比特模式:每像素 24 位(bits per pixel,bpp)編碼的 RGB 值:使用三個 8 位無符號整數(0 到 255)表示紅色、綠色和藍色的強度。
- 16 位比特模式:分配給每種原色各為 5 比特,其中綠色為 6 比特,因為人眼對綠色分辨的色調更精確。但某些情況下每種原色各占 5 比特,餘下的 1 比特不使用。
- 32 位比特模式:同 24 位比特模式,餘下的 8 比特用來表示像素的透明度(Alpha)。
圖片還有一個很重要的屬性就是分辨率,採用寬 x 高表示。
在處理圖像或視頻時另一個屬性是長寬比,它描述了圖像或像素的寬度和高度之間的比例關係。常見的比例有 4:3、16:9、21:9,通常指顯示寬高比(DAR),同樣像素也有不同的寬高比,稱之為像素長寬比(PAR)。
YUV 顏色模型#
RGB 訴求於人眼對色彩的感應,YUV 則著重於視覺對於亮度的敏感程度,Y 代表的是亮度(Luminance、Luma),UV 代表的是色度(Chrominance/Chroma)(因此黑白電影可省略 UV,相近於 RGB),分別用 Cr 和 Cb 來表示,因此 YUV 的記錄通常以 Y 的格式呈現。
為節省帶寬起見,大多數 YUV 格式平均使用的每像素位數都少於 24 位。主要的抽樣(subsample)格式有 YCbCr 4:2:0、YCbCr 4:2:2、YCbCr 4:1:1 和 YCbCr 4:4:4。YUV 的表示法稱為 A:B 表示法:
- 4:4:4 表示完全取樣。
- 4:2:2 表示 2:1 的水平取樣,垂直完全採樣。
- 4:2:0 表示 2:1 的水平取樣,垂直 2:1 採樣。
- 4:1:1 表示 4:1 的水平取樣,垂直完全採樣。
RGB 與 YUV 的轉換#
# 第一步計算亮度
Y = 0.299R + 0.587G + 0.114B
# 一旦有了亮度,可以分割顏色(色度藍色和紅色):
Cb = 0.564(B - Y)
Cr = 0.713(R - Y)
# 也可以通過使用 YCbCr 進行轉換,甚至獲得 RGB
R = Y + 1.402Cr
B = Y + 1.772Cb
G = Y - 0.344Cb - 0.714Cr
采樣率、碼率、幀以及場的概念#
圖像則是對模擬信號進行采樣量化後獲得,而視頻則是由一系列的圖像組成,採集時圖像的分辨率及量化位數越高,所能表達的信息越多,畫面則越清晰。視頻存在一個采樣頻率的屬性,即單位時間內采樣的次數。視頻的采樣頻率也受人眼的限制,通常在每秒 20 ~ 30 幀之間。當采樣頻率在每秒 10~20 幀時,對於快速運動的圖像,人眼可以感覺到不流暢,而采樣頻率提高到 20~30 幀時,人眼看起來比較流暢了。如果將采樣頻率在提高,人眼是很難感覺這種差異的,這也是目前電影拍攝時使用 24 幀或者 30 幀采樣頻率的原因。
顯示視頻所需要的每秒位數稱作為比特率,也叫碼率。計算公式為 比特率=寬 高 位深度 每秒幀數
* 例如,如果我們不採用任何類型的壓縮,每秒 30 幀,每像素 24 位,480x240 分辨率的視頻將需要 82,944,000 位 / 秒 或 82.944 Mbps(30x480x240x24)。
當比特率幾乎恆定時,稱為恆定比特率(CBR),但它也可以變化,然後稱為可變比特率(VBR)。下圖顯示了一個受限的 VBR,在幀為黑色時不會花太多位。
視頻采樣中通過逐行掃描得到一幅完整的圖像稱之為一幀,通常幀頻率為 25 幀(PAL 制)、30 幀每秒(NTSC 制),而通過隔行掃描(奇、偶數行),那麼一幀圖像就被分成兩場,通常場頻為 50Hz(PAL 制)、60Hz(NTSC 制)。這是在早期,工程師們提出的一種技術,能夠在不消耗額外的帶寬的情況下,使得顯示器的感知幀率倍增。這種技術稱為隔行視頻;它基本上在 1 幀中發送一半的屏幕,而在下一幀中發送另一半。
H264 簡介#
H264 是屬於視頻的編碼層的標準格式,視頻編碼顯然是為了壓縮大小。
概念#
- SODB:數據比特串 -> 最原始的編碼數據
- RBSP:原始字節序列載荷 -> 在 SODB 的後面填加了結尾比特(RBSP trailing bits 一個 bit “1”)若干比特 “0”,以便字節對齊。
- EBSP:擴展字節序列載荷 -> 在 RBSP 基礎上填加了仿校驗字節(0X03)它的原因是:在 NALU 加到 Annexb 上時,需要填加每組 NALU 之前的開始碼 StartCodePrefix,如果該 NALU 對應的 slice 為一幀的開始則用 4 位字節表示,0x00000001,否則用 3 位字節表示 0x000001,為了使 NALU 主體中不包括與開始碼相沖突的,在編碼時,每遇到兩個字節連續為 0,就插入一個字節的 0x03。解碼時將 0x03 去掉,也稱為脫殼操作。
H.264 的功能分為兩層,視頻編碼層(VCL)和網絡提取層(NAL)
VCL 數據即被壓縮編碼後的視頻數據序列。在 VCL 數據要封裝到 NAL 單元中之後,才可以用來傳輸或存儲。
H.264 的編碼視頻序列包括一系列的 NAL 單元,每個 NAL 單元包含一個 RBSP,見表 1。編碼片(包括數據分割片 IDR 片)和序列 RBSP 結束符被定義為 VCL NAL 單元,其餘為 NAL 單元。典型的 RBSP 單元序列如圖 2 所示。
每個單元都按獨立的 NAL 單元傳送。單元的信息頭(一個字節)定義了 RBSP 單元的類型,NAL 單元的其餘部分為 RBSP 數據。
NAL 單元#
每個 NAL 單元是一個一定語法元素的可變長字節字符串,包括包含一個字節的頭信息(用來表示數據類型),以及若干整數字節的負荷數據。一個 NAL 單元可以攜帶一個編碼片、A/B/C 型數據分割或一個序列或圖像參數集。
NALU 頭由一個字節組成,它的語法如下:
NAL 單元按 RTP 序列號按序傳送。其中,T 為負荷數據類型,占 5 bit;R 為重要性指示位,占 2 個 bit;最後的 F 為禁止位,占 1 bit。具體如下:
- NALU 類型位可以表示 NALU 的 32 種不同類型特徵,類型 1~12 是 H.264 定義的,類型 24~31 是用於 H.264 以外的,RTP 負荷規範使用這其中的一些值來定義包聚合和分裂,其他值為 H.264 保留。
- 重要性指示位用於在重構過程中標記一個 NAL 單元的重要性,值越大,越重要。值為 0 表示這個 NAL 單元沒有用於預測,因此可被解碼器拋棄而不會有錯誤擴散;值高於 0 表示此 NAL 單元要用於無漂移重構,且值越高,對此 NAL 單元丟失的影響越大。
- 禁止位編碼中默認值為 0,當網絡識別此單元中存在比特錯誤時,可將其設為 1,以便接收方丟掉該單元,主要用於適應不同種類的網絡環境(比如有線無線相結合的環境)。
264 常見的幀頭數據為:
- 00 00 00 01 67 (SPS)
- 00 00 00 01 68 (PPS)
- 00 00 00 01 65 (IDR 幀)
- 00 00 00 01 61 (P 幀)
上述的 67、68、65、61,還有 41 等,都是該 NALU 的識別級別。
F:禁止為,0 表示正常,1 表示錯誤,一般都是 0
NRI:重要級別,11 表示非常重要。
TYPE:表示該 NALU 的類型是什麼,
見下表,由此可知 7 為序列參數集(SPS),8 為圖像參數集(PPS),5 代表 I 幀。1 代表非 I 幀。
{% asset_img h264-header.png h264-header %}
由此可知,61 和 41 其實都是 P 幀(type 值為 1),只是重要級別不一樣(它們的 NRI 一個是 11 BIN,一個是 10 BIN)
H264 (NAL 簡介與 I 幀判斷)#
我們還是接著看最上面圖的碼流對應的數據來層層分析,以 00 00 00 01 分割之後的下個字節就是 NALU 類型,將其轉為二進制數據後,
解讀順序為從左往右算,如下:
(1)第 1 位禁止位,值為 1 表示語法出錯
(2)第 2~3 位為參考級別
(3)第 4~8 為是 nal 單元類型
例如上面 00000001 後有 67、68 以及 65
其中 0x67 的二進制碼為:
0110 0111
4-8 為 00111,轉為十進制 7,參考第二幅圖:7 對應序列參數集 SPS
其中 0x68 的二進制碼為:
0110 1000 4-8 為 01000,轉為十進制 8,參考第二幅圖:8 對應圖像參數集 PPS
其中 0x65 的二進制碼為:
011 00101
4-8 位為 00101,轉為十進制 5,參考第二幅圖:5 對應 IDR 圖像中的片 (I 幀)
所以判斷是否為 I 幀的算法為:
(NALU 類型 & 0001 1111) = 5 即 (NALU 類型 & 31) = 5 比如 0x65 & 31 = 5
RTP 打包發送 H264 之封包詳解#
RFC3984 是 H.264 的 baseline 碼流在 RTP 方式下傳輸的規範
H264 的碼流結構
單個 NALU#
12 字節的 RTP 頭後面的就是音視頻數據,比較簡單。一個封裝單個 NAL 單元包到 RTP 的 NAL 單元流的 RTP 序號必須符合 NAL 單元的解碼順序。對於 NALU 的長度小於 MTU 大小的包,一般採用單一 NAL 單元模式。對於一個原始的 H.264 NALU 單元常由[Start Code] [NALU Header] [NALU Payload]
三部分組成,其中 Start Code 用於標示這是一個 NALU 單元的開始,必須是 “00 00 00 01” 或 “00 00 01”, NALU 頭僅一個字節,其後都是 NALU 單元內容。
打包時去除 “00 00 01” 或 “00 00 00 01” 的開始碼,把其他數據封包的 RTP 包即可。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| type | |
+-+-+-+-+-+-+-+-+ |
| |
| Bytes 2..n of a Single NAL unit |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
如有一個 H.264 的 NALU 是這樣的:
[00 00 00 01 67 42 A0 1E 23 56 0E 2F …]
這是一個序列參數集 NAL 單元。[00 00 00 01] 是四個字節的開始碼,67 是 NALU 頭,42 開始的數據是 NALU 內容。
封裝成 RTP 包將如下:
[RTP Header] [67 42 A0 1E 23 56 0E 2F]
即只要去掉 4 個字節的開始碼就可以了。
包聚合#
當 NALU 的長度特別小時,可以把幾個 NALU 單元封在一個 RTP 包中。
為了體現 / 應對有線網絡和無線網絡的 MTU 巨大差異,RTP 協議定義了包聚合策略:
- STAP-A:聚合的 NALU 時間戳都一樣,無 DON(decoding order number);
- STAP-B:聚合的 NALU 時間戳都一樣,有 DON;
- MTAP16:聚合的 NALU 時間戳不同,時間戳差值用 16 bit 記錄;
- MTAP24:聚合的 NALU 時間戳不同,時間戳差值用 24 bit 記錄;
- 包聚合時,RTP 的時間戳是所有 NALU 時間戳的最小值;
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|F|NRI| Type | |
+-+-+-+-+-+-+-+-+ |
| |
| one or more aggregation units |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 3. RTP payload format for aggregation packets
STAP-A 示例:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP Header |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|STAP-A NAL HDR | NALU 1 Size | NALU 1 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 1 Data |
: :
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| | NALU 2 Size | NALU 2 HDR |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NALU 2 Data |
: :
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 7. An example of an RTP packet including an STAP-A
containing two single-time aggregation units
FU-A 的分片格式#
數據比較大的 H264 視頻包,被 RTP 分片發送。12 字節的 RTP 頭後面跟隨的就是 FU-A 分片:而當 NALU 的長度超過 MTU 時,就必須對 NALU 單元進行分片封包。也稱為 Fragmentation Units (FUs).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A
FU indicator 有以下格式:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|F|NRI| Type |
+---------------+
FU 指示字節的類型域 Type=28 表示 FU-A。NRI 域的值必須根據分片 NAL 單元的 NRI 域的值設置。
FU header 的格式如下:
+---------------+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
|S|E|R| Type |
+---------------+
S: 1 bit 當設置成 1 表明這是 NALU 的首個 fragment。當跟隨的 FU 荷載不是分片 NAL 單元荷載的開始,開始位設為 0。
E: 1 bit 當設置成 1 表明是 NALU 的最後一個 fragment,即,荷載的最後字節也是分片 NAL 單元的最後一個字節。當跟隨的 FU 荷載不是分片 NAL 單元的最後分片,結束位設置為 0。
R: 1 bit 保留位必須設置為 0,接收者必須忽略該位。
Type: 5 bits
NAL 單元荷載類型定義見下表
單元類型以及荷載結構總結
.Type Packet Type name
---------------------------------------------------------
0 undefined -
1-23 NAL unit Single NAL unit packet per H.264
24 STAP-A Single-time aggregation packet
25 STAP-B Single-time aggregation packet
26 MTAP16 Multi-time aggregation packet
27 MTAP24 Multi-time aggregation packet
28 FU-A Fragmentation unit
29 FU-B Fragmentation unit
30-31 undefined
拆包和解包
拆包:當編碼器在編碼時需要將原有一個 NAL 按照 FU-A 進行分片,原有的 NAL 的單元頭與分片後的 FU-A 的單元頭有如下關係:
原始的 NAL 頭的前三位為 FU indicator 的前三位,原始的 NAL 頭的後五位為 FU header 的後五位,
FU indicator 與 FU header 的剩余位數根據實際情況決定。
** 解包:** 當接收端收到 FU-A 的分片數據,需要將所有的分片包組合還原成原始的 NAl 包時,FU-A 的單元頭與還原後的 NAL 的關係如下:
還原後的 NAL 頭的八位是由 FU indicator 的前三位加 FU header 的後五位組成,即:
nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f)