欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

HEVC解码器HM源码阅读(三)读取一个NALU

程序员文章站 2022-07-07 14:06:29
...

读取一个NALU



视频数据的两种存储传输方式


视频的压缩数据,有两种存储传输方式:
1、存放在本地文件中,就是所谓的字节流应用(本章节讨论),也就是我们常说的比特流。
2、把数据发送到网络上,就是所谓的分组流应用(涉及到RTSP、rtmp等等封装协议,这里不细讲)。


NALU和比特流之间的关系


常见的比特流如下图所示:

HEVC解码器HM源码阅读(三)读取一个NALU
1、NALU按照顺序存放在比特流中
2、比特流的NALU之间存在若干字节,用于对NALU进行分隔
3、比特流以leading_zero_8bits开始,其值是0x00
4、如果NALU存放的是VPS、SPS、PPS或者第一块压缩数据,在NALU的前面添加zero_byte,值是0x00
5、在zero_byte(如果存在)与NALU之间添加start_code_prefix_one_3bytes,值是0x000001
6、根据需要可以在NALU的后面添加trailing_zero_8bits用于填充,值为0x00



NALU的读取流程


读取流程:
1、打开数据文件
2、使用InputByteStream对数据文件的操作进行封装
3、循环读取文件
(1)定义一个AnnexBStats对象,用于统计比特流中的一些信息
(2)定义一个InputNALUnit对象,表示NALU单元的头部
(3)定义一个vector<uint8_t>对象nalUnit,表示NALU的数据部分
(4)调用byteStreamNALUnit/_byteStreamNALUnit函数,读取一个NALU
(5)调用read函数,把NALU的头部解析出来


读取一个NALU

NALU存放在比特流中,需要一个一个的读取出来

// 从比特流中把一个NALU读取出来	
static void _byteStreamNALUnit(
  InputByteStream& bs,
  vector<uint8_t>& nalUnit,
  AnnexBStats& stats)
{

// 一直读取,直到遇到zero_byte(可选)+start_code_prefix_one_3bytes
  while ((bs.eofBeforeNBytes(24/8) || bs.peekBytes(24/8) != 0x000001)
  &&     (bs.eofBeforeNBytes(32/8) || bs.peekBytes(32/8) != 0x00000001))
  {
    uint8_t leading_zero_8bits = bs.readByte();
    assert(leading_zero_8bits == 0);
    stats.m_numLeadingZero8BitsBytes++;
  }

  // 如果zero_byte存在
  if (bs.peekBytes(24/8) != 0x000001)
  {
    uint8_t zero_byte = bs.readByte();
    assert(zero_byte == 0);
    stats.m_numZeroByteBytes++;
  }

  // 读取start_code_prefix_one_3bytes
  uint32_t start_code_prefix_one_3bytes = bs.readBytes(24/8);
  assert(start_code_prefix_one_3bytes == 0x000001);
  stats.m_numStartCodePrefixBytes += 3;

  // 读取NALU(包括头部和载荷)
  while (bs.eofBeforeNBytes(24/8) || bs.peekBytes(24/8) > 2) 
  {
    nalUnit.push_back(bs.readByte());
  }
  
  // 如果存在trailing_zero_8bits,那么就一直读取,并丢弃
  while ((bs.eofBeforeNBytes(24/8) || bs.peekBytes(24/8) != 0x000001)
  &&     (bs.eofBeforeNBytes(32/8) || bs.peekBytes(32/8) != 0x00000001))
  {
    uint8_t trailing_zero_8bits = bs.readByte();
    assert(trailing_zero_8bits == 0);
    stats.m_numTrailingZero8BitsBytes++;
  }
}


分离NALU的头部和载荷

NALU由头部和载荷组成,载荷也叫做RBSP

/*
** 从NALU中把头部和载荷分离出来
*/
void read(InputNALUnit& nalu, vector<uint8_t>& nalUnitBuf)
{
  /* perform anti-emulation prevention */
  TComInputBitstream *pcBitstream = new TComInputBitstream(NULL);
  
  // 把载荷(Payload)转换成RBSP,因为荷载是经过格式化的数据
  convertPayloadToRBSP(nalUnitBuf, pcBitstream, (nalUnitBuf[0] & 64) == 0);
  
  nalu.m_Bitstream = new TComInputBitstream(&nalUnitBuf);
  nalu.m_Bitstream->setEmulationPreventionByteLocation(pcBitstream->getEmulationPreventionByteLocation());
  delete pcBitstream;
  // 读取NALU的头部
  readNalUnitHeader(nalu);
}

把载荷转换成原始数据

编码器生成的原始数据的长度不一定是整数个字节,需要添加若干比特形成整数个字节,然后经过格式化,形成RBSP

/*
** 把载荷转换成RBSP,因为荷载是经过格式化的数据
** RBSP表示整字节化的SODB(String Of Data Bits,也就是编码器生成的数据)
** 编码器生成的原始数据的长度不一定是整数个字节,需要添加若干比特形成整数个字节
*/
static void convertPayloadToRBSP(vector<uint8_t>& nalUnitBuf, TComInputBitstream *bitstream, Bool isVclNalUnit)
{
  UInt zeroCount = 0;
  vector<uint8_t>::iterator it_read, it_write;

  UInt pos = 0;
  bitstream->clearEmulationPreventionByteLocation();
  for (it_read = it_write = nalUnitBuf.begin(); it_read != nalUnitBuf.end(); it_read++, it_write++, pos++)
  {
	// 移除在SODB中添加的0x03字节(该字节用于格式化)
    assert(zeroCount < 2 || *it_read >= 0x03);
    if (zeroCount == 2 && *it_read == 0x03)
    {
      bitstream->pushEmulationPreventionByteLocation( pos );
      pos++;
      it_read++;
      zeroCount = 0;
      if (it_read == nalUnitBuf.end())
      {
        break;
      }
    }
    zeroCount = (*it_read == 0x00) ? zeroCount+1 : 0;
    *it_write = *it_read;
  }
  assert(zeroCount == 0);
  
  // 把cabac_zero_word移除
  if (isVclNalUnit)
  {
    // Remove cabac_zero_word from payload if present
    Int n = 0;
    
    while (it_write[-1] == 0x00)
    {
      it_write--;
      n++;
    }
    
    if (n > 0)
    {
      printf("\nDetected %d instances of cabac_zero_word", n/2);      
    }
  }

  nalUnitBuf.resize(it_write - nalUnitBuf.begin());
}



解析NALU的头部


/*
** 从NALU中解析出头部(用InputNALUnit表示头部)
** 头部的长度固定是2字节
*/
Void readNalUnitHeader(InputNALUnit& nalu)
{
  TComInputBitstream& bs = *nalu.m_Bitstream;

  // 头部的forbidden_zero_bit,它的值固定是0,长度是1 比特
  Bool forbidden_zero_bit = bs.read(1);           // forbidden_zero_bit
  assert(forbidden_zero_bit == 0);
  
  // 解析NALU的类型
  nalu.m_nalUnitType = (NalUnitType) bs.read(6);  // nal_unit_type
  
  // 解析reservedZero6Bits,值固定是0
  nalu.m_reservedZero6Bits = bs.read(6);       // nuh_reserved_zero_6bits
  assert(nalu.m_reservedZero6Bits == 0);
  
  // 解析NALU的时域层ID
  nalu.m_temporalId = bs.read(3) - 1;             // nuh_temporal_id_plus1

  // 判断时域层id的合法性
  if ( nalu.m_temporalId )
  {
    assert( nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_BLA_W_LP
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_BLA_W_RADL
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_BLA_N_LP
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_IDR_W_RADL
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_IDR_N_LP
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_CRA
         && nalu.m_nalUnitType != NAL_UNIT_VPS
         && nalu.m_nalUnitType != NAL_UNIT_SPS
         && nalu.m_nalUnitType != NAL_UNIT_EOS
         && nalu.m_nalUnitType != NAL_UNIT_EOB );
  }
  else
  {
    assert( nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_TLA_R
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_TSA_N
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_STSA_R
         && nalu.m_nalUnitType != NAL_UNIT_CODED_SLICE_STSA_N );
  }
}


与读取NALU相关的类


比特流统计类

/*
** 比特流统计信息
*/
struct AnnexBStats
{
  UInt m_numLeadingZero8BitsBytes; // leading_zero_8bits的个数
  UInt m_numZeroByteBytes; // zero_byte的个数
  UInt m_numStartCodePrefixBytes; // start_code_prefix_one_3bytes的个数
  UInt m_numBytesInNALUnit; // (所有的)NALU中字节的数量
  UInt m_numTrailingZero8BitsBytes; // trailing_zero_8bits的个数

  AnnexBStats& operator+=(const AnnexBStats& rhs)
  {
    this->m_numLeadingZero8BitsBytes += rhs.m_numLeadingZero8BitsBytes;
    this->m_numZeroByteBytes += rhs.m_numZeroByteBytes;
    this->m_numStartCodePrefixBytes += rhs.m_numStartCodePrefixBytes;
    this->m_numBytesInNALUnit += rhs.m_numBytesInNALUnit;
    this->m_numTrailingZero8BitsBytes += rhs.m_numTrailingZero8BitsBytes;
    return *this;
  }
};



NALU头部

/*
** NALU的头部
*/
struct NALUnit
{
  NalUnitType m_nalUnitType; ///< nal_unit_type NALU的类型
  UInt        m_temporalId;  ///< temporal_id NALU所属的时域层
  UInt        m_reservedZero6Bits; ///< reserved_zero_6bits 

  /** construct an NALunit structure with given header values. */
  NALUnit(
    NalUnitType nalUnitType,
    Int         temporalId = 0,
    Int         reservedZero6Bits = 0)
    :m_nalUnitType (nalUnitType)
    ,m_temporalId  (temporalId)
    ,m_reservedZero6Bits(reservedZero6Bits)
  {}

  /** default constructor - no initialization; must be perfomed by user */
  NALUnit() {}

  /** returns true if the NALunit is a slice NALunit */
  Bool isSlice()
  {
    return m_nalUnitType == NAL_UNIT_CODED_SLICE_TRAIL_R
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_TRAIL_N
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_TLA_R
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_TSA_N
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_STSA_R
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_STSA_N
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_LP
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_W_RADL
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_BLA_N_LP
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_W_RADL
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_IDR_N_LP
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_CRA
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_RADL_N
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_RADL_R
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_RASL_N
        || m_nalUnitType == NAL_UNIT_CODED_SLICE_RASL_R;
  }
  Bool isSei()
  {
    return m_nalUnitType == NAL_UNIT_PREFIX_SEI 
        || m_nalUnitType == NAL_UNIT_SUFFIX_SEI;
  }

  Bool isVcl()
  {
    return ( (UInt)m_nalUnitType < 32 );
  }
};	

/*
** 对NALU的头部进一步包装,表示输入的NALU头部
*/
struct InputNALUnit : public NALUnit
{
  InputNALUnit() : m_Bitstream(0) {};
  ~InputNALUnit() { delete m_Bitstream; }

  TComInputBitstream* m_Bitstream;
};


文件操作的封装类

/*
** 文件操作的封装类
*/
class InputByteStream
{
public:
  InputByteStream(std::istream& istream)
  : m_NumFutureBytes(0)
  , m_FutureBytes(0)
  , m_Input(istream)
  {
    istream.exceptions(std::istream::eofbit);
  }

  void reset()
  {
    m_NumFutureBytes = 0;
    m_FutureBytes = 0;
  }

  // 判断下n个字节是否会遇到文件末尾
  // 同时将字节读取出来
  Bool eofBeforeNBytes(UInt n)
  {
    assert(n <= 4);
    if (m_NumFutureBytes >= n)
      return false;

    n -= m_NumFutureBytes;
    try
    {
      for (UInt i = 0; i < n; i++)
      {
        m_FutureBytes = (m_FutureBytes << 8) | m_Input.get();
        m_NumFutureBytes++;
      }
    }
    catch (...)
    {
      return true;
    }
    return false;
  }

  // 抽取n个字节,原来的字节不会被删除
  uint32_t peekBytes(UInt n)
  {
    eofBeforeNBytes(n);
    return m_FutureBytes >> 8*(m_NumFutureBytes - n);
  }

  // 读取1个字节,原来的字节被删除
  uint8_t readByte()
  {
    if (!m_NumFutureBytes)
    {
      uint8_t byte = m_Input.get();
      return byte;
    }
    m_NumFutureBytes--;
    uint8_t wanted_byte = m_FutureBytes >> 8*m_NumFutureBytes;
    m_FutureBytes &= ~(0xff << 8*m_NumFutureBytes);
    return wanted_byte;
  }

  // 读取n个字节,原来的字节被删除
  uint32_t readBytes(UInt n)
  {
    uint32_t val = 0;
    for (UInt i = 0; i < n; i++)
      val = (val << 8) | readByte();
    return val;
  }

private:
  UInt m_NumFutureBytes; /* number of valid bytes in m_FutureBytes */
  uint32_t m_FutureBytes; /* bytes that have been peeked */
  std::istream& m_Input; /* Input stream to read from */
};