概念#
- SODB: 數據比特串 -> 最原始的編碼數據
- RBSP: 原始字節序列載荷 -> 在 SODB 的後面填加了結尾比特(RBSP trailing bits 一個 bit “1”)若干比特 “0”, 以便字節對齊。
- EBSP: 擴展字節序列載荷– > 在 RBSP 基礎上填加了仿校驗字節(0X03)它的原因是:在 NALU 加到 Annexb 上時,需要填加每組 NALU 之前的開始碼 StartCodePrefix, 如果該 NALU 對應的 slice 為一幀的開始則用 4 位字節表示,ox00000001, 否則用 3 位字節表示 ox000001. 為了使 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 為負荷數據類型,占 5bit;R 為重要性指示位,占 2 個 bit;最後的 F 為禁止位,占 1bit。具體如下:
- 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 幀。
由此可知,61 和 41 其實都是 P 幀(type 值為 1),只是重要級別不一樣(它們的 NRI 一個是 11BIN,一個是 10BIN)
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 的首個 fragmnet。当跟隨的 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)