H.264視訊流分析工具
(1)SpecialVH264
軟體:
連結:https://pan.baidu.com/s/1O6UL_WDzp5zYukJvfhvixg
提取碼:ooid
源代碼:
連結:https://pan.baidu.com/s/1TsOwBwezLTXvEsYUfEzauA
提取碼:49k1
(2)基于上面的一個軟體,可以同時顯示二進制,
軟體:
連結:https://pan.baidu.com/s/1MeaMXl1qrDN83oOWH174yA
提取碼:38vv
源代碼:
連結:https://pan.baidu.com/s/19hhWwjejpP1_hRzicbs8dA
提取碼:05hz
其他視音頻編解碼分析器:
H.264說明文檔:https://en.wikipedia.org/wiki/H.264/MPEG-4_AVC
一、H264的NAL單元詳解
H.264 的功能分為兩層,即視訊編碼層(VCL:Video Coding Layer)和網絡提取層(NAL:Network Abstraction Layer)。
VCL 資料即編碼處理的輸出,它表示被壓縮編碼後的視訊資料序列。在VCL 資料傳輸或存儲之前,這些編碼的VCL 資料,先被映射或封裝進NAL 單元中。這樣,高編碼效率和網絡友好性的任務分别由VCL和NAL來完成。
1、VCL隻關心編碼部分,重點在于編碼算法以及在特定硬體平台的實作
(1)SODB 是VCL輸出的是編碼後的純視訊流資訊,沒有任何備援頭資訊
2、NAL關心的是VCL的輸出純視訊流如何被表達和封包以利于網絡傳輸,
(1)RBSP 是通過SODB封裝成nal_unit格式得到的,Nal_unit是一個通用封裝格式,可以适用于有序位元組流方式和IP包交換方式
具體封裝流程:javascript:void(0)
(2)NALU 是針對不同的傳送網絡(電路交換|包交換),将RBSP 封裝成針對不同網絡的封裝格式(加上NAL header)
3、之間關系:
SODB + RBSP trailing bits = RBSP
NAL header(1 byte) + RBSP = NALU
RTP封裝格式(12個位元組) + NALU = 最後sendto出去的完整包
RBSP就是H.264編碼後出來的裸流檔案,給檔案加上字尾.h264,得到xxx.h264

2、RBSP的内容
如何判斷幀類型(是圖像參考幀還是I、P幀等)?
nal_unit_type | NAL類型 | C |
未使用 | ||
1 | 不分區、非 IDR 圖像的片 | 2, 3, 4 |
2 | 片分區 A | 2 |
3 | 片分區 B | 3 |
4 | 片分區 C | 4 |
5 | IDR 圖像中的片 | 2, 3 |
6 | 補充增強資訊單元(SEI) | 5 |
7 | 序列參數集 (SPS) | |
8 | 圖像參數集 (PPS) | 1 |
9 | 分界符 | 6 |
10 | 序列結束 | 7 |
11 | 碼流結束 | 8 |
12 | 填充 | 9 |
13..23 | 保留 | |
24..31 | 未使用 |
我們還是接着看最上面圖的碼流對應的資料來層層分析,以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的二進制碼為:0110 0101
4-8為00101,轉為十進制5,參考第二幅圖:5對應IDR圖像中的片(I幀)
3、序列 sequence
(1) 一段h.264的碼流其實就是多個sequence組成的
(2)一個sequence是一秒,如果FPS等于30,就有30幀圖像(I/P/B幀)
(3)每個sequence均有固定結構單元:1sps+1pps+1sei+1I幀+若幹p幀(加上B幀一共有6種單元情況)
分隔符
(1) H.264在編碼的時候,生成一個序列時,序列中每個單元前面就會加上00 00 00 01作為分隔符
分隔符後第一個位元組
(1)分隔符後面緊跟着的第一個位元組就是用來判斷是什麼類型的單元
1、第1位禁止位,值為1表示文法出錯
2、第2~3位為參考級别
3、第4~8為是nal單元類型nal_unit_type(比如是0x67(取5位00111表示sps)
播放器算法解碼時,就知道這個位元組開始到下一個分隔符之間的資料按照sps類型解析資料
單元之一:sps(序列參數集:固定14個位元組)
(1)序列參數集。SPS中儲存了一組編碼視訊序列(Coded video sequence)的全局參數,所謂的編碼視訊序列即原始視訊的一幀一幀的像素資料經過編碼之後的結構組成的序列。而每一幀的編碼後資料所依賴的參數儲存于圖像參數集中
(2)裡面包含宏塊編碼方式、圖像大小尺寸、宏塊個數,播放器通過這些參數,調用播放器裡面的對應算法去解碼
單元之二:pps(圖像參數集:固定4個位元組)
單元之三:SEI
(1)也是一些圖像的額外資訊,幫助播放器解析壓縮圖像
單元之四:I幀、P幀、B幀
(1)I幀:幀内編碼幀,I幀表示關鍵幀,你可以了解為這一幀畫面的完整保留;解碼時隻需要本幀資料就可以完成(因為包含完整畫面),I幀大,說明本身壓縮比不高,圖像資料更完整,則P幀可以越小,反之I幀越小則P幀會越大
(2)P幀:前向預測編碼幀。P幀表示的是這一幀跟之前的一個關鍵幀(或P幀)的差别,解碼時需要用之前緩存的畫面疊加上本幀定義的差别,生成最終畫面。(也就是差别幀,P幀沒有完整畫面資料,隻有與前一幀的畫面差别的資料)
(3)B幀:雙向預測内插編碼幀。B幀是雙向差别幀,也就是B幀記錄的是本幀與前後幀的差别
(4)IDR幀:一個序列的第一個圖像叫做 IDR 圖像(立即重新整理圖像), IDR 圖像都是 I圖像。 H.264 引入 IDR 圖像是為了解碼的重同步,當解碼器解碼到 IDR 圖像時,立即将參考幀隊列清空,将已解碼的資料全部輸出或抛棄,重新查找參數集,開始一個新的序列。這樣,如果在前一個序列的傳輸中發生重大錯誤,如嚴重的丢包,或其他原因引起資料錯位,在這裡可以獲得重新同步。 IDR 圖像之後的圖像永遠不會引用 IDR 圖像之前的圖像的資料來解碼。
要注意 IDR 圖像和 I 圖像的差別, IDR 圖像一定是 I 圖像, 但 I 圖像不一定是 IDR 圖像。一個序列中可以有很多的 I 圖像, I 圖像之後的圖像可以引用 I 圖像之間的圖像做運動參考。
宏塊、片: 一個編碼圖像通常劃分成若幹宏塊組成,一個宏塊由一個16×16亮度像素和附加的一個8×8 Cb和一個8×8 Cr彩色像素塊組成。每個圖象中,若幹宏塊被排列成片的形式。
CBR(固定碼率)和VBR(可變碼率)(壓縮時候一個sequence參考的碼率方式)
(1)一個sequence有30個幀,每個幀就是1/30秒,壓縮後的位元組數不一樣,2000到10000+不等,主要看圖像動作是否很大
(2)CBR就是犧牲圖像清晰度,保證每幀圖像位元組數變化不會很大,這樣碼率就能均衡,網絡傳輸也能穩定
(3)VBR就是犧牲碼率,動作變化大的時候,參考VBR,幀資料會很大,保證清晰度,不管碼率是否穩定,這樣在網絡傳輸的時候,網絡帶寬有限,帶寬給的寬,資源限制,給的小,大的幀傳輸不完,比如I幀,網絡就會不穩定
在雷神的H.264分析器中可以判斷I,P,B幀
對應的代碼為:
//在 debug_slice_header 函數中
printf("======= Slice Header =======\n");
printf(" first_mb_in_slice : %d \n", sh->first_mb_in_slice );
switch(sh->slice_type)
{
case SH_SLICE_TYPE_P : slice_type_name = "P slice"; break;
case SH_SLICE_TYPE_B : slice_type_name = "B slice"; break;
case SH_SLICE_TYPE_I : slice_type_name = "I slice"; break;
case SH_SLICE_TYPE_SP : slice_type_name = "SP slice"; break;
case SH_SLICE_TYPE_SI : slice_type_name = "SI slice"; break;
case SH_SLICE_TYPE_P_ONLY : slice_type_name = "P slice only"; break;
case SH_SLICE_TYPE_B_ONLY : slice_type_name = "B slice only"; break;
case SH_SLICE_TYPE_I_ONLY : slice_type_name = "I slice only"; break;
case SH_SLICE_TYPE_SP_ONLY : slice_type_name = "SP slice only"; break;
case SH_SLICE_TYPE_SI_ONLY : slice_type_name = "SI slice only"; break;
default : slice_type_name = "Unknown"; break;
}
printf(" slice_type : %d ( %s ) \n", sh->slice_type, slice_type_name );
slice_type 表示片的類型