FFmpeg學習(六)H264流媒體協議解析


一:H264了解(可跳過)

(一)H.264的主要目標

1.高的視頻壓縮比;2.良好的網絡親和性;

為了完成這些目標H264的解決方案是:

1.VCL   video coding layer    視頻編碼層;    視頻編碼層,H264編碼/壓縮的核心,主要負責將視頻數據編碼/壓縮,再切分。
2.NAL   network abstraction layer   網絡提取層;   網絡抽象層,負責將VCL的數據組織打包。

其中,VCL層是對核心算法引擎,宏塊及片的語法級別的定義,他最終輸出編碼完的數據 SODB;

1.壓縮:預測(幀內預測和幀間預測)-> DCT變化和量化 -> 比特流編碼;
2.切分數據,主要為了第三步。這里一點,網上看到的“切片(slice)”、“宏塊(macroblock)”是在VCL中的概念,一方面提高編碼效率和降低誤碼率、另一方面提高網絡傳輸的靈活性。(這塊本文將不展開)
3.輸出編碼完的數據 SODB

NAL層定義片級以上的語法級別(如序列參數集和圖像參數集,針對網絡傳輸),

同時支持以下功能:獨立片解碼,起始碼唯一保證,SEI以及流格式編碼數據傳送,NAL層將SODB打包成RBSP然后加上NAL頭,組成一個NALU(NAL單元);

NAL旨在提供“網絡友好性”,以便為各種系統簡單有效地定制VCL的使用。 NAL有助於將VCL數據映射到傳輸層,例如:
· RTP / IP適用於任何類型的實時有線和無線互聯網服務。
· 文件格式,例如用於存儲和MMS的ISO MP4。
· H.32X用於有線和無線會話服務。
· 用於廣播服務等的MPEG-2系統。

(二)名詞解釋(和下面沒關系,可以忽略)

H.264 中,句法元素共被組織成 序列、圖像、片、宏塊、子宏塊五個層次。

H.264 的碼流結構並沒有大家想的那么復雜,編碼后視頻的每一組圖像(GOP,圖像組)都給予了傳輸中的序列(PPS)和本身這個幀的圖像參數(SPS),所以,我們的整體結構,應該如此:

GOP (圖像組)主要用作形容一個 i 幀 到下一個 i 幀之間的間隔了多少個幀,增大圖片組能有效的減少編碼后的視頻體積,但是也會降低視頻質量,至於怎么取舍,得看需求了。
注意: GOP是不包括SPS、PPS等NALU的
GOP 就是一段連續圖像幀且畫面之間變化不大。//當某個幀與前面的幀圖像變化很大,無法參考前面幀生成時,就要開始一個新序列。

補充:
宏塊:宏塊大小通常為16×16像素,分為I、B、P宏塊;編碼處理的基本單元,由多個塊組成。
:一個編碼圖像要划分成多個塊才能進行處理,一個塊是4X4像素

------1.一幀圖片跟 NALU 的關聯 :

一幀圖片經過 H.264 編碼器之后,就被編碼為一個或多個片(slice)。<編碼階段,由VCL實現>

而裝載着這些片(slice)的載體,就是 NALU 了,我們可以來看看 NALU 跟片的關系(slice)。<由NAL實現>

注意:實際中NAL還要對切片進行處理,然后形成NALU。

片(slice)的概念不同與幀(frame),幀(frame)是用作描述一張圖片的,一幀(frame)對應一張圖片,而片(slice),是 H.264 中提出的新概念,是通過編碼圖片后切分通過高效的方式整合出來的概念,一張圖片至少有一個或多個片(slice)。

上圖中可以看出,片(slice)都是又 NALU 裝載並進行網絡傳輸的,但是這並不代表 NALU 內就一定是切片,這是充分不必要條件,因為 NALU 還有可能裝載着其他用作描述視頻的信息,比如后面提到的SPS、PPS。

------2.什么是切片(slice)?

片的主要作用是用作宏塊(Macroblock)的載體(ps:下面會介紹到宏塊的概念)。片之所以被創造出來,主要目的是為限制誤碼的擴散和傳輸。

如何限制誤碼的擴散和傳輸?

每個片(slice)都應該是互相獨立被傳輸的,某片的預測(片(slice)內預測和片(slice)間預測)不能以其它片中的宏塊(Macroblock)為參考圖像。

我們可以理解為一 張/幀 圖片可以包含一個或多個分片(Slice),而每一個分片(Slice)包含整數個宏塊(Macroblock),即每片(slice)至少一個 宏塊(Macroblock),最多時每片包 整個圖像的宏塊。
上圖結構中,我們不難看出,每個分片也包含着頭和數據兩部分:
1、分片頭中包含着分片類型、分片中的宏塊類型、分片幀的數量、分片屬於那個圖像以及對應的幀的設置和參數等信息。
2分片數據中則是宏塊,這里就是我們要找的存儲像素數據的地方。

------3.什么是宏塊?

宏塊是視頻信息的主要承載者,因為它包含着每一個像素的亮度和色度信息視頻解碼最主要的工作則是提供高效的方式從碼流中獲得宏塊中的像素陣列。
組成部分:
  一個宏塊由一個16×16亮度像素附加的一個8×
8 Cb和一個 8×8 Cr 彩色像素塊組成。
  每個圖象中,若干宏塊被排列成片的形式。

從上圖中,可以看到,宏塊中包含了宏塊類型、預測類型、Coded Block Pattern 編碼的塊模式Quantization Parameter 量化參數、像素的亮度和色度數據集等等信息。

------4. 切片(slice)類型跟宏塊類型的關系

I片:只包 I宏塊,I 宏塊利用從當前片中已解碼的像素作為參考進行幀內預測(不能取其它片中的已解碼像素作為參考進行幀內預測)。

P片:可包 P和I宏塊,P 宏塊利用前面已編碼圖象作為參考圖象進行幀內預測,一個幀內編碼的宏塊可進一步作宏塊的分割:即 16×16、16×8、8×16 或 8×8 亮度像素塊(以及附帶的彩色像素);如果選了 8×8 的子宏塊,則可再分成各種子宏塊的分割,其尺寸為 8×8、8×4、4×8 或 4×4 亮度像素塊(以及附帶的彩色像素)。

B片:可包 B和I宏塊,B 宏塊則利用雙向的參考圖象(當前和 來的已編碼圖象幀)進行幀內預測。

SP片(切換P):用於不同編碼流之間的切換,包含 P 和/或 I 宏塊

SI片:擴展檔次中必須具有的切換,它包 了一種特殊類型的編碼宏塊,叫做 SI 宏塊,SI 也是擴展檔次中的必備功能。

二:H264的格式

詳見更多:https://blog.csdn.net/qq_42024067/article/details/102292535 

H.264的兩種打包/封裝方法:字節流AnnexB格式 和 AVCC格式 (只有這兩種)

其中在https://www.yuque.com/keith-an9fr/aab7xp/vng2pb中介紹的應該是有部分問題!!!

在H264用於網絡發送時要封裝成RTP格式!!!

(一)AnnexB格式---用於實時播放

------1.AnnexB流結構:使用start code分隔NAL(start code為三字節或四字節,0x000001或0x00000001,一般是四字節);SPS和PPS按流的方式寫在頭部

開始前綴(00000001或000001)+ NALU數據  絕大部分編碼器的默認輸出格式

NALU 就是 h264的實際數據部分。NALU = NALUHeader+EBSP 組成; EBSP = 防止競爭碼+RBSP; RBSP = SODB + RBSP尾部 。

EBSP為擴展字節序列載荷(Encapsulated Byte Sequence Payload)  EBSP = RBSP插入防競爭字節0x03
RBSP為原始字節序列載荷(Raw Byte Sequence Payload)-------- RBSP = SODB + RBSP Trailing Bits(RBSP尾部補齊字節);引入RBSP Trailing Bits做8位字節補齊。
SODB為原始數據比特流 (String Of Data Bits)   -------   就是最原始的編碼/壓縮得到的數據。

H264碼流結構:(下面兩種表示都不錯)

一共有兩種起始碼start_code:

   ①3字節0x000001  單幀多slice(即單幀多個NALU)之間間隔    ②4字節0x00000001 幀之間,或者SPS、PPS等之前

4字節類型的起始碼在連續的數據傳輸中非常有用,因為用字節來對齊、分割流數據,比如:用連續的31個bit 0 后接一個bit 1 來分割流數據,是很容易的。

如果接下來的bit是0(因為每個NALU都以bit0開始),那么這就是一個NALU包數據的起始位置了。4字節類型的開始碼通常只用於標識流中的隨機訪問點,如SPS PPS AUD和IDR,然后其他地方都用3字節類型的開始碼以減少數據量

防止競爭字節(0x03:前面講到用StartCode的字節串來分割NALU,於是問題來了,如果RBSP中也包括了StartCode(0x000001或0x00000001)怎么辦呢?所以,就有了防止競爭字節(0x03):

編碼時,掃描RBSP,如果遇到連續兩個0x00字節,就在后面添加防止競爭字節(0x03);
解碼時,同樣掃描EBSP,進行逆向操作即可。

編碼如下:

解碼操作:

在解碼的時候如果在內部遇到0x000003序列時,就可以將其拋棄即可以恢復原始數據。EBSP 去除防止競爭碼后就可以得到 RBSP。

------2.NALU Header

上面是截取的 NAL 層的語法頭部部分。如果先不考慮語法,可以先如下理解,將第一個字節(1+2+5 正好是一個字節)按 bits 展開。

  • 第一位為 forbidden_zero_bit forbidden_zero_bit 禁止位,初始為0,當網絡發現NAL單元有比特錯誤時可設置該比特為1,以便接收方糾錯或丟掉該單元。
  • 后兩位為 nal_ref_idc nal_ref_idc 代表 NALU 的重要性。值越大說明約重要。取值范圍0~3,解碼器在解碼處理不過來的時候,可以丟掉重要性為0的NALU。當前的 NAL 是參考幀,序列集參數集或圖像集重要數據時必須大於0。
  • 最后五位為 nal_unit_type 指的是當前 NAL 的類型。

1-4:I/P/B幀,合起來介紹的原因是,他們是依據VLC的slice區分的,這塊因為本文不涉及,一方面是這個太過於細節,真要展開篇幅太長;另一個原因是就算不了解slice、macroblock也不影響對H264格式的理解。
5IDR幀。I幀的一種,告訴解碼器,之前依賴的解碼參數集合(接下來要出現的SPS\PPS等)可以被刷新了。 6:SEI,英文全稱Supplemental Enhancement Information,翻譯為“補充增強信息”,提供了向視頻碼流中加入額外信息的方法。
7:SPS,全稱Sequence Paramater Set,翻譯為“序列參數集”。SPS中保存了一組編碼視頻序列(Coded Video Sequence)的全局參數。因此該類型保存的是和編碼序列相關的參數。
8: PPS,全稱Picture Paramater Set,翻譯為“圖像參數集”。該類型保存了整體圖像相關的參數。
9AU分隔符,AU全稱Access Unit,它是一個或者多個NALU的集合,代表了一個完整的幀。

根據以上字段:判斷類型

例如上面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幀)

其中0x41的二進制碼為:0100 0001

4-8為00001,轉為十進制1,參考第二幅圖:1對應非IDR圖像中的片(這里指的是P幀)

其中0x61的二進制碼為:0110 0001

4-8為00001,轉為十進制1,參考第二幅圖:1對應非IDR圖像中的片(同上,為P幀,僅僅是重要性不同)

其中0x06的二進制碼為:0000 0100

4-8為00100,轉為十進制6,參考第二幅圖:6對應SEI

特殊的NALU類型:SPS和PPS

SPS和PPS存儲了編解碼需要一些圖像參數。

AnnexB格式每個NALU都包含起始碼,且通常會周期性的在關鍵幀之前重復SPS和PPS (在I幀之前)
  👉👉👉所以解碼器可以從視頻流隨機點開始進行解碼,實時的流格式

I/P/B幀:

I幀(幀內編碼幀)是一種自帶全部信息的獨立幀無需參考其它圖像便可獨立進行解碼。可以理解為這一幀畫面的完整保留;解碼時只需要本幀數據就可以完成(因為包含完整畫面)

視頻序列中的第一個幀始終都是I幀。
如果所傳輸的比特流遭到破壞,則需要將I幀用作新查看器的起始點或重新同步點
I幀可以用來實現快進、快退以及其它隨機訪問功能。
1.它是一個全幀壓縮編碼幀。它將全幀圖像信息進行JPEG壓縮編碼及傳輸;
2.解碼時僅用I幀的數據就可重構完整圖像;
3.I幀描述了圖像背景和運動主體的詳情;
4.I幀不需要參考其他畫面而生成;
5.I幀P幀和B幀的參考幀(其質量直接影響到同組中以后各幀的質量);
6.I幀是幀組GOP的基礎幀(第一幀),在一組中只有一個I幀;
7.I幀不需要考慮運動矢量;
8.I幀所占數據的信息量比較大。

P幀(幀間預測編碼幀)需要參考前面的I幀和/或P幀的不同部分才能進行編碼。

與I幀相比,P幀通常占用更少的數據位,但其缺點是,由於P幀對前面的P和I參考幀有着復雜的依賴性,因此對傳輸錯誤非常敏感
P幀屬於前向預測的幀間編碼,它只參考 前面最靠近它 的I幀或者P幀
P幀是以I幀為參考幀,在I幀中找出P幀“某點”的預測值和運動矢量,取預測差值和運動矢量一起傳送。
在接收端根據運動矢量從I幀中找出P幀“某點”的預測值並與差值相加以得到P幀“某點”樣值,從而可得到完整的P幀。

1.P幀是I幀后面相隔1~2幀的編碼幀;
2.P幀采用運動補償的方法傳送它與前面的IP幀的差值及運動矢量(預測誤差);
3.解碼時必須將I幀中的預測值與預測誤差求和后才能重構完整的P幀圖像;
4.P幀屬於前向預測的幀間編碼。它只參考前面最靠近它的I幀或P幀;
5.P幀可以是其后面P幀的參考幀,也可以是前后 的B幀的參考幀;
6.由於P幀是參考幀,它可能造成解碼錯誤的擴散;
7.由於是差值傳送,P幀的壓縮比較高

B幀:雙向預測內插編碼幀。

B幀是雙向差別幀,也就是B幀記錄的是本幀與前后幀的差別(具體比較復雜,有4種情況,但我這樣說簡單些),
換言之,要解碼B幀,不僅要取得之前的緩存畫面,還要解碼之后的畫面,通過前后畫面與本幀數據的疊加取得最終的畫面。B幀壓縮率高,但是解碼時CPU會比較累
1.B幀是前面的I或P幀后面的P幀 來進行預測的;
2.B幀傳送的是它與前面的I或P幀和后面的P幀之間預測誤差運動矢量;
3.B幀是雙向預測編碼幀;
4.B幀壓縮比最高,因為它只反映丙參考幀間運動主體的變化情況,預測比較准確;
5.B幀不是參考幀,不會造成解碼錯誤的擴散。

I、B、P各幀是根據壓縮算法的需要,是人為定義的,它們都是實實在在的物理幀。一般來說,I幀的壓縮率是7(跟JPG差不多),P幀是20,B幀可以達到50。可見使用B幀能節省大量空間,節省出來的空間可以用來保存多一些I幀,這樣在相同碼率下,可以提供更好的畫質。

AU分隔:

一個單獨的NALU包、或者甚至一個VCL NALU包都不意味着是一個獨立的幀,一幀數據可以被分割成幾個NALU,一個或多個NALU組成了一個Access Units(AU),AU包含了一個完整的幀。把幀分割成幾個獨立的NALU需要耗費許多CPU資源,所以分割幀數據並不經常使用。實際上AU分隔不常用:http://www.360doc.com/content/13/0913/15/13084517_314201133.shtml

案例:分析一幀數據(查看了其他視頻,好像都是以大端方式存放:高地址放低位,比如 0000 0001,按2字節存放時,就是高地址<越往后越高>放低位01,低地址存放高位00)

0000 0001 6764 000a ac72 8444 2684 0000
0300 0400 0003 00ca 3c48 9611 8000 0000
0168 e843 8f13 2130 0000 0165 8881 0005
4e7f 87df 61a5 8b95 eea4 e938 b76a 306a
71b9 5560 0b76 2eb5 0ee4 8059 27b8 67a9
6337 5e82 2055 fbe4 6ae9 3735 72e2 2291
9e4d ff60 86ce 7e42 b795 ce2a e126 be87
7384 26ba 1636 f4e6 9f17 dad8 6475 54b1
f345 0c0b 3c74 b39d bceb 5373 87c3 0e62
4748 62ca 59eb 863f 3afa 86b5 bfa8 6d06
1650 82c4 ce62 9e4e e64c c730 3ede a10b
d883 0bb6 b828 bca9 eb77 43fc 7a17 9485
21ca 376b 3095 b546 7730 60b7 12d6 8cc5
5485 29d8 69a9 6f12 4e71 dfe3 e2b1 6b6b
bf9f fb2e 5730 a969 76c4 46a2 dffa 91d9
5074 551d 4904 5a1c d686 687c b661 486c
96e6 124c 27ad bac7 5199 8ed0 f0ed 8ef6
6579 79a6 12a1 95db c8ae e3b6 35e6 8dbc
48a3 7faf 4a28 8a53 e27e 6808 9f67 7798
52db 5084 d65e 25e1 4a99 5834 c711 d643
ffc4 fd9a 4416 d1b2 fb02 dba1 8969 34c2
3255 98f9 9bb2 313f 4959 0c06 8cdb a5b2
9d7e 122f d087 9444 e40a 76ef 992d 9118
3950 3b29 3bf5 2c97 7348 9183 b0a6 f34b
702f 1c8f 3b78 23c6 aa86 4643 1dd7 2a23
5e2c d948 0af5 f52c d1fb 3ff0 4b78 37e9
45dd 72cf 8035 c395 07f3 d906 e54a 5876
036c 8120 6245 6544 73bc fec1 9f31 e5db
895c 6b79 d868 90d7 26a8 a188 8681 dc9a
4f40 a523 c7de be6f 76ab 7916 5121 6783
2ef3 d627 1a42 c294 d15d 6cdb 4a7a e2cb
0bb0 680b be19 5900 50fc c0bd 9df5 f5f8
a817 19d6 b3e9 74ba 50e5 2c45 7bf9 93ea
5af9 a930 b16f 5b36 241e 8d55 57f4 cc67
b265 6aa9 3626 d006 b8e2 e373 8bd1 c01c
5215 cab5 ac60 3e36 42f1 2cbd 9977 aba8
a9a4 8e9c 8b84 de73 f091 2997 aedb afd6
f85e 9b86 b3b3 03b3 ac75 6fa6 1169 2f3d
3ace fa53 8660 956c bbc5 4ef3 
pic.h264
with open("pic.h264","wb") as fw:
    with open("pic.txt","r") as fr:
        line = fr.readline();
        while line:
            for st in line.split(" "):
                if st=="" or st=="\n":
                    continue
                fw.write(int(st,16).to_bytes(1,"big"));
            line = fr.readline();
附送python代碼字符串轉字節

 

這是一個完整的訪問單元(AU),包括3個NALU包,如你所見,數據序列以開始碼開始,后面接了一個SPS(SPS 以0x67開始),在SPS中,你可以看到有2個防競爭字節。

沒有這些字節那么非法的數據序列就會出現在這些位置。

然后可以看到一個開始碼后面接着一個PPS(PPS 以0x68開始),然后是一個最后的開始碼,后面跟着一個IDR包。

這是一個完整的H.264流,如果你把這些數據以16進制的方式保存到一個以.264為后綴名的文件中,可以把這些數據顯示為上面的圖片。

0000 0001 (start code) 67 (SPS) 64 000a ac72 8444 2684 0000
03 (防競爭字節) 00 0400 0003 (防競爭字節) 00ca 3c48 9611 8000 0000
01 (start code) 68 (PPS) e843 8f13 2130 0000 01 (start code) 65 (I幀) 8881 0005
4e7f 87df 61a5 8b95 eea4 e938 b76a 306a
71b9 5560 0b76 2eb5 0ee4 8059 27b8 67a9
6337 5e82 2055 fbe4 6ae9 3735 72e2 2291
9e4d ff60 86ce 7e42 b795 ce2a e126 be87
7384 26ba 1636 f4e6 9f17 dad8 6475 54b1
f345 0c0b 3c74 b39d bceb 5373 87c3 0e62
4748 62ca 59eb 863f 3afa 86b5 bfa8 6d06
1650 82c4 ce62 9e4e e64c c730 3ede a10b
d883 0bb6 b828 bca9 eb77 43fc 7a17 9485
21ca 376b 3095 b546 7730 60b7 12d6 8cc5
5485 29d8 69a9 6f12 4e71 dfe3 e2b1 6b6b
bf9f fb2e 5730 a969 76c4 46a2 dffa 91d9
5074 551d 4904 5a1c d686 687c b661 486c
96e6 124c 27ad bac7 5199 8ed0 f0ed 8ef6
6579 79a6 12a1 95db c8ae e3b6 35e6 8dbc
48a3 7faf 4a28 8a53 e27e 6808 9f67 7798
52db 5084 d65e 25e1 4a99 5834 c711 d643
ffc4 fd9a 4416 d1b2 fb02 dba1 8969 34c2
3255 98f9 9bb2 313f 4959 0c06 8cdb a5b2
9d7e 122f d087 9444 e40a 76ef 992d 9118
3950 3b29 3bf5 2c97 7348 9183 b0a6 f34b
702f 1c8f 3b78 23c6 aa86 4643 1dd7 2a23
5e2c d948 0af5 f52c d1fb 3ff0 4b78 37e9
45dd 72cf 8035 c395 07f3 d906 e54a 5876
036c 8120 6245 6544 73bc fec1 9f31 e5db
895c 6b79 d868 90d7 26a8 a188 8681 dc9a
4f40 a523 c7de be6f 76ab 7916 5121 6783
2ef3 d627 1a42 c294 d15d 6cdb 4a7a e2cb
0bb0 680b be19 5900 50fc c0bd 9df5 f5f8
a817 19d6 b3e9 74ba 50e5 2c45 7bf9 93ea
5af9 a930 b16f 5b36 241e 8d55 57f4 cc67
b265 6aa9 3626 d006 b8e2 e373 8bd1 c01c
5215 cab5 ac60 3e36 42f1 2cbd 9977 aba8
a9a4 8e9c 8b84 de73 f091 2997 aedb afd6
f85e 9b86 b3b3 03b3 ac75 6fa6 1169 2f3d
3ace fa53 8660 956c bbc5 4ef3 

由於是I幀,直接保存了一幀數據,不需要分隔符號。

------3.NALU Body       由NALU = NALUHeader+EBSP可以知道Body=EBSP;

EBSP = 防止競爭碼+RBSP;       
RBSP
= SODB + RBSP尾部 。

去掉防競爭碼后,我們得到了 RBSP。下一步就是如何從 RBSP 中獲取原始的編碼數據SODP(String Of Data Bits)。

對於 RBSP 尾部分成兩種類型:

(1)RBSP尾部:其中大多數類型(非1-5)的NALU,使用這種尾部。

rbsp_stop_one_bit 停止位 占1個比特位,值為1

rbsp_alignment_zero_bit 值為0,目的是為了進行字節對齊,占據若干比特位,用若干個0bits對齊,用來補齊這個字節

所以RBSP就等於,SODB在它的最后一個字節的最后一個比特后,緊跟值為1的1個比特,然后增加若干比特的0,以補齊這個字節。

(2)條帶RBSP尾部 :另一種尾部,就是當NALU類型為條帶時,也即nal_unit_type等於1~5時,這時RBSP使用下面這種尾部

可以看到,rbsp_slice_trailing_bits()默認情況下,就是上面介紹的第一種尾部
只是當entropy_coding_mode_flag值為1,也即當前采用的熵編碼為CABAC,而且more_rbsp_trailing_data()返回為true,也即RBSP中有更多數據時,添加一個或多個0x0000。

所以我們拿到RBSP,只需要按照上述語法,去掉RBSP的尾部,就可以得到SODB。然后就可以對照對應類型的NALU的句法,解析出語法元素的值。

(二)AVCC---用於存儲

另一個存儲H.264流的方式是AVCC格式,在這種格式中,每一個NALU包都加上了一個指定其長度(NALU包大小)的前綴(in big endian format大端格式),這種格式的包非常容易解析,但是這種格式去掉了Annex B格式中的字節對齊特性,而且前綴可以是1、2或4字節,這讓AVCC格式變得更復雜了,指定前綴字節數(1、2或4字節)的值保存在一個頭部對象中(流開始的部分),這個頭通常稱為’extradata’或者’sequence header’(重點)

------1.AVCC格式了解:使用NALU長度(固定字節,通常為4字節,取決於頭部的NALULengthSizeMinusOne字段)分隔NAL;在頭部包含extradata(或sequence header)的結構體。(extradata包含分隔的字節數、SPS和PPS)

解碼器配置參數在一開始就配置好了(所以我們不能像視頻網站中的實時播放一樣可以在中間修改參數,比如:幀率,畫面),系統可以很容易的識別NALU的邊界,不需要額外的起始碼,減少了資源的浪費,同時可以在播放時調到視頻的中間位置。
這種格式通常被用於可以被隨機訪問多媒體數據。如存儲在硬盤的文件:MP4、MKV通常用AVCC格式來存儲

 AVCC格式不使用起始碼(start code)作為NALU的分界,這種格式在每個NALU前都加上一個大端格式的前綴(1、2、4字節,代表NALU長度)

所以在解析AVCC格式的時候需要將指定的前綴字節數的值保存在一個頭部對象中,這個都通常稱為extradata或者sequence header。同時,SPS和PPS數據也需要保存在extradata或者叫’sequence header’中。

  • 視頻開始有extradata,包含SPS,PPS
  • 每個NALU前有存儲NALU的長度,

------2.先分析頭部extradata,獲取SPS、PPS以及NALULengthSizeMinusOne字段信息

1.其中 extradata 前4字節無用,跳過即可。

2.第5個字節:前6位保留,全部置為1,即('111111'b);后兩位NALULengthSizeMinusOne字段用於告訴我們NALU前綴大小

  值=0 對應前綴1字節 對應每個NALU包最大長度255字節
  值=1 對應前綴2字節 對應每個NALU包最大長度64K
  值=3 對應前綴4字節 使用最多

3.第6個字節:前3位保留,全部置為1,即('111'b);后5位用於存放SPS NALU的個數(通常為1個)

4.取決於第6個字節中指定的SPS NALU個數,開始進行循環獲取SPS數據:

  接下來獲取兩個字節采用兩個字節,作為前綴指示接下來的一個SPS NALU的大小"N"(字節數)。每次獲取一個SPS NALU單元數據,都需要先獲取其前綴信息

  接下來獲取N個字節獲取SPS的數據

5.獲取全部SPS數據后,開始獲取PPS數據,獲取1個字節,內部存放了PPS NALU單元的個數(通常為1個)

6.取決於前1個字節中指定的PPS NALU個數,開始進行循環獲取PPS數據:

  接下來獲取兩個字節采用兩個字節,作為前綴指示接下來的一個PPS NALU的大小"N"(字節數)。每次獲取一個PPS NALU單元數據,都需要先獲取其前綴信息

  接下來獲取N個字節獲取PPS的數據

SPS和PPS被存儲在了非NALU包中(out of band帶外),即獨立於基本流數據。 這些數據的存儲和傳輸是文件容器的任務,超出了本文的范疇。

------3.AVCC流結構

 

AVCC中的NALU格式,與AnnexB格式一致。當我們將AVCC轉AnnexB時,如果檢測到NALU Type = 5關鍵幀,那么在關鍵幀前面加上SPS NALU和PPS NALU即可。

雖然AVCC格式不使用起始碼,防競爭字節還是有的。所以我們在轉換AVCC與AnnexB格式的時候,不用考慮防競爭字節。因為NALU內部是一致的。

三:RTP格式---用於網絡發送

RTP封裝= 12字節固定RTP包頭 + 載荷(NALU)

補充:針對IP網絡的RTP打包方式。為原始的NAL打包格式,就是開始的若干字節(1,2,4字節)是NAL的長度,而不是start_code,此時必須借助某個全局的數據來獲得編碼器的profile,level,PPS,SPS等信息才可以解碼。(AVCC格式)???

(一)RTP包頭

前12字節固定 + (0~15)個32位的CSRC標識符

 

V (2bits):   RTP協議的版本號,當前協議版本號為2。
P (1bit):    填充標志,如果設置填充位P=1,在包尾將包含附加填充字節,它不屬於有效載荷。填充的最后一個八進制包含應該忽略的八進制計數。某些加密算法需要固定大小的填充字節,或為在底層協議數據單元中攜帶幾個RTP包。
X (1bit):    擴展標志,如果X=1,則在RTP報頭后跟有一個擴展報頭
CC(4bits):     CSRC計數器,指示CSRC 標識符的個數。

M (1bit):    標記位(不同載荷含義不同,視頻標記一幀的最后一個分片slice則=1,其他=0)
PT (7bits):    載荷類型RTP_PAYLOAD_RTSP,記錄后面資料使用哪種 Codec , receiver 端找出相應的 decoder 解碼出來。例如H264=96
序列號(16bits): 用於標識發送者所發送的 RTP 報文的序列號(初始值隨機),每發送一個報文,序號增加 1

時間戳(32bits): 時間戳反映了該 RTP 報文的第一個八位組的采樣時刻。 接受者使用時間戳來計算延遲和抖動, 並進行同步控制。
SSRC(32bits): 區分是在和誰通信。值隨機選擇,參加同一視頻會議的兩個同步信源的SSRC要相同。

貢獻源(CSRC)標識符(32bits):每個CSRC標識符占32位,可以有0~15個。每個CSRC標識了包含在該RTP報文有效載荷中的所有特約信源。

RTP 協議實際上是由實時傳輸協議RTP(Real-time Transport Protocol)實時傳輸控制協議RTCP(Real-time Transport Control Protocol)兩部分組成。

  RTP 協議基於多播或單播網絡為用戶提供連續媒體數據的實時傳輸服務;     RTCP 協議是 RTP 協議的控制部分,用於實時監控數據傳輸質量,為系統提供擁塞控制和流控制。

(二)回顧NALU類型

F: 1 個比特.

forbidden_zero_bit. 在 H.264 規范中規定了這一位必須為 0.

NRI: 2 個比特.

nal_ref_idc. 取 00 ~ 11, 似乎指示這個 NALU 的重要性, 如 00 的 NALU 解碼器可以丟棄它而不影響圖像的回放. 不過一般情況下不太關心這個屬性

Type: 5 個比特.

nal_unit_type. 這個 NALU 單元的類型. 簡述如下:

  0     沒有定義
  1-23 NAL單元 單個 NAL 單元包. 24 STAP-A 單一時間的組合包 25 STAP-B 單一時間的組合包 26 MTAP16 多個時間的組合包 27 MTAP24 多個時間的組合包 28 FU-A 分片的單元 29 FU-B 分片的單元 30-31 沒有定義

(三)打包模式 : 拆包(1種) or 不拆包(2種)

RTP單次發送有上限👉2種RTP打包:拆包or不拆包

在IP網絡中,當要傳輸的IP報文大小超過【最大傳輸單元MTU】時就會產生IP分片情況。(若交給底層協議拆包容易出問題→→→主動拆分NALU再打包成RTP包后發送

------1.SDP文件描述和封包的關聯

H264的RTP中有三種不同的封包模式(Single NAL,Non-interleaved,Interleaved) 通過SDP參數中指定,如:

m=video 49170 RTP/AVP 98
a=rtpmap:98 H264/90000
a=fmtp:98 profile-level-id=42A01E; packetization-mode=1; sprop-parameter-sets=Z0IACpZTBYmI,aMljiA==

1、packetization-mode決定封包模式:

當 packetization-mode 的值為 0 時或不存在時, 必須使用單一 NALU 單元模式.(無此字段時,缺省為0) 單包
當 packetization-mode 的值為 1 時必須使用非交錯(non-interleaved)封包模式.  FU-A
當 packetization-mode 的值為 2 時必須使用交錯(interleaved)封包模式.     FU-B

2、sprop-parameter-sets: SPS,PPS

這個參數可以用於傳輸 H.264 的序列參數集和圖像參數 NAL 單元. 這個參數的值采用 Base64 進行編碼. 不同的參數集間用","號隔開。
//若不用Base64則可能會有數據丟失

3、profile-level-id:

這個參數用於指示 H.264 流的 profile 類型和級別. 由 Base16(十六進制) 表示的 3 個字節. 
  第一個字節表示 H.264 的 Profile 類型,
  第三個字節表示 H.264 的 Profile 級別

 

①single NAL unit packet 單包(1個RTP包:1個NALU)
②aggregation packets   聚合(組合)包(1個RTP包:多個NALU,提高傳輸效率),需要解包時在重組。
  ①STAP (Single-time aggregation packet)
    STAP-A
    STAP-B
  ② MTAP (Multi-time aggregation packet)
    MTAP16
    MTAP24
③Fragmentation Unit  拆包處理【一個NALU→多包 NALU>最大傳輸單元MTU】
    FU-A  //非交錯模式
    FU-B  //交錯模式

------2.單一NALU的RTP包

對於 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 包即可.

如有一個 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 個字節的開始碼就可以了.

------3.組合NALU的RTP包

其次, 當 NALU 的長度特別小時, 可以把幾個 NALU 單元封在一個 RTP 包中.

 這里只介紹STAP-A模式,如果是STAP-B的話會多加入一個DON域,另外還有MTAP16、MTAP24

例:如有一個 H.264 的 NALU 是這樣的:

[00 00 00 01 67 42 A0 1E 23 56 0E 2F ... ]

[00 00 00 01 68 42 B0 12 58 6A D4 FF ... ]

封裝成 RTP 包將如下:

[ RTP Header ] [78 (STAP-A頭,占用1個字節)] [第一個NALU長度 (占用兩個字節)] [ 67 42 A0 1E 23 56 0E 2F ] [第二個NALU長度 (占用兩個字節)] [68 42 B0 12 58 6A D4 FF ... ]

------4.分片NALU的RTP包:FU_indicator和FU_head(RTP分包時的包頭)

當NALU的長度超過MTU時,就必須對NALU單元進行分片封包.也稱為Fragmentation Units(FUs).

FU-A的分片格式:數據比較大的H264視頻包,被RTP分片發送。12字節的RTP頭后面跟隨的就是FU-A分片

FU_indicator:
F  禁止位 NRI 重要標識位👈即拆包的nalu自身的NRI ---- F與NRI 保存了 NALU的前3位
type RTP打包頭類型,FU-A時type=28
FU_header:
S  開始位 1表示分片NAL單元的開始,反之=0
E  結束位 1表示分片NAL單元的結束,反之=0。
R  保留位 必須為0,接收者必須忽略該位。
type NALU數據類型 👈NALU_header  ----  保存了 NALU 類型(即NALU的后5位)

例:

0x7C85=01111100 10000101 (開始包)

0x7C05=01111100 00000101 (中間包)

0x7C45=01111100 01000101 (結束包)

如有一個 H.264 的 NALU 是這樣的:

[00 00 00 01 65 42 A0 1E 23 56 0E 2F ...  02 17 C8 FD F1 B9 C7 53 59 72 ... CB FF FF F4 1A D5 C4 18 A8 ... F1 B9 C7 1D A5 FA 13 0B ...]  

封裝成 RTP 包將如下(注意:下面去掉了開始碼和NALU頭部,但是在FU_header的type存放了NALU數據類型

[ RTP Header ] [ 7C 85 42 A0 1E 23 56 0E 2F ...]

[ RTP Header ] [ 7C 05 02 17 C8 FD F1 B9 C7 53 59 72 ...]

[ RTP Header ] [ 7C 05 CB FF FF F4 1A D5 C4 18 A8 ...]

[ RTP Header ] [ 7C 45 F1 B9 C7 1D A5 FA 13 0B ...]

拆包和解包:

發送端—拆包:NAL_header與分片后的FU的單元頭有如下關系:
     NAL_header前三位為FU_indicator的前三位
     NAL_header后五位為FU_header的后五位
接收端—解包:將所有的分片包組合還原成原始的NAl包
     nal_unit_type = (fu_indicator & 0xe0) | (fu_header & 0x1f) 

參考文章:

H264---封裝格式:字節流格式(AnnexB)、AVCC 、RTP打包格式:https://blog.csdn.net/qq_42024067/article/details/102292535

H264格式 詳細介紹:https://blog.csdn.net/shixin_0125/article/details/78940402

H264 編碼分析:https://www.yuque.com/keith-an9fr/aab7xp/vng2pb

視頻和視頻幀:H264編碼格式整理:https://zhuanlan.zhihu.com/p/71928833

I、P、B幀區別: https://blog.csdn.net/sjin_1314/article/details/40989173

切片與宏塊:https://www.jianshu.com/p/9522c4a7818d

使用FFMPEG類庫分離出多媒體文件中的H.264碼流:https://blog.csdn.net/leixiaohua1020/article/details/11800877

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM