1) FU-A分包原理
超过MTU发送单元的H264视频包,需要分片发送,12字节的RTP头后面跟随的就是FU-A分片。FU-A分片的前两个字节分别位FU indicator 和 FU header。FU indicator的前三位和
FU header的后五位构成了NAL单元的头
说明下元素
F:禁止位,0表示正常,1表示错误,一般都是0
NRI:重要级别,11表示非常重要。
TYPE:表示该NALU的类型是什么,
所以有的文档中SPS是0x27,只是NRI的重要性设置不一样而已
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| FU indicator | FU header | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| |
| FU payload |
| |
| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| :...OPTIONAL RTP padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 14. RTP payload format for FU-A
2)起始码的作用
一个NAL单元没有起始位置和终止位置确定其范围(NAL单元没有一个说明该数据长度的语法),如果编码数据存储位一个文件,编码器将无法从数据流中分离出每个NAL的起始位置和终止位置。因此H264采用起始码来解决这个问题
H.264编码时,在每个NAL前添加起始码 (一帧的开始则用4位字节表示,ox00000001,否则用3位字节表示ox000001),解码器在码流中检测到起始码,当前NAL结束。为了防止NAL内部出现0x000001的数据,h.264又提出'防止竞争 emulation prevention"机制,在编码完一个NAL时,如果检测出有连续两个0x00字节,就在后面插入一个0x03。当解码器在NAL内部检测到0x000003的数据,就把0x03抛弃,恢复原始数据。
0x000000 >>>>>> 0x00000300
0x000001 >>>>>> 0x00000301
0x000002 >>>>>> 0x00000302
0x000003 >>>>>> 0x00000303
实际测试结果
在一帧的开始添加0x00000001,在内部添加0x000001解码不出来,提示帧不完整
代码剖析
bool ReadOneFrame(const std::string strRTPBuffer)
{
static std::uint8_t szStartCode[] = { 0x00, 0x00, 0x00, 0x01 };
//收到的RTP包,保存在strRTPBuffer缓存中
std::uint8_t* pData = (std::uint8_t*)strRTPBuffer.c_str();
//同一帧的数据,时间戳都是一样的
std::uint32_t nTimeStamp = (((pData[4] << 8) + pData[5]) << 16) + (pData[6] << 8) + pData[7];
//获取FU-A分片的第一个字节的后五位,判断分片类型
std::uint8_t cFragmentationUnitType = pData[12] & 0x1F;
bKeyFrame = CVOS_FRAME_TYPE_NON_KEY_FRAME;
switch (cFragmentationUnitType)
{
case 7:
case 8:
{
//一般情况下SPS/PPS都是单独完整的一个RTP包,直接去掉RTP头部
m_strCompleteOneFrame.append((char*)szStartCode, 4);
m_strCompleteOneFrame.append(&m_strRTPBuffer[12], m_strRTPBuffer.size() - 12);
break;
}
case 28:
{
//获取FU-A分片的第二个字节的后五位,判断当前的帧类型
std::uint8_t cFrameType = pData[13] & 0x1F;
if (5 == cFrameType)
{
//收到关键帧
}
std::uint8_t cStartBit = pData[13] >> 7;
std::uint8_t cEndBit = (pData[13] & 0x40) >> 6;
if (1 == cStartBit)
{
m_strCompleteOneFrame.append((char*)szStartCode, 4);
//根据FU-A indicator 和 FU-A header 拼凑出NAL头部
unsigned char cNALHeader = (pData[12] & 0xE0) | (pData[13] & 0x1F);
m_strCompleteOneFrame.append((char*)&cNALHeader, 1);
}
if (1 == cEndBit)
{
//标志为完整的一帧
m_bCompleteOneFrame = true;
}
//去掉RTP头部和FU-A头
m_strCompleteOneFrame.append(&m_strRTPBuffer[14], m_strRTPBuffer.size() - 14);
break;
}
}
bool bRet = m_bCompleteOneFrame;
if (m_bCompleteOneFrame)
{
//开始处理完整的一帧
m_strCompleteOneFrame.clear();
m_bCompleteOneFrame = false;
}
return bRet;
}