HEVC函数入门(24)——比特流

NB_vol_1的博文在比特流之前讲了去方块滤波和SAO,这个部分我打算放到后面,这里先看一下比特流。本文整理自http://blog.csdn.net/nb_vol_1/article/details/55057213
在讲比特流之前先了解下VCL和NAL,HEVC编码分成两个层次,高层处理编码具体细节的被称为VCL(视频编码层)、底层处理比特流的被称为NAL(网络适配层)。预测编码、变换、量化、环路滤波以及熵编码都属于VCL。而处理比特流封装细节的部分则属于NAL。
VCLU
编码之后的数据要在网络上传输,必须按照一定的格式进行封装成报文,这样的数据报文被称为nal unit简称NALU。一个NALU包含了一个参数集或者一个SS(slice segment)的数据;包含一个参数集或者其他信息的NALU被称为non-VCLU;包含一个SS的压缩数据的NALU被称为VCLU。
HEVC规定一副图像中的VCLU具有相同的时域重要性及与其他图像的时域依赖关系。
NALU可以包含一个SS的压缩数据、vps、sps、pps、补充增强信息(SEI)、也可以为定界、序列结束、比特流结束、填充数据等
NALU的结构如下,它包含NALU头部(2字节)和NALU载荷(称为RBSP,整数字节)。
注意,RBSP和压缩的数据(SODB)是有区别的。SODB通过下面方式转换成RBSP:
1、把下面的字节流进行转换:
0x000000 ——> 0x00000300
0x000001 ——> 0x00000301
0x000002 ——> 0x00000302
0x000003 ——> 0x00000303
因为,0x000001是NALU的起始码,0x000000是NALU的结束码,0x000002是预留码,对0x000003进行替换是为了避免与0x0000030X(0x00000300,0x00000301,0x00000302,0x00000303)冲突。
2、如果转换之后的SODB不足整数个字节,那么在后面填充比特0,直到包含整数个字节。
3、最后可能会加入16个比特的0作为填充比特。
NALU的头部:
1比特的forbidden_zero_bit(固定的0比特)
6比特的nal_unit_type(NALU类型)
6比特的nuh_layer_id(当前应该取0,非0值用于3D视频等)
3比特的nuh_temporal_id_plus1,该值减去1表示NALU所在时域层的标识号temporalId,不能为0,temporalId表示NALU的时域层级,根据temporalId可以确定图像的重要性,配合nal_unit_type就可以实现视频流的时域分级。
关于上文出现的一些缩写和概念可以参考我以前的博文:
http://blog.csdn.net/qq_21747841/article/details/73332394
http://blog.csdn.net/qq_21747841/article/details/75224344
Access Unit
HEVC中引入了接入单元(Access Unit,AU)的概念,一个AU表示多个按照顺序(解码顺序)排列的NALU,这些NALU刚好可以解码生成一个图像。
AU可以看作压缩视频比特流的基本单位,即压缩的视频流由多个按顺序排列的AU组成。
一个AU应该包含一幅图像的所有VCLU,还可以包含non-VCLU。一个AU可以从定界NALU、SEI类型的NALU或者第一个SS的NALU开始,可以用最后一个SS的NALU、序列结束NALU或者比特流结束NALU来结束。
一般情况下,一个AU不包含下面类型的NALU:参数集,保留VCL、填充、保留non-VCLU、未明确等。
NALU在网络上的传输
NALU在网上传输的时候分为两种类型:
1、字节流。
NALU生成字节流的过程如下:
(1)在每个NALU前面插入3字节的起始码start_code_prefix_one_3bytes,其值为0x000001
(2)如果NALU的类型为:VPS_NUT,SPS_NUT,PPS_NUT或者解码顺序为一个AU的第一个NALU,则在其起始码前再插入一个zero_byte,值为0x00
(3)在视频首个NALU的起始码(可能包含zero_byte)前插入leading_zero_8bits,值是0x00
(4)根据需要可在每个NALU后增加trailing_zero_8bits,值是0x00,作为填充数据。
2、分组流。使用RTP、RTMP等协议,把NALU直接作为网络分组的有效载荷。
比特流在HEVC中的实现
这里是重点,因为之前都是看的帧内帧间的实现,这里要重新找它的位置,这里是在编码的入口TAppEncTop搜索m_nalUnitType找到的。
HEVC中的实现:
1、NALUnit表示一个NALU头部

/**
 * Represents a single NALunit header and the associated RBSPayload
 */
// NAL单元头部 
struct NALUnit
{// NAL单元的类型 
  NalUnitType m_nalUnitType; ///< nal_unit_type
  UInt        m_temporalId;  ///< temporal_id
  UInt        m_nuhLayerId;  ///< nuh_layer_id

  /** construct an NALunit structure with given header values. */
   // 构造函数
  NALUnit(
    NalUnitType nalUnitType,
    Int         temporalId = 0,
    Int         nuhLayerId = 0)
    :m_nalUnitType (nalUnitType)
    ,m_temporalId  (temporalId)
    ,m_nuhLayerId  (nuhLayerId)
  {}

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

  /** returns true if the NALunit is a slice NALunit */
  // 判断是否为条带,即判断这个NAL单元存放的是否为条带数据  
  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_TSA_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;
  } // 判断这个NAL单元中存放是否为sei(增强信息)  
  Bool isSei()
  {
    return m_nalUnitType == NAL_UNIT_PREFIX_SEI
        || m_nalUnitType == NAL_UNIT_SUFFIX_SEI;
  }
  // 判断是否为vcl  
  Bool isVcl()
  {
    return ( (UInt)m_nalUnitType < 32 );
  }
};

这里开始和原文略有不同,下面是原文中的话,直接摘下来,我们来看看HM16.3中的代码,发现还是可以大致适用的,关键是理解代码的意图。 2、OutputNALUnit表示一个编码器输出的NALU,拥有TComOutputBitstream成员,用于处理编码器的输出,OutputNALUnit直接继承自NALUnit

struct OutputNALUnit : public NALUnit
{
  /**
   * construct an OutputNALunit structure with given header values and
   * storage for a bitstream.  Upon construction the NALunit header is
   * written to the bitstream.
   */
  OutputNALUnit(
    NalUnitType nalUnitType,
    UInt temporalID = 0,
    UInt reserved_zero_6bits = 0)
  : NALUnit(nalUnitType, temporalID, reserved_zero_6bits)
  , m_Bitstream()
  {}

  OutputNALUnit& operator=(const NALUnit& src)
  {
    m_Bitstream.clear();
    static_cast<NALUnit*>(this)->operator=(src);
    return *this;
  }

3、NALUnitEBSP是一个辅助类,专门用来处理OutputNALUnit,它有个ostringstream类型的成员。NALUnitEBSP在构造函数里调用void write(std::ostream& out, OutputNALUnit& nalu);

/**
 * A single NALunit, with complete payload in EBSP format.
 */
 // 一个辅助类,作用是把NALU写到ostringstream(相当于buffer)中  
struct NALUnitEBSP : public NALUnit 
{
  std::ostringstream m_nalUnitData; // 会把NALU的头部连同数据写到这里

  /**
   * convert the OutputNALUnit nalu into EBSP format by writing out
   * the NALUnit header, then the rbsp_bytes including any
   * emulation_prevention_three_byte symbols.
   */
  NALUnitEBSP(OutputNALUnit& nalu);
};
//! \}
//! \}

#endif

4、AccessUnit表示了Access Unit,实际就是一个list,list的元素是NALUnitEBSP (这里不是很懂)

class AccessUnit : public std::list<NALUnitEBSP*> // NOTE: Should not inherit from STL.
{
public:
  ~AccessUnit()
  {
    for (AccessUnit::iterator it = this->begin(); it != this->end(); it++)
    {
      delete *it;
    }
  }
};

5、TComBitIf表示比特流的公共接口

/// pure virtual class for basic bit handling
class TComBitIf
{
public:
  virtual Void        writeAlignOne         () {};
  virtual Void        writeAlignZero        () {};
  virtual Void        write                 ( UInt uiBits, UInt uiNumberOfBits )  = 0;
  virtual Void        resetBits             ()                                    = 0;
  virtual UInt getNumberOfWrittenBits() const = 0;
  virtual ~TComBitIf() {}
};

6、TComOutputBitstream表示编码器的输出比特流,它有一个char类型的vector用于编码器的输出。

class TComOutputBitstream : public TComBitIf
{
  /**
   * FIFO for storage of bytes.  Use:
   *  - fifo.push_back(x) to append words
   *  - fifo.clear() to empty the FIFO
   *  - &fifo.front() to get a pointer to the data array.
   *    NB, this pointer is only valid until the next push_back()/clear()
   */
  std::vector<uint8_t> m_fifo; // 用vector存放数据
 // 已经持有,但是还没有冲刷的比特数 
  UInt m_num_held_bits; /// number of bits not flushed to bytestream.
  // 总的比特数,包括还没有冲刷的
  UChar m_held_bits; /// the bits held and not flushed to bytestream.
                             /// this value is always msb-aligned, bigendian.
public:
  // create / destroy
  TComOutputBitstream();
  ~TComOutputBitstream();

  // interface for encoding
  /**
   * append uiNumberOfBits least significant bits of uiBits to
   * the current bitstream
   */
   // 写入比特,数据存放在uiBits,比特数存放在uiNumberOfBits  
  Void        write           ( UInt uiBits, UInt uiNumberOfBits );

  /** insert one bits until the bitstream is byte-aligned */
  // 写入比特1直到按字节对齐  
  Void        writeAlignOne   ();
  // 写入比特0知道按字节对齐
  /** insert zero bits until the bitstream is byte-aligned */
  Void        writeAlignZero  ();

  /** this function should never be called */
  Void resetBits() { assert(0); }

  // utility functions

  /**
   * Return a pointer to the start of the byte-stream buffer.
   * Pointer is valid until the next write/flush/reset call.
   * NB, data is arranged such that subsequent bytes in the
   * bytestream are stored in ascending addresses.
   */
  Char* getByteStream() const;

  /**
   * Return the number of valid bytes available from  getByteStream()
   */
  UInt getByteStreamLength();

  /**
   * Reset all internal state.
   */
  Void clear();

  /**
   * returns the number of bits that need to be written to
   * achieve byte alignment.
   */
  Int getNumBitsUntilByteAligned() { return (8 - m_num_held_bits) & 0x7; }

  /**
   * Return the number of bits that have been written since the last clear()
   */
  UInt getNumberOfWrittenBits() const { return UInt(m_fifo.size()) * 8 + m_num_held_bits; }

  Void insertAt(const TComOutputBitstream& src, UInt pos);

  /**
   * Return a reference to the internal fifo
   */
  std::vector<uint8_t>& getFIFO() { return m_fifo; }

  UChar getHeldBits  ()          { return m_held_bits;          }

  //TComOutputBitstream& operator= (const TComOutputBitstream& src);
  /** Return a reference to the internal fifo */
  const std::vector<uint8_t>& getFIFO() const { return m_fifo; }
  // 添加子数据流 
  Void          addSubstream    ( TComOutputBitstream* pcSubstream );
  Void writeByteAlignment();

  //! returns the number of start code emulations contained in the current buffer
  Int countStartCodeEmulations();
};

7、TComInputBitstream表示解码器的输入比特流
8、 NALUnit(OutputNALUnit)与TComBitIfTComOutputBitstream)之间关系:NALUnit是NALU的头部,而TComBitIf表示有效的编码数据
9、那么NALUnit与TComBitIf(TComOutputBitstream)是怎么样结合起来的,这就要从熵编码器入手了:
(1)先定义一个OutputNALUnit,因为OutputNALUnit包含TComOutputBitstream成员,因此比特流也同时存在了;
(2)TEncEntropy有一个setBitstream函数,用于设置熵编码比特流,setBitstream的参数就是OutputNALUnit中的TComOutputBitstream成员。因此,这时候熵编码的输出就可以写入比特流(TComOutputBitstream)中了。
(3)最后AccessUnit是一个NALUnitEBSP的队列,NALUnitEBSP的构造函数需要OutputNALUnit类型的参数,实际NALUnitEBSP的构造函数内部就会调用
write(std::ostream& out, OutputNALUnit& nalu)函数,这个函数用来把NALU写到NALUnitEBSP的ostringstream(相当于一个buffer)成员中,最后,NALUnitEBSP被添加到AccessUnit中。

Void write(ostream& out, OutputNALUnit& nalu)
{
  writeNalUnitHeader(out, nalu);
  /* write out rsbp_byte's, inserting any required
   * emulation_prevention_three_byte's */
  /* 7.4.1 ...
   * emulation_prevention_three_byte is a byte equal to 0x03. When an
   * emulation_prevention_three_byte is present in the NAL unit, it shall be
   * discarded by the decoding process.
   * The last byte of the NAL unit shall not be equal to 0x00.
   * Within the NAL unit, the following three-byte sequences shall not occur at
   * any byte-aligned position:
   *  - 0x000000
   *  - 0x000001
   *  - 0x000002
   * Within the NAL unit, any four-byte sequence that starts with 0x000003
   * other than the following sequences shall not occur at any byte-aligned
   * position:
   *  - 0x00000300
   *  - 0x00000301
   *  - 0x00000302
   *  - 0x00000303
   */
  vector<uint8_t>& rbsp   = nalu.m_Bitstream.getFIFO();

  vector<uint8_t> outputBuffer;
  outputBuffer.resize(rbsp.size()*2+1); //there can never be enough emulation_prevention_three_bytes to require this much space
  std::size_t outputAmount = 0;
  Int         zeroCount    = 0;
  for (vector<uint8_t>::iterator it = rbsp.begin(); it != rbsp.end(); it++)
  {
    const uint8_t v=(*it);
    if (zeroCount==2 && v<=3)
    {
      outputBuffer[outputAmount++]=emulation_prevention_three_byte[0];
      zeroCount=0;
    }

    if (v==0)
    {
      zeroCount++;
    }
    else
    {
      zeroCount=0;
    }
    outputBuffer[outputAmount++]=v;
  }

  /* 7.4.1.1
   * ... when the last byte of the RBSP data is equal to 0x00 (which can
   * only occur when the RBSP ends in a cabac_zero_word), a final byte equal
   * to 0x03 is appended to the end of the data.
   */
  if (zeroCount>0)
  {
    outputBuffer[outputAmount++]=emulation_prevention_three_byte[0];
  }
  out.write((Char*)&(*outputBuffer.begin()), outputAmount);
}

(4)最后我们得到了一个完整的AccessUnit(包含一个图像的所有的NALU),压缩的数据都在AccessUnit的NALUnitEBSP的ostringstream成员中
(5)最后我们需要把AccessUnit写到网络或者本地磁盘上,需要使用writeAnnexB函数(这个函数被xWriteOutput函数调用),该函数使用字节流的方式,把数据写到网络或者磁盘上。

static std::vector<UInt> writeAnnexB(std::ostream& out, const AccessUnit& au)
{//通过在编码器入口搜索writeAnnexB找到的
  std::vector<UInt> annexBsizes;

  for (AccessUnit::const_iterator it = au.begin(); it != au.end(); it++)
  {
    const NALUnitEBSP& nalu = **it;
    UInt size = 0; /* size of annexB unit in bytes */

    static const Char start_code_prefix[] = {0,0,0,1};
    if (it == au.begin() || nalu.m_nalUnitType == NAL_UNIT_VPS || nalu.m_nalUnitType == NAL_UNIT_SPS || nalu.m_nalUnitType == NAL_UNIT_PPS)
    {
      /* From AVC, When any of the following conditions are fulfilled, the
       * zero_byte syntax element shall be present:
       *  - the nal_unit_type within the nal_unit() is equal to 7 (sequence
       *    parameter set) or 8 (picture parameter set),
       *  - the byte stream NAL unit syntax structure contains the first NAL
       *    unit of an access unit in decoding order, as specified by subclause
       *    7.4.1.2.3.
       */
      out.write(start_code_prefix, 4);
      size += 4;
    }
    else
    {
      out.write(start_code_prefix+1, 3);
      size += 3;
    }
    out << nalu.m_nalUnitData.str();
    size += UInt(nalu.m_nalUnitData.str().size());

    annexBsizes.push_back(size);
  }

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