一、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 }