1、引言+nal.h+nal_read

一直在学习编解码相关的知识,但是在这个社会中,似乎自学算法,难度确实会有点大;很多人从入门便开始放弃;而我现在也差点步入放弃的队伍之中;因为总是很难找到学习方法,很难看懂一些东西;
但是,经过我很长时间的摸索,我发现了一个问题;那就是与其自己去长时间的看编码的东西,不如花时间去先看解码的东西;因为如果解码搞懂了,编码自然而然的就不是问题了;因为步骤只是反过来而已;而且通过解码,可以搞懂每个参数到底有什么用,编码器的实现可能多种多样,而且算法复杂造成函数写起来实现会比较庞大;但是解码不同;拿HM的简单的小解码器,就很容易把265的框架搞通;
===================================================
这里,我会做到原创,因为抄袭是在是没有用,除非是文章写得很好,我看明白了,我会放到这里,并注明引用。来保证自己的原创精神,也希望如果有缘看到我的文章,可以提出各种各样的建议,学习方法。错误;不胜感激
===================================================
首先来说,这里我分析的是基于HM15.0版本的265解码部分;
先看NAL.h
因为代码比较短,所以我还是把它贴过来

#pragma once

#include <vector>
#include <sstream>
#include "CommonDef.h"

class TComOutputBitstream;

/**
 * Represents a single NALunit header and the associated RBSPayload
 */
struct NALUnit
{
  NalUnitType m_nalUnitType; ///< nal_unit_type
  UInt        m_temporalId;  ///< temporal_id
  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_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;
  }
  Bool isSei()
  {
    return m_nalUnitType == NAL_UNIT_PREFIX_SEI 
        || m_nalUnitType == NAL_UNIT_SUFFIX_SEI;
  }

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

struct OutputNALUnit;

/**
 * A single NALunit, with complete payload in EBSP format.
 */
struct NALUnitEBSP : public NALUnit
{
  std::ostringstream m_nalUnitData;

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

这里主要是看NALUnit这个结构体,首先定义结构体的成员,包括naltype,temporalId,reservedzero6bits;这三个,第一个指的是nal的类型;第二个指的是时域层标识号,这个标识号是nal header的最后3bits -1得来的;最后一个参数是6位reseved nalheader的东西;这个我们不用特别的关心,感觉用处是不大的;
这里,如果概念不是很清楚的话,请看《HEVC原理,标准与实现》第9章网络适配层的部分;就会一目了然
紧接着是构造函数,这个不多说;
然后是判断该nal是否为slice nal,这个参考265的nal类型可以看出哪些nal是slice类型;当然,通过代码可以很清楚的看出来;
然后是SEI nal判断,这个判断同上
然后是是否为图像判断,这个通过看书还是看代码都很清楚的可以看出,当nal<32的时候,是代表这个nal承载的内容是图像内容,不是non-vcal内容;
=======================================================
上面讲到这里,nal的东西,也就是说,nal的基础类其实就这些东西;按道理来说,这一章,我没有必要去分析下面的nalunitebsp内容;因为这部分是集成的nalunit;不过也可以看一下;
首先说什么是ebsp,我们的图像,编码出来以后,是裸流,二进制的裸流,这个没有争议,这个流称之为SODB;然后对SODB会进行封装,封装成为RBSP,这个封装其实就是把裸流封成一个字节一个字节的样子;
我们说,sodb是一个bit一个bit的;俺么肯定最后是不一定凑得齐1个字节的;那么RBSP就会补充好最后,让他成为1个字节,补充的东西就是一个1,和若干个0(还有可能有0x0000补充在最后面);当RBSP封装好了以后,加上nal同步字节头,就是0x00000001或者是0x000001,结尾加上0x000000,那么这个nal就封装好了;
但是这样封装好了,真的可以用嘛;当然不行,因为如果在nal里面有0x000000或者0x000001怎么办呢?所以要因为防止竞争的机制;就是如果出现了连续两个0x0000了,那么后面如果是01/02/03的话,我们就会补充上03,防止竞争;也就是说,如果你发现在nal里,有0x00000301,那么这个03其实是为了防止竞争,也就是怕人们当做成为nal起始,而加入的东西;
讲到这里,主要是如果你配上书的话,那么就会很容易懂得这块代码;如果你没有理论,那么这块简直不知道在说啥,理论就去参考HEVC原理,标准与实现》第9章网络适配层的部分
=========================================================
讲到这里,大概对nal有了一个比较清楚的认识,下面我们看一下HM是如何开始读取nal的;这里我以一个我自己的例子,也就是265的vps nal为例;先看一下二进制的数据,如下:


image.png

然后我们直接讲nalread.cpp
先看read函数

void read(InputNALUnit& nalu, vector<uint8_t>& nalUnitBuf)
{
  /* perform anti-emulation prevention */
  TComInputBitstream *pcBitstream = new TComInputBitstream(NULL);
  convertPayloadToRBSP(nalUnitBuf, pcBitstream, (nalUnitBuf[0] & 64) == 0);
  
  nalu.m_Bitstream = new TComInputBitstream(&nalUnitBuf);
  nalu.m_Bitstream->setEmulationPreventionByteLocation(pcBitstream->getEmulationPreventionByteLocation());
  delete pcBitstream;
  readNalUnitHeader(nalu);
}

这里面代码较为简单,我一句句的分析:
1、首先,初始化一个输入的码流;
2、需要把EBSP开始化成RBSP,也就是把这些03防止竞争码都去掉;
3、初始化nalu里的码流;
4、把除去了竞争码以后的数据,给到nalu里面;这里面的函数操作简单的很,其实就是一个容器的操作;push-back;
5、读取nalu头;这里面就是我们上面分析的那一坨;
下面我把EBSP转成RBSP的函数以及读取nail头都粘贴到这里;仅供参考:

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++)
  {
    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;
      }
      assert(*it_read <= 0x03);
    }
    zeroCount = (*it_read == 0x00) ? zeroCount+1 : 0;
    *it_write = *it_read;
  }
  assert(zeroCount == 0);
  
  if (isVclNalUnit)
  {
    // Remove cabac_zero_word from payload if present
    Int n = 0;
    
    while (it_write[-1] == 0x00)
    {
      it_write--;
      n++;
    }
    
    if (n > 0)
    {
      //拖尾0
      printf("\nDetected %d instances of cabac_zero_word", n/2);      
    }
  }

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

Void readNalUnitHeader(InputNALUnit& nalu)
{
  TComInputBitstream& bs = *nalu.m_Bitstream;

  Bool forbidden_zero_bit = bs.read(1);           // forbidden_zero_bit
  assert(forbidden_zero_bit == 0);
  nalu.m_nalUnitType = (NalUnitType) bs.read(6);  // nal_unit_type
  nalu.m_reservedZero6Bits = bs.read(6);       // nuh_reserved_zero_6bits
  assert(nalu.m_reservedZero6Bits == 0);
  nalu.m_temporalId = bs.read(3) - 1;             // nuh_temporal_id_plus1

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

到此,我们知道如何获取一个nal的数据,并将它转换从EBSP--->RBSP得到字节表示的二进制码流;后面,也就开始了解析;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。