1. H264 基礎概念
在 H.264/AVC 視頻編碼標准中,整個系統框架划分為如下兩個層面:
- 視頻編碼層(VCL):VCL 數據即被壓縮編碼后的視頻數據序列,負責有效表示視頻數據的內容;
- 網絡抽象層(NAL):負責將 VCL 數據封裝到 NAL 單元中,並提供頭信息,以保證數據適合各種信道和存儲介質上的傳輸。
因此平時每幀數據就是一個 NAL 單元。NAL 單元的實際格式如下:
.
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NAL Header | EBSP | NAL Header | EBSP | ...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
每個 NAL 單元都是由 1 字節 NAL header 和 若干整數字節的負荷數據 EBSP 構成。
1.1 起始碼
在網絡傳輸 H.264 數據時,一個 UDP 包就是一個 NALU,解碼器可以很方便的檢測出 NAL 分界和解碼。但是如果編碼數據存儲為一個文件,原來的解碼器將無法從數據流中分辨出每個 NAL 的起始位置和終止位置,因此 H.264 用起始碼來解決這個問題。
H.264 編碼中,在每個 NAL 前添加起始碼 0x00000001(4bytes) 或 0x000001(3bytes),解碼器在碼流中檢測到起始碼,表明前一個 NAL 的結束並開始新的一個 NAL 單元。為了防止 NAL 內部出現 0x00000001 的數據,H.264 又提出 "防止競爭 emulation prevention" 機制,在編碼完一個 NAL 時,如果檢測出有連續的兩個 0x00 字節,就在后面插入一個 0x03。當解碼器再 NAL 內部檢測到 0x000003 的數據時,就把 0x03 拋棄,恢復原始數據。
- 0x000000 >>>>>> 0x00000300
- 0x000001 >>>>>> 0x00000301
- 0x000002 >>>>>> 0x00000302
- 0x000003 >>>>>> 0x00000303
1.2 NAL Header
NAL Header 的格式如下所示:
.
0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+
|F|NRI| TYPE |
+-+-+-+-+-+-+-+-+
- F(1 bit):Forbidden_zero_bit,禁止位,編碼中默認為 0,當網絡識別此單元中存在比特錯誤時,可將其設為 1,以便接收方丟掉該單元。主要用於適應不同種類的網絡環境(比如有線無線相結合的環境)。例如對於從無線到有線的網關,一邊是無線的非 IP 環境,一邊是有線網絡的無比特錯誤的環境。假設一個 NAL 單元達到無線那邊時,校驗和檢測失敗,網關可以選擇從 NAL 流中去掉這個 NAL 單元,也可以把已知被破壞的 NAL 單元傳給接收端。在這種情況下,智能的解碼器將嘗試重構這個 NAL 單元(已知它可能包含比特錯誤)。而非智能的解碼器將簡單的拋棄這個 NAL 單元。
- NRI(2 bits):Nal_ref_idc,重要性指示位,用於在重構過程中標記一個 NAL 單元的重要性,值越大,越重要。值為 0 表示這個 NAL 單元沒有用於預測,因此可被解碼器拋棄而不會有錯誤擴散;值高於 0 表示此 NAL 單元要用於無漂移重構,且值越高,對此 NAL 單元丟失的影響越大。例如,若當前 NAL 屬於參考幀的片,或是序列參數集,或是圖像參數集這些重要的單位時,該值必須大於 0.
- TYPE(5 bits):表示當前 NAL 單元的類型,類型 1~12 是 H.264 定義的,類型 24~31 是用於 H.264 以外的,RTMP 符合規范使用這其中的一些值來定義包聚合和分裂,其他值為 H.264 保留。
TYPE 的取值如下表所示:
1.3 EBSP
- SODB:數據比特串,最原始的編碼數據
- RBSP:原始字節序列載荷,在 SODB 的后面填加了結尾比特(RBSP trailing bits,一個 bit "1")若干比特 "0",以便字節對齊。
- EBSP:擴展字節序列載荷,在 RBSP 基礎上填加了仿校驗字節(0x03),它的原因是:在 NALU 加到 Annexb 上時,需要添加每組 NALU 之前的開始碼 StartCodePrefix,如果該 NALU 對應的 slice 為一幀的開始(即為 IDR 幀)則用 4 位字節表示:0x00000001,否則用 3 位字節表示:0x000001。為了使 NALU 主體中不包括與開始碼相沖突的,在編碼時,每遇到兩個字節連續為 0,就插入一個字節的 0x03。解碼時將 0x03 去掉。也稱為 "脫殼操作"。
H.264 的具體封裝過程如下:
- 第一步:將 VCL 層輸出的 SODB 封裝成 nal_unit,nal_unit 是一個通用的封裝格式,可以適用於有序字節流方式和 IP 包交換方式。
- 第二步:針對不同的傳送網絡(電路交換/包交換),將 nal_unit 封裝成針對不同網絡的封裝格式。
第一步具體過程
VCL 層輸出的比特流 SODB(String Of Data Bits)到 nal_unit 之間,經過以下三步處理:
- SODB 字節對齊處理后封裝成 RBSP(Raw Byte Sequence Payload)。
- 為防止 RBSP 的字節流與有序字節流傳送方式下的 SCP (start_code_prefix_one_3bytes,0x000001)出現字節競爭情形,循環檢測 RBSP 前三個字節,在出現字節競爭時在第三個字節前加入 emulation_prevention_three_byte(0x03),具體如下:
nal_unit(NumBytesInNALunit)
{
forbidden_zero_bit;
nal_ref_idc;
nal_unit_type;
NumBytesInRBSP = 0;
for (i = 1; i < NumBytesInNALunit; i++)
{
if (i + 2 < NumBytesInNALunit && next_bits(24) == 0x000003)
{
rbsp_byte[ NumBytesInRBSP++ ];
rbsp_byte[ NumBytesInRBSP++ ];
i += 2;
emulation_prevention_three_byte; /* equal to 0x03 */
}
else
{
rbsp_byte[ NumBytesInRBSP++ ]
}
}
}
- 防止字節競爭處理后的 RBSP(即此時為 EBSP 了)再加上一個字節的 NAL Header(forbidden_zero_bit + nal_ref_idc + nal_unit_type),封裝成 nal_unit。
第二步的具體過程
case 1:有序字節流的封裝
byte_stream_nal_unit( NumBytesInNALunit )
{
while( next_bits( 24 ) != 0x000001 )
zero_byte /* equal to 0x00 */
if( more_data_in_byte_stream( ) )
{
start_code_prefix_one_3bytes /* equal to 0x000001 */ nal_unit( NumBytesInNALunit )
}
}
類似H.320和MPEG-2/H.222.0等傳輸系統,傳輸NAL作為有序連續字節或比特流,同時要依靠數據本身識別NAL單元邊界。在這樣的應用系統中,H.264/AVC規范定義了字節流格式,每個NAL單元前面增加3個字節的前綴,即同步字節。在比特流應用中,每個圖像需要增加一個附加字節作為邊界定位。還有一種可選特性,在字節流中增加附加數據,用做擴充發送數據量,能實現快速邊界定位,恢復同步.
case 2: IP 網絡的 RTP 打包封裝
分組打包的規則:
- 額外的開銷要小,使 MTU 尺寸在 100~64K 字節范圍都可以;
- 不用對分組內的數據解碼就可以判別分組的重要性;
- 載荷規范應當保證不用解碼就可識別由於其他的比特丟失而造成的分組不可解碼;
- 支持將 NALU 分割成多個 RTP 分組;
- 支持將多個 NALU 匯集在一個 RTP 分組中。
RTP 的頭標可以是 NALU 的頭標,並可以實現以上的打包規則。
一個 RTP 分組里放入一個 NALU,將 NALU(包括同時作為載荷頭標的 NALU 頭)放入 RTP 載荷中,設置 RTP 頭標值。為了避免 IP 層對大分組的再一次分割,片分組的大小一般都要小於 MTU 尺寸。由於包傳送的路徑不同,解碼端要重新對片分組排序,RTP 包含的次序信息可以用來解決這一問題。
NALU 分割
對於預先已經編碼的內容,NALU 可能大於 MTU 尺寸的限制。雖然 IP 層的分割可以使數據塊小於 64k 字節,但無法在應用層實現保護,從而降低了非等重保護方案的效果。由於 UDP 數據包小於 64k 字節,而且一個片的長度對某些應用場合來說太小,所以應用層打包是 RTP 打包方案的一部分。
新的討論方案(IETF)應當符合以下特征:
- NALU 的分塊以按 RTP 次序號升序傳輸;
- 能夠標記第一個和最后一個 NALU 分塊;
- 可以檢測丟失的分塊
NALU 合並
一些 NALU 如 SEI 、參數集等非常小,將他們合並在一起有利於減少頭標開銷。已有兩種集合分組:
- 單一時間集合分組(STAP),按時間戳進行組合
- 多時間集合分組(MTAP),不同時間戳也可以組合
NAL 規范視頻數據的格式,主要是提供頭部信息,以適合各種媒體的傳輸和存儲。NAL支持各種網絡,包括:
- 任何使用 RTP/IP 協議的實時有線和無線 Internet 服務
- 作為 MP4 文件存儲和多媒體信息文件服務
- MPEG-2 系統
- 其他網
NAL 規定一種通用的格式,既適合面向包傳輸,也適合流傳送。實際上,包傳輸和流傳輸的方式是相同的,不同之處是傳輸前面增加了一個起始碼前綴在類似 Internet/RTP 面向包傳送協議系統中,包結構中包含包邊界識別字節,在這種情況下,不需要同步字節。
NAL 單元分為 VCL 和 非VCL 兩種:
- VCL NAL 單元包含視頻圖像采樣信息
- 非 VCL 包含各種有關的附加信息,例如參數集(頭部信息,應用到大量的 VCL NAL 單元)、提供性能的附加信息、定時信息等
參數集
參數集是很少變化的信息,用於大量 VCL NAL 單元的解碼,分為兩種類型:
- 序列參數集(SPS: 7),作用於一串連續的視頻圖像,即視頻序列。兩個 IDR 圖像之間為序列參數。
- 圖像參數集(PPS: 8),作用於視頻序列中的一個或多個個別的圖像。
序列和圖像參數集機制,減少了重復參數的傳送,每個 VCL NAL 單元包含一個標識,指向有關的圖像參數集,每個圖像參數集包含一個標識,指向有關的序列參數集的內容。因此,只用少數的指針信息,應用大量的參數,大大減少每個 VCL NAL 單元重復傳送的信息。
序列和圖像參數集可以在發送 VCL NAL 單元以前發送,並且重復傳送,大大提高糾錯能力。序列和圖像參數集可以在"帶內",也可以用更為可靠的其他"帶外" 通道傳送。
2. I幀、B幀和 P幀
視頻壓縮中,每一幀代表一副靜止的圖像。而在實際壓縮時,會采取各種算法減少數據的容量,其中 IPB 就是最常見的。
簡單地說,I幀 就是關鍵幀,屬於幀內壓縮。P 是向前搜索的意思。B 是雙向搜索。它們都是基於 I幀 來壓縮數據。
- I幀:表示關鍵幀,可以理解為 這一幀畫面的完整保留,解碼時只需要本幀數據就可以完成(因為包含完整畫面)。
- P幀:表示的是這一幀跟之前的一個關鍵幀(或 P幀)的差別,解碼時需要用之前緩存的畫面疊加上本幀定義的差別,生成最終畫面。(也就是差別幀,P幀 沒有完整畫面數據,只有與前一幀的畫面差別的數據)。
- B幀:是雙向差別的幀,也就是 B幀 記錄的是本幀與前后幀的差別(具體比較復雜,有四種情況)。換言之,要解碼 B幀,不僅要取得之前的緩存畫面,還要本幀之后的畫面,通過前后畫面與本幀數據的疊加取得最終的畫面。B幀 壓縮率高,但是解碼時 CPU 會比較累。
采用的壓縮方法:分組,將幾幀圖像分為一組(GOP),為防止運動變化,幀數不宜取多。
- 定義幀:將每組內各幀圖像定義為三種類型,即 I幀,P幀,B幀;
- 預測幀:以 I幀 為基礎幀,以 I幀 預測 P幀,再由 I幀 和 P幀 預測 B幀;
- 數據傳輸:最后將 I幀 數據與預測的差值信息進行存儲和傳輸。
2.1 I幀
I 圖像(幀)是靠盡可能去除圖像空間冗余信息來壓縮傳輸數據量的幀內編碼圖像。
I幀 又稱為內部畫面(intra picture),I幀 通常是每個 GOP(MPEG 所使用的一種視頻壓縮技術)的第一個幀,經過適度地壓縮(作為隨機訪問的參考點)可以當成是圖像。在 MPEG 編碼的過程中部分視頻幀序列壓縮成為 I幀,部分壓縮成 P幀,還有部分壓縮成 B幀。I幀 法是幀內壓縮法(P、B 為幀間),也稱為"關鍵幀"壓縮法。I幀 法是基於離散余弦變換 DCT(Discrete Cosine Transform)的壓縮技術,這種算法與 JPEG 壓縮算法類似。采用I幀壓縮可達到 1/6 的壓縮比而無明顯的壓縮痕跡。
2.1.1 I幀特點
- 它是一個全幀壓縮編碼幀。它將全幀圖像信息進行 JPEG 壓縮編碼及傳輸;
- 解碼時僅用 I幀 的數據就可以重構完整圖像;
- I幀 描述了圖像背景和運動主體的詳情;
- I幀 是 P幀 和 B幀 的參考幀(其質量直接影響到同組中以后各幀的質量);
- I幀 是幀組 GOP 的基礎幀(第一幀),在一組中只有一個 I幀;
- I幀 不需要考慮運動矢量;
- I幀 所占數據的信息量比較大。
2.2 P幀
P圖像(幀)是通過充分降低圖像序列中前面已編碼幀的時間冗余信息來壓縮傳輸數據量的編碼圖像,也叫預測幀。
在針對連續動態圖像編碼時,將連續若干幅圖像分成 P,B,I 三種類型,P幀 由在它前面的 P幀 或 I幀 預測而來,它比較與它前面的 P幀 或 I幀 之間的相同信息或數據,也即考慮運動的特性進行幀間壓縮。P幀 法是根據本幀與相鄰的前一幀(I幀 或 P幀)的不同點來壓縮本幀數據。采取 P幀 和 I幀 聯合壓縮的方法可達到更高的壓縮且無明顯的壓縮痕跡。
P幀的預測和重構:
P 幀是以 I 幀為參考幀,在 I 幀中找出 P 幀"某點"的預測值和運動矢量,取預測差值和運動矢量一起傳送。在接收端根據運動矢量從 I 幀中找出 P 幀"某點"的預測值並與差值相加以得到 P 幀"某點"樣值,從而得到完整的 P 幀。
2.2.1 P 幀特點
- P 幀是 I 幀后面相隔 1~2 幀的編碼幀;
- P 幀采用運動補償的方法傳送與它前面的 I 幀或 P 幀的差值及運動矢量(預測誤差);
- 解碼時必須將 I 幀中的預測值與預測誤差求和后才能重構完整的 P 幀圖像;
- P 幀屬於前向預測的幀間編碼。它只參考前面最靠近它的 I 幀或 P 幀;
- P 幀可以是其后面 P 幀的參考幀,也可以是前后 B 幀的參考幀;
- 由於 P 幀是參考幀,它可能造成解碼錯誤的擴散;
- 由於是差值傳送,P 幀的壓縮比較高。
2.3 B 幀
B 圖像(幀)是既考慮與源圖像序列前面已編碼幀,也顧及源圖像序列后面已編碼幀之間的時間冗余信息來壓縮傳輸數據量的編碼圖像,也叫雙向預測幀。
B 幀法是雙向預測的幀間壓縮法。當把一幀圖像壓縮成 B 幀時,它根據相鄰的前一幀、本幀以及后一幀數據的不同點來壓縮本幀,也即僅記錄本幀與前后幀的差值。只有采用 B 幀壓縮才能達到 200:1 的高壓縮。一般地,I 幀壓縮效率最低,P 幀較高,B 幀最高。
B 幀的預測和重構:
B 幀以前面的 I 幀或 P 幀以及后面的 P 幀為參考幀,"找出" B 幀 "某點" 的預測值和兩個運動矢量,並取預測值和運動矢量傳送。接收端根據運動矢量在兩個參考幀中 "找出(算出)" 預測值並與差值求和,得到 B 幀 "某點" 樣值,從而得到完整的 B 幀。
2.3.1 B 幀特點
- B 幀是由前面的 I 幀或 P 幀和后面的 P 幀來進行預測的;
- B 幀傳送的是它與前面的 I 幀或 P 幀和后面的 P 幀之間的預測誤差及運動矢量;
- B 幀是雙向預測編碼幀;
- B 幀壓縮比最高,因為它只反映兩參考幀間運動主體的變化情況,預測比較准確;
- B 幀不是參考幀,不會造成解碼錯誤的擴散。
3. DTS 和 PTS
- dts:Decode Time Stamp。dts 主要是標識讀入內存中的 bit 流在什么時候送入解碼器中進行解碼。
- pts:Presentation Time Stamp。pts 主要用於度量解碼后的視頻幀什么時候被顯示出來。
在沒有 B 幀的情況下 dts 的順序和 pts 的順序是一樣的。
dts 和 pts 的不同:
dts 主要用於視頻的解碼,在解碼階段使用。pts 主要用於視頻的同步和輸出,在 display 的時候使用。在沒有 B frame 的情況下,dts 和 pts 的輸出順序是一樣的。
例子:
下面給出一個 GOP 為 15 的例子,其解碼的參照 frame 及其解碼的順序都在里面:
如上圖,I 幀的解碼不依賴於任何的其它的幀,而 P 幀的解碼則依賴於前面的 I 幀或者 P 幀。B 幀的解碼則依賴於其前的最近的一個 I 幀或者 P 幀及其后的最近的一個 P 幀。
4. 語法層次結構分析
TaigaComplex 寫的 h.264語法結構分析。