HEVC解碼器HM源碼閱讀(三)讀取一個NALU

讀取一個NALU



視頻數據的兩種存儲傳輸方式


視頻的壓縮數據,有兩種存儲傳輸方式:
1、存放在本地文件中,就是所謂的字節流應用(本章節討論),也就是我們常說的比特流。
2、把數據發送到網絡上,就是所謂的分組流應用(涉及到RTSP、rtmp等等封裝協議,這裏不細講)。


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




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章