音視頻傳輸:RTP協議詳解和H.264打包方案

問題背景:

前面講解了PS、TS、FLV這三種媒體封裝格式,現在新開一個系列講解下傳輸協議,這裏面會包含RTP、RTSP、HLS、RTMP等。當然最複雜的封裝格式MP4在準備中,後面會把封裝格式這個系列講完。今天要說的RTP傳輸協議,有人也認爲這是封裝格式,因爲協議中打包音視頻要填寫時間戳的相關信息,FFmpeg就把這個作爲封裝格式。我覺得都沒啥問題,不過我更偏向認爲是傳輸協議。

關注公衆號:瞭解更多!

RTP協議即實時傳輸協議(Real-time Transport Protocol),從字面理解也是和實時傳輸有關係,協議的初衷是爲了實時多人視頻會議而設計的,現在應用很廣泛。在視頻監控、實時直播、語音電話、視頻會議都能看到應用。特別是在目前大熱的webRTC中將其作爲傳輸協議,國內安防標準GB28181和國際標準ONVIF也是用這個傳輸音視頻。所以在視頻監控和實時視頻傳輸還是統治級別的存在,沒有啥協議能夠進行短期取代,當然這都是由RTP特點決定的。

 

下面篇章會講解下RTP協議的內容和一些周邊協議的關係,最後還會講解如何把H264的NALU數據打包到RTP協議內,你可以選擇性閱讀。

RTP協議背景:

RTP協議即Real-time Transport Protocol是一種網絡傳輸協議,一般負責音視頻數據的封包和傳輸。其中IETF多媒體小組在1996年的RFC1889就給出了該協議的規範和細節,其後在RFC3550中進行了更新,如果你要系統性學習,直接看RFC3550規範即可。

跟RTSP、RTCP的關係:

RTCP協議:實時傳輸控制協議即Real-time Transport Control Protocol,這個協議是RTP協議的姊妹協議,它是爲了進行服務質量的監視與反饋、媒體間的同步,以及多播組中成員的標識,目前WecRTC用這個協議進行流量和擁塞控制。

RTSP協議:實時流協議即Real Time Streaming Protocol,這是一種會話管理和媒體控制的協議,用的最多地方就是視頻監控。視頻監控中攝像機、NVR、前後端之間都是用這個協議和RTP協議配合進行流媒體傳輸。

站在播放器實現角度,理解三者關係,看圖:

 

我用一個開源軟件通過RTSP的URL拉取了一個大華攝像頭的視頻。其中大家看到這些開始播放,暫停、快播這些播放按鈕背後的功能就需要靠RTSP來支持。看到這個圖像視頻數據,其實靠的是RTP協議傳輸過來的。RTCP呢?由於RTP協議大多數情況是不可靠協議,它只管傳輸音視頻數據,但是並不保證這些數據丟了怎麼處理,發快了怎麼處理,這種數據的可靠性控制需要的RTCP協議來保障的。不過我們今天只講RTP協議,通過這個講解相信你能有個感官認識。

站在網絡層次模型的角度,同樣看圖:

 

其中RTSP協議就和HTTP協議一樣,屬於應用層協議,一般傳輸層靠的是TCP傳輸。其實本身的語法和HTTP協議都非常相似,後面文章會詳細講解。

RTP協議既可以理解爲傳輸層也可以理解爲應用層,這麼說是因爲RTP負載可以放到RTSP上進行傳輸,通過二元交織通道方式實現。也可以下層放到TCP進行承載,不過大多數情況RTP的負載協議是UDP,如果放到TCP上大大降低了RTP協議的實時性。之所以設計RTP協議,就是因爲爲了規避TCP協議的一些缺點,因爲TCP協議在操作系統的協議棧上實現了流量和擁塞控制等機制,但是TCP並沒有考慮傳輸視頻的情況,它是針對傳輸任何數據更通用的做法,但是結合流媒體傳輸的特點,我們發現UDP更適合傳輸,所以我們把保證傳輸速度即快又正確這件事交給了上層的RTP和RTCP協議,這樣我們開發者是可以來實現和控制這兩種協議的,這樣就爲解決音視頻低延時等場景提供了可能。

給個實例看下,上圖:

 

RTP協議原理:

1.發送地址的確定:

上面說了RTP協議是發送端傳輸流媒體數據的,但是往那個IP和端口傳輸,如何將自己傳輸的音視頻屬性告訴給接收端就需要一種機制來實現,常見的做法就是用SDP進行描述,然後通過RTSP、SIP或者HTTP等協議和接收端協商。一般在協商過程中,會確定發送端RTP和RTCP的目的地址,目的地址由一個IP地址和端口對組成,偶數端口就是RTP媒體流的目的端口,偶數端口+1就是RTCP協議的目的端口,其中RTSP協議傳輸端口的確定就是通過SetUP方法,看下圖:

 

2.RTP數據包的生成:

通過RTSP等協議的SDP信息協商好了RTP數據包的發送目的和傳輸方式,我們就需要把音視頻數據打包成RTP包,用UDP發送給接收端了。RTP不僅可以用來傳視頻,也可以傳音頻,甚至可以傳輸圖像和非音視頻數據。傳輸視頻不僅可以傳輸H264編碼的數據,也可以傳輸H265,同樣可以傳輸谷歌的VP8 VP9系列編碼的視頻裸數據。音頻可以傳輸G7xx系列、AAC系列。那封裝好的數據可以傳輸嗎,也是可以的。其中安防中常說的國標流就是RTP+PS形式,也可以傳輸RTP+TS數據;

3. RTP的靈活性:

之所以看到RTP協議應用場景廣,傳輸的數據格式多,主要是因爲RTP協議設計簡單,有時少即是多。RTP協議把很多控制權交給了上層應用者,許多字段也是允許用戶自己協商和確定,這樣RTP協議的生命力和適應性就強很多。下面分析RTP格式和通過一個示例來看下RTP數據包格式。

RTP數據包格式:

RTP固定頭:

RTP的數據包由RTP Header + RTP Playload組成。其中RTP固定頭如下圖所示:

 

各個字段的解釋:

1. V:當前的協議的版本號是2,其中0和1已經在草案規範中被佔用,這裏基本就是固定值了;

2. P:填充標記,包的末尾包含了一個或者多個填充字節,其中填充字節的第一字節包括了後面填充字節的長度,該長度字段包含自己,主要是爲了一些對齊處理;

3. X位,如果爲1則說明有擴展頭,一般默認爲0,很少有場景會用到;

4. CC位:是爲了計算後面有多少個CSRC,四位說明則最大支持15個CSRC,一般默認爲0。

5. M位:特別對於視頻而言就是一幀的結束,視頻幀比較大,需要通過多個NALU來傳輸,當看到M位爲1時就認爲是這個I幀的結束,由於音頻幀比較小,一個RTP包就是一個音頻幀,所以該位直接置1。

6. Sequence number序列號:16位,用於標識發送者發送的RTP報文序列號,每發送一個RTP包,則這裏就增加1,當達到最大值後,則重新從0開始。剛纔說了一般RTP協議是承載協議是UDP,UDP是不可靠傳輸協議。那我們如何保證接收端收的數據是正確的呢,就是通過這個字段進行重新排序,所以接收端一般收到RTP數據第一件事就是排序。

特別注意兩點:

a. 這個序列號的初始值可以爲0但是也可以爲其它隨機值,只要符合+1就行;

b. b.發送端的音頻和視頻都是通過RTP傳輸的,但是他們是分別計數的,所用的序列號是不同的。

7. timestamp時間戳:佔32位四字節,這個單位要注意是採樣率到倒數,不是真實的時間,一般要根據採樣率進行換算。這裏反應的RTP報文第一個八位組的採樣時刻,目的是爲了接收端計算延遲、抖動和音視頻同步。需要說明的是,一個視頻幀的時間戳是相同的,但是一個視頻幀數據量很大可能需要多個RTP包傳輸,這樣就存在多個RTP包時間戳相同的情況,音頻幀數據小,不存在音頻幀跨RTP的情況,所以不存在這個問題。

8. SSR同步信號源:佔32位四字節,用於標識同步信號源,這個值只要保證在一路音視頻會話裏面值不相同即可。該標識符是隨機選取的 RFC1889推薦了MD5隨機算法。該值的作用就是在會話中標識RTP負載流的身份,給一個唯一標記值。

9. CSRC特約信號源CSRC:同樣是32位,四字節。一個RTP頭最多可以含有0-15個,如果是1對1的流媒體傳輸,這個字段就不用處理,直接忽略該字段。但是混流和混音時,則需要把各方的RTP同步信號源列出來,這樣接收端就能正確指出交談雙方的身份。

RTP擴展頭解析:

RTP提供了擴展機制以實現個性化:某些新的負載格式獨立的功能要求的附加信息可以允許放到RTP數據包頭的擴展部分進行傳輸,基本的RT並不定義任何擴展頭本身。

我的理解就是爲了給RTP傳輸協議增加一些擴展性,防止未來一些新功能的加入,同時允許用戶增加一些私有信息和私有功能在裏面,大部分音視頻場景都沒有啓用RTP擴展部分,但是也有例外。在WebRTC中看到利用RTP擴展部分做了FEC(前向糾錯,核心思想就是一些異或運算)的算法處理,這樣當發生RTP丟包可以通過擴展快速恢復丟包,在網絡不好的時候特別有用。如果你對這部分很瞭解,可以微信後臺私信我一起探討下。

擴展頭格式:

 

 

字段說明:

擴展字段定義define by profile:16bit兩字節,這個由上層的具體實現協議來決定;

擴展頭長度length:表示擴展頭的長度字段,16bit即2字節,最大擴展長度1024字節;

注意:

如果要啓用擴展頭,固定頭的擴展標記X置1,負載類型playload需要按照規範定義,擴展頭字段的長度可以爲0,因爲不包括頭字段的4字節,最大1024.

 

H264打包RTP的方法:

上面已經交代了,RTP的特點不僅僅支持承載在UDP上,這樣利於低延遲音視頻數據的傳輸,另外一個特點是它允許通過其它協議接收端和發送端協商音視頻數據的封裝和編解碼格式,這樣固定頭的playload type字段就比較靈活。截止目前爲止,RTP是我見過傳輸音視頻數據類型最多的,具體參考:https://en.wikipedia.org/wiki/RTP_payload_formats。

 

今天我以H264裸碼流NALU爲例,給大家講述下如何進行H264的打包,這也是我上面幾篇封裝格式講解的固定套路,其中H264打包的詳細方法要參考RFC6184文檔。

 

H.264標準協議定義了兩種不同的類型:一種是VCL即Video Coding Layer,一種是NAL即Network Abstraction Layer。其中前者就是編碼器吐出來的原始編碼數據,沒有考慮傳輸和存儲問題。後面這種就是爲了展現H.264的網絡親和性,對VCL輸出的slice片數據進行了封裝爲NALUs(NAL  Units),然後再封裝爲RTP包進行傳輸,這些都是H.264的基礎,見後續文章。

 

NALU的基本格式是:NALU Header + NALU Data,其中NALU的頭由一個字節組成如下所示:

+-----------------+

|0|1|2 |3|4|5|6|7|

+-+-+---+-+-+-+

|F| NRI |Type|

+-----------------+

F

(1 bit)

如果是壞幀,則置1,其餘H.264固定爲0。

NRI

(2 bit)

用來指示該NALU 的重要性等級。值越大,表示當前NALU越重要。具體大於0 時取何值,沒有具體規定。

例如:如果是00,則表示此幀即使丟失了,也不影響解碼;其他值則表示此幀如果丟失了,會影響解碼,這個字段指明瞭該NALU的重要性,但是實際我們不太關心這個字段。

 

Nalu_Type

(5 bit)

NAL Unit的類型,這個值指明瞭NALU的類型,其中NALU的類型見下表

 

Nalu_Type

NALU內容

備註

0

未指定

 

1

非IDR圖像編碼的slice

比如普通I、P、B幀

2

編碼slice數據劃分A

2類型時,只傳遞片中最重要的信息,如片頭,片中宏塊的預測模式等;一般不會用到;

3

編碼slice數據劃分B

3類型是隻傳輸殘差;一般不會用到;

4

編碼slice數據劃分C

4時則只可以傳輸殘差中的AC係數;一般不會用到;

5

IDR圖像中的編碼slice

IDR幀,IDR一定是I幀但是I幀不一定是IDR幀。

6

SEI補充增強信息單元

可以存一些私有數據等;

7

SPS 序列參數集

編碼的參數配置

8

PPS 圖像參數集

編碼的參數配置

9

接入單元定界符

 

10

序列結束

 

11

碼流結束

 

12

填充數據

 

24

STAP-A

Single-time aggregation packet

單一時間聚合包模式,意味着一個RTP包可以傳輸多個NALU,但是這些NALU的編碼時間要一樣才能聚合到一個RTP。

25

STAP-B

Single-time aggregation packet

 

26

MTAP 16

Muti-time aggregation packet

多個時間聚合包模式:意味着一個RTP包可以傳輸多個NALU,但是這些NALU的編碼時間有可能不一樣。

27

MTAP 24

Muti-time aggregation packet

 

28

FU-A

Fragmentation unit

切包模式:當一個RTP容納不下一個NALU時,就需要FUs這種格式。

29

FU-B

Fragmention unit

 

30-31

未指定,保留  

我們看到1-11就是NALU的單個包類型,但是一個NALU的大小是不一樣的,如果是非視頻數據的SPS PPS才十幾個字節,對於IDR幀,則有可能幾十KB。這樣把NALU打包到RTP方式就很多:分爲一個RTP包承載一個NALU,多個NALU合併到一個RTP,一個大的NALU切分成多個RTP。同時由於時間戳的問題,就有了24-29幾種類型。

但是對於發送端組RTP包的一方來說,儘可能找簡單的打包方式。對於接受端則需要適配各種發送端的打包方式,因爲無法決定輸入源的打包方式。這裏先分享下我們的打包方式,比較簡單:

1. 我們對於NALU的長度<1400的則採用的是單一NALU打包到單一的RTP包中;

2. 我們對於NALU的長度>=1400的則採用了FU-A的方式進行了打包,這種就是把一個大的NALU進行了切分,最後接收方則進行了合併,把多個RTP包合併成一個完整的NALU即可;

3. 至於爲什麼NALU的長度大於1400字節就要進行FU-A切片,是因爲底層MTU大小值固定爲1500,從傳輸效率講,這裏用1400作爲切分條件。

同時我們發現現在視頻監控領域攝像頭通過RTP 傳輸碼流的打包方式都是基本這種,這種打包方案簡單容易實現,又滿足需要。如下圖所示:

 

① 28、29、30三個RTP分別傳輸的SPS、PPS、SEI這三種NALU,其中一個NALU分到一個RTP包,這是單一打包方式;

② 31、32三個RTP包分別傳輸了IDR幀的NALU單元,由於比較大,發送方採用了FU-A的打包方式;

③ 上圖還顯示了SPS、PPS、SEI的RTP包固定頭,Seq初始值不爲0,爲隨機值,並且一個RTP包就順序+1,這跟上面分析的一致;

④ 上面SPS、PPS、SEI本身不涉及時間戳,但是這裏頭填充爲和自己後面的第一個RTP保持一致,同樣IDR的NALU切分的不同RTP包時間戳也是一樣的;

 

 

那麼到底將單一的NALU打包到RTP或者把比較大的NLAU打包到多個RTP即FU-A方式是怎麼操作的呢?


打包方式之Single NAL Unit

就是一個RTP包打包一個單獨的NALU方式,其實最好理解,就是在RTP固定頭後面直接填充NLAU單元數據即可,即:

RTP Header + NALU Header + NALU Data;

我了驗證猜想我進行了抓包和寫文件,我們發現寫文件的SPS和抓包RTP包固定頭後面的負載完全是一致的,寫文件中的SPS:

抓包中的RTP固定頭後面的SPS:

 

打包方式之FU-A

 

這種打包方式也不復雜,爲了解釋清楚,需要了解下面兩個數據包頭即FU indicator和Fu header。

FU indication:

  +---------------+

  |0|1|2|3|4|5|6|7|

  +-+-+-+-+-+-+-+-+

  |F|NRI|  Type   |

  +---------------+

這裏面的的F和NRI已經在NALU的Header解釋清楚了,就是NALU頭的前面三個bit位,後面的TYPE就是NALU的FU-A類型28,這樣在RTP固定頭後面第一字節的後面5bit提取出來就確認了該RTP包承載的不是一個完整的NALU,是其一部分。

那麼問題來了,一個NALU切分成多個RTP包傳輸,那麼到底從哪兒開始哪兒結束呢?可能有人說RTP包固定頭不是有mark標記麼,注意區分那個是以幀圖像的結束標記,這裏要確定是NALU結束的標記,其次NALU的類型呢?那麼就需要RTP固定12字節後面的Fu Header來進行區分。

 

FU header

  +---------------+

  |0|1|2|3|4|5|6|7|

  +-+-+-+-+-+-+-+-+

  |S|E|R|  Type   |

  +---------------+

字段解釋:

S: 1 bit 當設置成1,開始位指示分片NAL單元的開始。當跟隨的FU荷載不是分片NAL單元荷載的開始,開始位設爲0。

E: 1 bit 當設置成1, 結束位指示分片NAL單元的結束,即, 荷載的最後字節也是分片NAL單元的最後一個字節,當跟隨的FU荷載不是分片NAL單元的最後分片,結束位設置爲0。

也就是說一個NALU切片時,第一個切片的SE是10,然後中間的切片是00,最後一個切片時11。

R: 1 bit

保留位必須設置爲0,接收者必須忽略該位。

Type: 5 bits

此處的Type就是NALU頭中的Type,取1-23的那個值,表示 NAL單元荷載類型定義,

 


綜上所述:

對於比較大的NLAU進行FU-A切片時,其中NALU的Header字段在RTP打包時劃分爲兩個字節

1、NALU header的前3bit爲RTP固定頭後面第一個字節FU-indication的前3bit,後面5bit後面跟了FU-A打包這種類型28;

2、NALU header的後面5bit變成了RTP固定頭第二字節的後面5bit,其中前3bit標識了分片的開始和結束。

 

爲了驗證這種打包方式,我們同樣進行了寫文件和抓包,對第一個IDR幀的NLAU採取的這種分片進行了研究。

第一個IDR幀的NALU第一個切片:

FU indication

十六機制:0x7C

二進制:0111 1100

FU header

十六進制:0x85

二進制:1000 0101

這裏的SE是10,則說明該RTP包承載的NALU的第一個切片。

 

這樣我們提取FU indication字節的前3bit位和Fu header字節後5bit位則爲0110 0101即0x65這剛好符合IDR幀的NALU Header定義,後面的b8 00 00 03等二進制就是NALU的DATA字段。

第一個IDR幀的NALU第二個切片:

FU indication

十六機制:0x7C

二進制:0111 1100

FU header

十六進制:0x05

二進制:0000 0101

這裏的SE是00,則說明該RTP包承載的NALU的中間切片。

按照同樣方法提取,則NALU Header的二進制爲0110 0101即0x65,同樣說明是IDR幀類型,類似這種的中間切片有很多,從31-56RTP包都是中間切片,直到最後一個NALU的切片。

 

 

第一個IDR幀的NALU最後一個切片:

FU indication

十六機制:0x7C

二進制:0111 1100

FU header

十六進制:0x45

二進制:0100 0101

這裏的SE是01,則說明該RTP包承載的NALU的最後一個切片。

當然通過抓包你還可以到IDR幀時比較大的,而後面的P幀就相對比較小,因爲一個NALU切片4次就完了,同樣能看到時間戳的變化值等信息。

 


我們可以看到發送端一般採用Single NAL Unit和FU-A打包方式就基本可以將H264數據發送到接收端了,對於AAC音頻來說,直接將ADTS頭部去掉以1024字節組成一幀直接塞到RTP即可,打包並不難。至於其他的封裝格式如PS、TS或者H265,VPx等數據如何打包RTP,以後再給大家進行分享,完善這個傳輸系列。

 

 


參考網站和文檔:

https://www.rfc-editor.org/info/rfc3550

https://www.rfc-editor.org/info/rfc6184


今天就說這麼多,祝您心情愉快,工作順利!

如果有疑問,你可以在公衆號後臺發消息諮詢我。

 

 

 

 

 


往期文章回顧:

音視頻基礎知識:流媒體核心技術梳理

音視頻封裝:FLV格式詳解和打包H264、AAC方案(下)

音視頻封裝:FLV格式詳解和打包H264、AAC方案(上)

音視頻常見問題分析和解決:延時和抖動

音視頻封裝格式:AAC音頻基礎和ADTS打包方案詳解

音視頻基礎知識-時間戳的理解

 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章