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;
 }