一個基於JRTPLIB的輕量級RTSP客戶端(myRTSPClient)——實現篇:(七)RTP音視頻傳輸解析層之H264傳輸格式


一、H264傳輸封包格式的2個概念

(1)組包模式(Packetization Modes)

RFC3984中定義了3種組包模式:單NALU模式(Single Nal Unit Mode)、非交錯模式(Non-interleaved Mode)和交錯模式(Interleaved Mode)。

“單NALU模式”:NALU封包在傳輸過程中必須是整包傳輸,不可以分包(指應用層的分包,並非指傳輸層)。而且NALU必須是嚴格按照解碼順序傳輸,也就是說,假設1s中連續的24幀分別標記為:frame1,frame2...,frame24,則傳輸必須嚴格按frame1,frame2...,frame24這個順序傳輸。

“非交錯模式”:NALU必須是嚴格按照解碼順序傳輸,也就是說,假設1s中連續的24幀分別標記為:frame1,frame2...,frame24,則傳輸必須嚴格按frame1,frame2...,frame24這個順序傳輸。該模式可以分包(指應用層的分包,並非指傳輸層)。

“交錯模式”:NALU可以不按照解碼順序傳輸,也就是說,假設1s中連續的24幀分別標記為:frame1,frame2...,frame24,則傳輸順序可以是frame15,frame7,frame9...。該模式可以分包(指應用層的分包,並非指傳輸層)。

 

(2)封包類型(Packet Type)

RFC3984中定義了7種封包類型:Nal Unit, STAP-A, STAP-B, MTAP16, MTAP24, FU-A, FU-B。

這些類型分別對應着不同的傳輸屬性(如支持應用層的“大包分小包”、“小包組大包”)。其中比較常見的是FU-A(Fragmentation Units A)這種類型。

 

“組包模式”和“封包類型”一起規定了H264的傳輸格式,但它們之間也非隨意組合的,具體如下圖:

 

二、SDP中的組包模式

H264傳輸的組包模式在SDP中被指定,下圖是截取的一段SDP內容

 其中“packetization-mode=1”即規定了H264的組包模式。3種組包模式分別對應編號0,1,2(見RFC3984),1表示“非交錯模式”。

 

三、本地NALU和傳輸中NALU

(1)本地NALU

現在我們本地有一個NALU,大小為3000字節,如下圖。

其中NALU Header分別由,1bit禁止位,2bit權限位,和5bit類型位。

其中type的有效值為1-12,分別代表了NALU的不同類型,數值0禁用,13-31保留(type由5bit表示,范圍為0-31)。

 

 (2)傳輸中的NALU

假設“組包模式”=“非交錯模式”,“封包類型”=“FU-A”,並且將上述所述的包拆分成了3個進行傳輸,我們來舉例說明傳輸中的NALU和本地的NALU的區別。

 

以上就是按順序傳輸到客戶端的3個封包。與本地NALU不同的是,NALU Header的type不再是1-12,而是28(28表示FU-A傳輸格式,見RFC3984),真正的NALU的type被包含在FU-A Header中。

FU-A Header的格式如下

S(Start):起始包指示位,即當傳輸的是第1個NALU分包時,該位置1。上圖中Pack 1該位會被置位;

E(End):結束寶指示位,即當傳輸的是最后1個NALU分包時,該位置1。上圖中Pack 3該位會被置位;

R(Reserved):保留位,忽略之。

Type:NALU類型,即原來在NALU Header中的Type。

 

當客戶端收到這3個分包時,便可以將其還原成本地NALU的格式了。

 

四、源碼分析

在nalu_types_h264.cpp中,首先分析函數:

size_t FU_A::CopyData(uint8_t * buf, uint8_t * data, size_t size)

它的作用是將data中的數據復制到buf中,一共復制size個字節,返回實際復制的字節數。其中buf為用戶的緩沖區,data為rtp接收的數據。

 1 size_t FU_A::CopyData(uint8_t * buf, uint8_t * data, size_t size) 
 2 {
 3     size_t CopySize = 0;
 4     if(!buf || !data) return 0;
 5 
 6     StartFlag = IsPacketStart(data);
 7     EndFlag = IsPacketEnd(data);
 8 
 9     uint8_t NALUHeader = 0;
10     NALUHeader = (uint8_t)(  
11             ParseNALUHeader_F(data)      |   
12             ParseNALUHeader_NRI(data)    |   
13             ParseNALUHeader_Type(data)
14             );
15 
16     if(StartFlag) {
17 
18         // NALU start code size
19         buf[0] = 0; buf[1] = 0; buf[2] = 0; buf[3] = 1;
20         CopySize += 4;  
21         memcpy(buf + CopySize, &NALUHeader, sizeof(NALUHeader));
22         CopySize += sizeof(NALUHeader);
23     }
24     const int FU_A_HeaderSize = 2;
25     memcpy(buf + CopySize, data + FU_A_HeaderSize, size - FU_A_HeaderSize);
26     CopySize += size - FU_A_HeaderSize;
27 
28     return CopySize;
29 }

 

仔細看一下源碼,我們會發現該函數先解析data的前2個字節(IsPacketStart、IsPacketEnd、ParseNALUHeader_F、ParseNALUHeader_NRI和ParseNALUHeader_Type,源碼如下),如果該數據為NALU的第1個RTP分包,則在其最前面添加{0,0,0,1},以標注NALU的開頭。

 1 bool FU_A::IsPacketStart(const uint8_t * rtp_payload)
 2 {
 3     if(!IsPacketThisType(rtp_payload)) return false;
 4 
 5     uint8_t PacketS_Mask = 0x80; // binary:1000_0000
 6 
 7     return (rtp_payload[1] & PacketS_Mask);
 8 }
 9 
10 bool FU_A::IsPacketEnd(const uint8_t * rtp_payload)
11 {
12     if(!IsPacketThisType(rtp_payload)) return false;
13 
14     uint8_t PacketE_Mask = 0x40; // binary:0100_0000
15 
16     return (rtp_payload[1] & PacketE_Mask);
17 }
18 
19 uint16_t FU_A::ParseNALUHeader_F(const uint8_t * rtp_payload)
20 {
21     if(!rtp_payload) return FU_A_ERR;
22     if(FU_A_ID != (rtp_payload[0] & FU_A_ID)) return FU_A_ERR;
23 
24     uint16_t NALUHeader_F_Mask = 0x0080; // binary: 1000_0000
25 
26     // "F" at the byte of rtp_payload[0]
27     return (rtp_payload[0] & NALUHeader_F_Mask);
28 }
29 
30 uint16_t FU_A::ParseNALUHeader_NRI(const uint8_t * rtp_payload)
31 {
32     if(!rtp_payload) return FU_A_ERR;
33     if(FU_A_ID != (rtp_payload[0] & FU_A_ID)) return FU_A_ERR;
34 
35     uint16_t NALUHeader_NRI_Mask = 0x0060; // binary: 0110_0000
36 
37     // "NRI" at the byte of rtp_payload[0]
38     return (rtp_payload[0] & NALUHeader_NRI_Mask);
39 
40 }
41 
42 uint16_t FU_A::ParseNALUHeader_Type(const uint8_t * rtp_payload)
43 {
44     if(!rtp_payload) return FU_A_ERR;
45     if(FU_A_ID != (rtp_payload[0] & FU_A_ID)) return FU_A_ERR;
46 
47     uint16_t NALUHeader_Type_Mask = 0x001F; // binary: 0001_1111
48 
49     // "Type" at the byte of rtp_payload[0]
50     return (rtp_payload[1] & NALUHeader_Type_Mask);
51 }

 

 

 

上一篇                 回目錄                 下一篇


免責聲明!

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



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