一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(七)RTP音视频传输解析层之H264传输格式
2017-10-14 18:31 Ansersion 阅读(2372) 评论(0) 编辑 收藏 举报一、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 }