Aprendizaje de código VTM10.0 2: decodificación NALU

Esta serie es para registrar el proceso de aprendizaje de VTM10.0 y ejercitar la capacidad de expresión, principalmente desde el lado de la decodificación. Debido a mi nivel limitado, insto a todos a que me corrijan si cometo algún error. Bienvenidos a comunicarnos y progresar juntos.


1. m_cDecLib.decode

Luego continuará el último blog de esta serie La sección anterior 2.1.1 mencionó que la función m_cDecLib.decode es llamar a la clase decodificadora para la decodificación NALU. NALU se divide en dos categorías: VCL y no VCL. Hay muchas subdivisiones bajo estos dos tipos. La decodificación correspondiente también necesita decodificar diferentes tipos de NALU por separado.

  bool ret;
  // ignore all NAL units of layers > 0

  //将NALU的头信息存入顺序容器
  AccessUnitInfo auInfo;
  auInfo.m_nalUnitType = nalu.m_nalUnitType;
  auInfo.m_nuhLayerId = nalu.m_nuhLayerId;
  auInfo.m_temporalId = nalu.m_temporalId;
  m_accessUnitNals.push_back(auInfo);
  m_pictureUnitNals.push_back( nalu.m_nalUnitType );

  switch (nalu.m_nalUnitType)//根据NALU的类型分别解码
  {
    
    
    case NAL_UNIT_VPS:
      xDecodeVPS( nalu );//解码VPS 参考JVET-S2001 7.3.2.3 P83
      m_vps->m_targetOlsIdx = iTargetOlsIdx;//设置m_vps的目标输出层集的索引
      return false;
    case NAL_UNIT_DCI:
      xDecodeDCI( nalu );//解码DCI 参考JVET-S2001 7.3.2.1 P83
      return false;
    case NAL_UNIT_SPS:
      xDecodeSPS( nalu );//解码SPS 参考JVET-S2001 7.3.2.4 P86
      return false;

    case NAL_UNIT_PPS:
      xDecodePPS( nalu );//解码PPS 参考JVET-S2001 7.3.2.5 P91
      return false;

    case NAL_UNIT_PH:
      xDecodePicHeader(nalu);//解码picture header,如果解码过程处于一帧中的第一个slice,那么返回false。参考JVET-S2001 7.3.2.7 P95
      return !m_bFirstSliceInPicture;

    case NAL_UNIT_PREFIX_APS:
    case NAL_UNIT_SUFFIX_APS:
      xDecodeAPS(nalu);//解码APS 参考JVET-S2001 7.3.2.6 P94
      return false;

    case NAL_UNIT_CODED_SLICE_TRAIL:
    case NAL_UNIT_CODED_SLICE_STSA:
    case NAL_UNIT_CODED_SLICE_IDR_W_RADL:
    case NAL_UNIT_CODED_SLICE_IDR_N_LP:
    case NAL_UNIT_CODED_SLICE_CRA:
    case NAL_UNIT_CODED_SLICE_GDR:
    case NAL_UNIT_CODED_SLICE_RADL:
    case NAL_UNIT_CODED_SLICE_RASL:
      ret = xDecodeSlice(nalu, iSkipFrame, iPOCLastDisplay);//解码VCL类型的NALU 参考JVET-S2001 7.3.2.14 P99
      return ret;

    case NAL_UNIT_EOS://遇到EOS,重置解码器类中的一些属性
      m_associatedIRAPType[nalu.m_nuhLayerId] = NAL_UNIT_INVALID;
      m_pocCRA[nalu.m_nuhLayerId] = MAX_INT;
      m_prevGDRInSameLayerPOC[nalu.m_nuhLayerId] = MAX_INT;
      std::fill_n(m_prevGDRSubpicPOC[nalu.m_nuhLayerId], MAX_NUM_SUB_PICS, MAX_INT);
      memset(m_prevIRAPSubpicPOC[nalu.m_nuhLayerId], 0, sizeof(int)*MAX_NUM_SUB_PICS);
      memset(m_prevIRAPSubpicDecOrderNo[nalu.m_nuhLayerId], 0, sizeof(int)*MAX_NUM_SUB_PICS);
      std::fill_n(m_prevIRAPSubpicType[nalu.m_nuhLayerId], MAX_NUM_SUB_PICS, NAL_UNIT_INVALID);
      m_pocRandomAccess = MAX_INT;
      m_prevLayerID = MAX_INT;
      m_prevPOC = MAX_INT;
      m_prevSliceSkipped = false;
      m_skippedPOC = 0;
      m_accessUnitEos[nalu.m_nuhLayerId] = true;
#if JVET_S0155_EOS_NALU_CHECK
      m_prevEOS[nalu.m_nuhLayerId] = true;
#endif
      return false;

    case NAL_UNIT_ACCESS_UNIT_DELIMITER://遇到AU分隔符
      {
    
    
        AUDReader audReader;
        uint32_t picType;//参考JVET-S2001 aud_pic_type P179
        audReader.parseAccessUnitDelimiter(&(nalu.getBitstream()), m_audIrapOrGdrAuFlag, picType);//参考JVET-S2001 7.3.2.10 P53
        return !m_bFirstSliceInPicture;
      }

    case NAL_UNIT_EOB://遇到EOB(end of bitstream)
      return false;
          
    //......
  }

Lo anterior es el interior de m_cDecLib.decode. En primer lugar, hay una variable ret. No he descubierto qué es por ahora.

Entonces se almacena la información del encabezado de NALU.

La última es la instrucción switch para determinar el tipo de NALU y llamar a la función correspondiente. El SEI omitido y los tipos de NALU reservados o no definidos. Excepto en los casos en los que se llama a xDecodeSlice, el resto son todos tipos que no son de VCL. Para obtener más detalles, consulte JVET-s2001, cuya parte también se encuentra en los comentarios. xDecodeSlice es la función que se discutirá a continuación.


2. xDecodeSlice

xDecodeSlice es la función para decodificar un segmento de corte. El tipo de trama donde se ubica el segmento de corte corresponde al tipo de NALU. Primero explique los dos parámetros entrantes:

  • iSkipFrame: el número de fotogramas para omitir la decodificación
  • iPOCLastDisplay: el valor mínimo de POC en todos los cuadros que se decodificarán

Permítanme hablar sobre mi comprensión del segmento de segmento y segmento El segmento de segmento es la unidad de transmisión NALU. Un segmento de corte independiente va seguido de varios segmentos de corte no independientes para formar un corte. Debería ser que solo los segmentos de sector independientes necesiten decodificar el encabezado del sector. (No estoy seguro aquí, solo eche un vistazo)

xDecodeSlice se divide principalmente en dos partes, la primera parte es la operación en m_apcSlicePilot, y la segunda parte es la operación en pcSlice. Dado que la primera parte es bastante ignorante para mí, la segunda parte es más importante, por lo que mencionaré la primera parte ligeramente.


2.1 m_apcSlicePilot

  //m_apcSlicePilot用于解码slice的类指针,将picture header信息传入并初始化
  m_apcSlicePilot->setPicHeader( &m_picHeader );
  m_apcSlicePilot->initSlice(); // the slice pilot is an object to prepare for a new slice
                                // it is not associated with picture, sps or pps structures.

  Picture* scaledRefPic[MAX_NUM_REF] = {
    
    };//存有缩放参考帧的指针数组

  //分支1
  if (m_bFirstSliceInPicture)
  {
    
    
    m_uiSliceSegmentIdx = 0;//一帧内slice segment的index
  }
  else
  {
    
    
    m_apcSlicePilot->copySliceInfo( m_pcPic->slices[m_uiSliceSegmentIdx-1] );//复制上一个slice segment的信息
  }

  //将NALU的头信息传入m_apcSlicePilot中
  m_apcSlicePilot->setNalUnitType(nalu.m_nalUnitType);
  m_apcSlicePilot->setNalUnitLayerId(nalu.m_nuhLayerId);
  m_apcSlicePilot->setTLayer(nalu.m_temporalId);

  m_apcSlicePilot->m_ccAlfFilterParam = m_cALF.getCcAlfFilterParam();
  m_HLSReader.setBitstream( &nalu.getBitstream() );
  m_HLSReader.parseSliceHeader( m_apcSlicePilot, &m_picHeader, &m_parameterSetManager, m_prevTid0POC, m_prevPicPOC );//解码slice header 参考JVET-S2001 7.3.7 P107

setPicHeader (): pasa la información del encabezado de la imagen a m_apcSlicePilot

initSlice (): el proceso de inicialización de m_apcSlicePilot

scaledRefPic: Aquí se almacena la lista de marcos de referencia escalados

Rama 1: si el proceso de decodificación está en el primer segmento de una trama, m_uiSliceSegmentIdx debe establecerse en cero, que es el índice del segmento de segmento. De lo contrario, copie la información del último segmento de corte en m_apcSlicePilot.

Tres funciones establecidas: pasar la información del encabezado NALU a m_apcSlicePilot

m_ccAlfFilterParam: almacena los parámetros ALF del segmento de corte anterior

setBitstream () y parseSliceHeader (): Lo primero es establecer la fuente de flujo de bits de m_HLSReader, y parseSliceHeader comenzará a decodificar el encabezado del segmento.

Algunos de los procesamientos posteriores son más incómodos, la mayoría de los cuales son procesamientos a nivel de cuadro, como si el cuadro actual se omite para la decodificación.

  if (isRandomAccessSkipPicture(iSkipFrame, iPOCLastDisplay))
  {
    
    
    m_prevSliceSkipped = true;                        //设置解码器类的表示前一个slice跳过解码的flag为true
    m_skippedPOC = m_apcSlicePilot->getPOC();         //传入被跳过解码slice所在帧的POC
    return false;
  }

2,2 m_pcPic

El siguiente contenido es variado y complicado, y todos son trabajos preparatorios, que se explican en secciones

  xActivateParameterSets( nalu );//设置一下各种parameter sets的语法元素

  m_firstSliceInSequence[nalu.m_nuhLayerId] = false;
  m_firstSliceInBitstream  = false;

  Slice* pcSlice = m_pcPic->slices[m_uiSliceSegmentIdx];//取出图像类存有的slice segement
#if JVET_R0270
  m_pcPic->numSlices = m_uiSliceSegmentIdx + 1;         //slice segement的数量
#endif
  pcSlice->setPic( m_pcPic );                           //设置slice所在picture的指针
  m_pcPic->poc         = pcSlice->getPOC();
  m_pcPic->referenced  = true;                          //此帧是否被参考
  m_pcPic->temporalId  = nalu.m_temporalId;
  m_pcPic->layerId     = nalu.m_nuhLayerId;
  m_pcPic->subLayerNonReferencePictureDueToSTSA = false;//是否参考同一时域层的帧?

xActivateParameterSets (): establece los atributos relacionados de los conjuntos de parámetros en m_pcPic, y también existe la operación de asignar m_apcSlicePilot a m_pcPic, que vale la pena ver más de cerca (aunque no lo leí).

    //下面就是将m_apcSlicePilot赋值给m_pcPic的操作
    m_pcPic->allocateNewSlice();
    m_apcSlicePilot = m_pcPic->swapSliceObject(m_apcSlicePilot, m_uiSliceSegmentIdx);

pcSlice: saca el segmento de corte correspondiente para ser procesado en la clase de imagen

numSlices: el número de segmentos de corte en la clase de imagen

setPic (): establece la imagen donde se encuentra la clase de segmento

Las siguientes son todas las operaciones sobre tipos de imágenes. Generalmente, estos cambios solo tienen sentido cuando el proceso de decodificación alcanza el primer NALU de tipo VCL en un cuadro.


  if (m_bFirstSliceInPicture)
  {
    
    
    m_pcPic->setDecodingOrderNumber(m_decodingOrderCounter);//设置帧的解码顺序
    m_decodingOrderCounter++;
    m_pcPic->setPictureType(nalu.m_nalUnitType);
      
    // store sub-picture numbers, sizes, and locations with a picture
	// 有关subpicture的操作
    pcSlice->getPic()->subPictures.clear();

    for( int subPicIdx = 0; subPicIdx < sps->getNumSubPics(); subPicIdx++ )
    {
    
    
      pcSlice->getPic()->subPictures.push_back( pps->getSubPic( subPicIdx ) );
    }
      
    pcSlice->getPic()->numSlices = pps->getNumSlicesInPic();
    pcSlice->getPic()->sliceSubpicIdx.clear();
  }

Esta rama se activa solo cuando el proceso de decodificación está en el primer segmento de una trama.

Las primeras tres oraciones son para establecer el orden de decodificación y el tipo de imagen de la clase de imagen.

Primero borre la información de subPictures almacenada en la clase de imagen y luego recupérela de PPS (subPictures.clear () y for loop)

numSlices: establece el número de slcies de la clase de imagen

sliceSubpicIdx.clear (): Borrar el índice de subimágenes


  pcSlice->getPic()->sliceSubpicIdx.push_back(pps->getSubPicIdxFromSubPicId(pcSlice->getSliceSubPicId()));//从slice中获subpictureId转为subpictureIndex,存入slice所在帧类的容器sliceSubpicIdx
  pcSlice->constructRefPicList(m_cListPic);                                                               //构建参考帧列表
  pcSlice->setPrevGDRSubpicPOC(m_prevGDRSubpicPOC[nalu.m_nuhLayerId][currSubPicIdx]);
  pcSlice->setPrevIRAPSubpicPOC(m_prevIRAPSubpicPOC[nalu.m_nuhLayerId][currSubPicIdx]);
  pcSlice->setPrevIRAPSubpicType(m_prevIRAPSubpicType[nalu.m_nuhLayerId][currSubPicIdx]);
  pcSlice->scaleRefPicList( scaledRefPic, m_pcPic->cs->picHeader, m_parameterSetManager.getAPSs(), m_picHeader.getLmcsAPS(), m_picHeader.getScalingListAPS(), true );//构造缩放的参考帧列表,里面注释真正的缩放过程没看,应该是编码处用到的。传入的scaledRefPic貌似也没啥改动,就清空了一下

sliceSubpicIdx.push_back (): almacena la información del índice de subimágenes donde se encuentra cada rebanada

constructRefPicList (): construye una lista de marcos de referencia, vale la pena echarle un vistazo

Los siguientes tres conjuntos seguidos son bastante ignorantes, por lo que no lo explicaré temporalmente.

scaleRefPicList (): crea una lista de marcos de referencia para hacer zoom, que también vale la pena ver


    if (!pcSlice->isIntra())//如果不是I帧
    {
    
    
      bool bLowDelay = true;//是否是LowDelay模式
      int  iCurrPOC  = pcSlice->getPOC();//当前slice所在帧的POC
      int iRefIdx = 0;//参考帧的Index,用来循环

	  //下面两个循环就是判断此slice所在帧的参考帧的poc顺序是不是在当前poc之前,如果之后就不是LowDelay模式
      for (iRefIdx = 0; iRefIdx < pcSlice->getNumRefIdx(REF_PIC_LIST_0) && bLowDelay; iRefIdx++)
      {
    
    
        if ( pcSlice->getRefPic(REF_PIC_LIST_0, iRefIdx)->getPOC() > iCurrPOC )
        {
    
    
          bLowDelay = false;
        }
      }
      if (pcSlice->isInterB())
      {
    
    
        for (iRefIdx = 0; iRefIdx < pcSlice->getNumRefIdx(REF_PIC_LIST_1) && bLowDelay; iRefIdx++)
        {
    
    
          if ( pcSlice->getRefPic(REF_PIC_LIST_1, iRefIdx)->getPOC() > iCurrPOC )
          {
    
    
            bLowDelay = false;
          }
        }
      }

      pcSlice->setCheckLDC(bLowDelay);//传入slice类中
    }

	//如果开启SMVD模式,当然此时就不可能LowDelay,当然此时也必须要有MVD的传输
    if (pcSlice->getSPS()->getUseSMVD() && pcSlice->getCheckLDC() == false
      && pcSlice->getPicHeader()->getMvdL1ZeroFlag() == false
      )
    {
    
    
    }

El primero es principalmente para establecer si la clase de segmento es LowDelay o no. La condición de juicio también es simple. Veamos el código.

El segundo si está relacionado con el modo SMVD. El código es más largo de lo que se publica y la lógica no es difícil.


    pcSlice->setRefPOCList();//设置一下slice类中的m_aiRefPOCList属性,表示对应参考帧的POC

	//存储NALU一些信息
    NalUnitInfo naluInfo;
    naluInfo.m_nalUnitType = nalu.m_nalUnitType;
    naluInfo.m_nuhLayerId = nalu.m_nuhLayerId;
    naluInfo.m_firstCTUinSlice = pcSlice->getFirstCtuRsAddrInSlice();
    naluInfo.m_POC = pcSlice->getPOC();
    m_nalUnitInfo[naluInfo.m_nuhLayerId].push_back(naluInfo);

  Quant *quant = m_cTrQuant.getQuant();//获得量化变换相关的类
  if (pcSlice->getExplicitScalingListUsed())//如果使用显性缩放列表
  {
    
    
  }
  else
  {
    
    
  }

  if (pcSlice->getSPS()->getUseLmcs())//如果使用Lmcs
  {
    
    
  }
  else
  {
    
    
  }

setRefPOCList (): establece el atributo m_aiRefPOCList en la clase de segmento

A continuación, se almacenará cierta información sobre NALU

quant: esta es una instancia de clase relacionada con la transformación de cuantificación y está relacionada con la lista de escalado explícito a continuación

Los siguientes dos if, uno está relacionado con la lista de zoom explícito y el otro está relacionado con las Lmcs. Todos se obtienen de APS. Mírelos más de cerca cuando vea los relevantes.


  //  Decode a picture
  m_cSliceDecoder.decompressSlice( pcSlice, &( nalu.getBitstream() ), ( m_pcPic->poc == getDebugPOC() ? getDebugCTU() : -1 ) );//调用slice解码器解码

  m_bFirstSliceInPicture = false;//标识不再是一帧中的第一个slice
  m_uiSliceSegmentIdx++;//slice segment的Index加一

  pcSlice->freeScaledRefPicList( scaledRefPic );//清空缩放参考帧列表

m_cSliceDecoder.decompressSlice (): Llame al decodificador de cortes para decodificar, que también es la función más importante de este blog.La explicación detallada estará en el próximo artículo.

m_uiSliceSegmentIdx: el índice del segmento de corte debe recordar naturalmente agregar uno

freeScaledRefPicList: borra la lista de marcos de referencia escalados

Supongo que te gusta

Origin blog.csdn.net/hjhyxq2014/article/details/109157208
Recomendado
Clasificación