RFC3984是H.264的baseline码流在RTP方式下传输的规范,这里只讨论FU-A分包方式,因为工作上刚刚用到,就记下来。
FU_A一种分片封包的方式,就是将一个过大的NALU 单元封装成多个 RTP 包,这就不同以往那种单个NALU封装成单个RTP包方式,当然解析方式就不一样了。多个rtp包表示一个NALU单元,就涉及那个rtp包是这个NALU的开始,那个是结束,哪些是中间数据。当然,你可能关心的是I帧怎么解析。用Wireshark你可一非常直观的看到。
1. SPS,以0x27开头,一般SPS数据量不大
2. PPS,以0x28开头
3. FU_A封包标志以及开始:RTP头+FU_A,FU_A 一般两个字节,包括FU indicator一个字节和FU header一个字节,看下图
其中0x3C包含FU indicator, 0x85包含FU header,但是3C和85不只包含这两个信息,还有其他信息,真正的FU indicator= 0x3C&1F = 28, FU header = 0x85&0xE0 = 0x80.所以,如果FU indicator为28,代表使用FU_A封包。FU header为0x80表示第一个RTP包。
4. FU_A中间包以及结束的标志
FU header = 0x45&E0 = 0x40: 表示一个NALU使用FU_A方式封包结束,其他值代表中间的包
5. I帧标志
找到FU header,让其后5位与上0x1F,如果结果是0x5,则该包位I帧数据,如上图0x5&0x1f= 0x5,所以上图标记的这包RTP数据位I帧的数据。一般PPS,SPS包的RTP头不需要用FU_A方式结尾。
代码实现的解包:从RTP中分拆出H264数据
//功能:解码RTP H.264视频
// 参数:1.RTP包缓冲地址 2.RTP包数据大小 3.H264输出地址 4.输出数据大小
// 返回:true:表示一帧结束 false:FU-A分片未结束或帧未结束
#define RTP_HEADLEN 12
bool UnpackRTPH264( void * bufIn, int len, void ** pBufOut, int * pOutLen)
{
*pOutLen = 0 ;
if (len < RTP_HEADLEN)
{
return false ;
}
unsigned char * src = (unsigned char* )bufIn + RTP_HEADLEN;
unsigned char head1 = * src; // 获取第一个字节
unsigned char head2 = * (src + 1 ); // 获取第二个字节
unsigned char nal = head1 & 0x1f; // 获取FUindicator的类型域,
unsigned char flag = head2 & 0xe0 ; // 获取FU header的前三位,判断当前是分包的开始、中间或结束
unsigned char nal_fua = (head1 & 0xe0 ) | (head2 & 0x1f); // FU_A nal
bool bFinishFrame = false ;
if (nal == 0x1c ) // 判断NAL的类型为0x1c=28,说明是FU-A分片
{// fu-a
if (flag== 0x80 ) // 开始
{
* pBufOut = src - 3 ;
* (( int * )( * pBufOut)) = 0x01000000 ; // zyf:大模式会有问题
* ((char * )( * pBufOut) + 4) = nal_fua;
* pOutLen = len - RTP_HEADLEN + 3 ;
}
else if (flag == 0x40 ) // 结束
{
* pBufOut = src + 2 ;
* pOutLen = len - RTP_HEADLEN - 2 ;
}
else // 中间
{
* pBufOut = src + 2 ;
* pOutLen = len - RTP_HEADLEN - 2 ;
}
}
else // 单包数据
{
* pBufOut = src - 4 ;
* (( int * )( * pBufOut)) = 0x01000000 ; // zyf:大模式会有问题
* pOutLen = len - RTP_HEADLEN + 4 ;
}
unsigned char * bufTmp = (unsigned char* )bufIn;
if (bufTmp[ 1 ] & 0x80 )
{
bFinishFrame = true ; // rtpmark
}
else
{
bFinishFrame = false ;
}
return bFinishFrame;
}