HEVC学习:HM-10.1-dev代码分析之TLibVideoIO库

HEVC学习:HM-10.1-dev代码分析之TLibVideoIO库

    视频输入输出库涉及编解码过程的起始和结束操作,即编码开始的时候读取视频文件数据,解码结束的时候写视频文件数据。

    在HEVC中视频文件即为yuv文件,yuv文件中图像格式为YUV420格式。视频文件数据的操作是HEVC编解码中最基本的操作过程之一,也是学习、分析、理解和调试HM代码的重要一环,下面来分析一下VideoIO库的基本操作。

    VideoIO库包含一个头文件TVideoIOYuv.h和一个实现文件TVideoIOYuv.cpp

TVideoIOYuv.h
私有成员变量
fstream   m_cHandle;	//文件流句柄
Int m_fileBitDepthY;	//文件位深Y,即文件中图像亮度Y分量数据的位深度,一般为8或10位
Int m_fileBitDepthC;	//文件位深C,即文件中图像色度C分量数据的位深度,一般为8或10位
Int m_bitDepthShiftY;	//位深移动Y,即图像Y分量要改变的数据位深度,正表示增加,负表示减少
Int m_bitDepthShiftC;	//位深移动Y,即图像C分量要改变的数据位深度,正表示增加,负表示减少

公共成员函数
TVideoIOYuv();                     //构造函数
virtual ~TVideoIOYuv();            //虚构函数
Void open();                       //打开视频文件
Void close();                      //关闭视频文件
void skipFrames();                 //跳过数帧图像
Bool read();                       //读视频文件
Bool write();                      //写视频文件
Bool isEof();                      //判断文件结束
Bool isFail();                     //判断操作失败

TVideoIOYuv.cpp
全局函数
/**
 * Perform division with rounding of all pixels in img by
 * 2shiftbits. All pixels are clipped to [minval, maxval]
 *
 * @param img        pointer to image to be transformed
 * @param stride     distance between vertically adjacent pixels of img.
 * @param width      width of active area in img.
 * @param height     height of active area in img.
 * @param shiftbits  number of rounding bits
 * @param minval     minimum clipping value
 * @param maxval     maximum clipping value
 */
说明:反向缩放图像面函数,即缩小图像的某个分量像素值
参数:
  Pel* img:                 图像数据指针,Pel为16位Short类型
  UInt stride:               图像行偏移量,即从前一行开头与后一行开头之间的间距
  UInt width:                图像宽度
  UInt height:               图像高度
  UInt shiftbits:           移动位数,即在原来图像数据位数上调整的位数 
  Pel minval:                图像像素最小值
  Pel maxval:                图像像素最小值

static void invScalePlane(Pel* img, UInt stride, UInt width, UInt height,
	UInt shiftbits, Pel minval, Pel maxval)
{
    Pel offset = 1 << (shiftbits-1);		//求像素值调整量
    for (UInt y = 0; y < height; y++)		//遍历图像高度
    {
        for (UInt x = 0; x < width; x++)	         //遍历图像宽度
         {
            Pel val = (img[x] + offset) >> shiftbits;	//求缩小后像素值
              img[x] = Clip3(minval, maxval, val);		//限制像素值的范围
         }
        img += stride;					//移动图像数据指针到下一行
    }
}

/**
 * Multiply all pixels in img by 2shiftbits.
 *
 * @param img        pointer to image to be transformed
 * @param stride     distance between vertically adjacent pixels of img.
 * @param width      width of active area in img.
 * @param height     height of active area in img.
 * @param shiftbits  number of bits to shift
 */
说明:正向缩放图像,即放大图像的某个分量像素值
参数:
  Pel* img:                图像数据指针
  UInt stride:             图像行偏移量,即从前一行开头与后一行开头之间的间距
  UInt width:              图像宽度
  UInt height:             图像高度
  UInt shiftbits:          移动位数,即在原来图像数据位数上调整的位数

static void scalePlane(Pel* img, UInt stride, UInt width, UInt height,
                       UInt shiftbits)
{
  for (UInt y = 0; y < height; y++)         //遍历图像高度
  {
    for (UInt x = 0; x < width; x++)        //遍历图像宽度
    {
      img[x] <<= shiftbits;                 //放大像素值
    }
    img += stride;                          //数据指针移动到下一行
  }
}

/**
* Scale all pixels in img depending upon sign of shiftbits by a factor of
* 2shiftbits.
*
* @param img        pointer to image to be transformed
* @param stride  distance between vertically adjacent pixels of img.
* @param width   width of active area in img.
* @param height  height of active area in img.
* @param shiftbits if zero, no operation performed
*                  if > 0, multiply by 2shiftbits, see scalePlane()
*                  if < 0, divide and round by 2shiftbits and clip,
*                          see invScalePlane().
* @param minval  minimum clipping value when dividing.
* @param maxval  maximum clipping value when dividing.
 */
说明:缩放图像,即放大或者缩小图像的某个分量像素值
参数:
  Pel* img:                图像数据指针,Pel为16位Short类型
  UInt stride:              图像数据行间隔,即从前一行开头与后一行开头之间的间距
  UInt width:               图像宽度
  UInt height:              图像高度
  Int shiftbits:            移动位数,即在原来图像数据位数上调整的位数,正表示放大,负表示缩小
  Pel minval:               图像像素最小值
  Pel maxval:               图像像素最小值

static void scalePlane(Pel* img, UInt stride, UInt width, UInt height,
                       Int shiftbits, Pel minval, Pel maxval)
{
  if (shiftbits == 0)              //移动位数为0,即不需要作缩放处理
  {
    return;
  }

  if (shiftbits > 0)               //移动位数大于0,即需要作放大处理
  {
    scalePlane(img, stride, width, height, shiftbits);
  }
  else                             //移动位数小于0,即需要作缩小处理
  {
    invScalePlane(img, stride, width, height, -shiftbits, minval, maxval);
  }
}

类成员函数
// ====================================================================================================================
// Public member functions
// ====================================================================================================================

/**
 * Open file for reading/writing Y'CbCr frames.
 *
 * Frames read/written have bitdepth fileBitDepth, and are automatically
 * formatted as 8 or 16 bit word values (see TVideoIOYuv::write()).
 *
 * Image data read or written is converted to/from internalBitDepth
 * (See scalePlane(), TVideoIOYuv::read() and TVideoIOYuv::write() for
 * further details).
 *
 * \param pchFile          file name string
 * \param bWriteMode       file open mode: true=read, false=write
 * \param fileBitDepthY     bit-depth of input/output file data (luma component).
 * \param fileBitDepthC     bit-depth of input/output file data (chroma components).
 * \param internalBitDepthY bit-depth to scale image data to/from when reading/writing (luma component).
 * \param internalBitDepthC bit-depth to scale image data to/from when reading/writing (chroma components).
 */
说明:为数据帧读写打开yuv文件
参数:
  Char* pchFile:                   文件名字
  Bool bWriteMode:                 读写模式
  Int fileBitDepthY:               文件数据位深度Y
  Int fileBitDepthC:               文件数据位深度C
  Int internalBitDepthY:           内部(程序中)数据位深度Y
  Int internalBitDepthC:           内部(程序中)位数据深度C

Void TVideoIOYuv::open( Char* pchFile, Bool bWriteMode, Int fileBitDepthY, Int fileBitDepthC, Int internalBitDepthY, Int internalBitDepthC)
{
  m_bitDepthShiftY = internalBitDepthY - fileBitDepthY;      //Y分量位移动深度(位数)
  m_bitDepthShiftC = internalBitDepthC - fileBitDepthC;      //C分量位移动深度(位数)
  m_fileBitDepthY = fileBitDepthY;	                          //类成员(文件Y分量位深)赋值
  m_fileBitDepthC = fileBitDepthC;                           //类成员(文件C分量位深)赋值

  if ( bWriteMode )                                          //判断为写模式
  {
    m_cHandle.open( pchFile, ios::binary | ios::out );       //fstream以输出模式打开文件
    
    if( m_cHandle.fail() )	                                   //打开失败
    {
      printf("\nfailed to write reconstructed YUV file\n");
      exit(0);
    }
  }
  else                                                       //判断为读模式
  {
    m_cHandle.open( pchFile, ios::binary | ios::in );        //fstream以输入模式打开文件
    
    if( m_cHandle.fail() )	                                   //打开失败
    {
      printf("\nfailed to open Input YUV file\n");
      exit(0);
    }
  }
  
  return;
}

说明:关闭文件
Void TVideoIOYuv::close()
{
  m_cHandle.close();                //由fstream函数完成文件关闭
}

说明:判断文件结束
Bool TVideoIOYuv::isEof()
{
  return m_cHandle.eof();	         //由fstream函数完成判断文件结束
}

说明:判断文件出错
Bool TVideoIOYuv::isFail()
{
  return m_cHandle.fail();	         //由fstream函数完成判断文件出错
}

/**
 * Skip numFrames in input.
 *
 * This function correctly handles cases where the input file is not
 * seekable, by consuming bytes.
 */
说明:在输入文件中跳过数帧图像
参数:
  UInt numFrames:                  跳过的帧数
  UInt width:                       图像宽度
  UInt height:                      图像高度

void TVideoIOYuv::skipFrames(UInt numFrames, UInt width, UInt height)
{
  if (!numFrames)	                //帧数为0,即不作跳跃操作
    return;

  const UInt wordsize = (m_fileBitDepthY > 8 || m_fileBitDepthC > 8) ? 2 : 1;  //字节长度
  const streamoff framesize = wordsize * width * height * 3 / 2;               //YUV420图像帧大小
  const streamoff offset = framesize * numFrames;                              //跳跃的偏移量

  /* attempt to seek */
  if (!!m_cHandle.seekg(offset, ios::cur))  //直接跳过offset
    return; /* success */
  m_cHandle.clear();                        //直接操作识别,清除stream,然后通过读方式来实现跳跃

  /* fall back to consuming the input */
  Char buf[512];                            //定义读stream缓冲区
  const UInt offset_mod_bufsize = offset % sizeof(buf);                     //偏移量对缓冲区取模
  for (streamoff i = 0; i < offset - offset_mod_bufsize; i += sizeof(buf))  //遍历跳跃帧
  {
    m_cHandle.read(buf, sizeof(buf));       //读到缓冲区
  }
  m_cHandle.read(buf, offset_mod_bufsize);  //读剩余量
}

/**
 * Read width*height pixels from fd into dst, optionally
 * padding the left and right edges by edge-extension.  Input may be
 * either 8bit or 16bit little-endian lsb-aligned words.
 *
 * @param dst     destination image
 * @param fd      input file stream
 * @param is16bit true if input file carries > 8bit data, false otherwise.
 * @param stride  distance between vertically adjacent pixels of dst.
 * @param width   width of active area in dst.
 * @param height  height of active area in dst.
 * @param pad_x   length of horizontal padding.
 * @param pad_y   length of vertical padding.
 * @return true for success, false in case of error
 */
说明:从文件读图像某个分量数据
参数:
  Pel* dst:                目的数据指针
  istream& fd:             输入数据流
  Bool is16bit:            是否16位
  UInt stride:             图像行偏移量
  UInt width:              图像宽度
  UInt height:             图像高度
  UInt pad_x:              x对齐偏移量
  UInt pad_y:              y对齐偏移量

static Bool readPlane(Pel* dst, istream& fd, Bool is16bit,
                      UInt stride,
                      UInt width, UInt height,
                      UInt pad_x, UInt pad_y)
{
  Int read_len = width * (is16bit ? 2 : 1);	                  //单次读取数据量
  UChar *buf = new UChar[read_len];                           //申请一行图像缓冲区
  for (Int y = 0; y < height; y++)                            //图像行遍历
  {
    fd.read(reinterpret_cast(buf), read_len);	         //从文件读一行图像数据
    if (fd.eof() || fd.fail() )                               //判断文件结束和操作失败
    {
      delete[] buf;                                           //释放缓存
      return false;
    }

    if (!is16bit)	                                            //图像数据为非16位即8位单字节
    {
      for (Int x = 0; x < width; x++)                         //遍历宽度
      {
        dst[x] = buf[x];                                      //直接赋值给目的缓冲区
      }
    }
    else	                                                     //图像数据为非16位即双字节
    {
      for (Int x = 0; x < width; x++)			//遍历宽度
      {
        dst[x] = (buf[2*x+1] << 8) | buf[2*x];                //双字节拼接后赋给目的缓冲区
      }
    }

    for (Int x = width; x < width + pad_x; x++)               //对齐位置处理
    {
      dst[x] = dst[width - 1];                                //直接向左边界扩展
    }
    dst += stride;                                            //目的数据指针移动到下一行
  }
  for (Int y = height; y < height + pad_y; y++)               //处理高度方向对齐,即最后几行
  {
    for (Int x = 0; x < width + pad_x; x++)	                  //遍历对齐行的对齐列
    {
      dst[x] = (dst - stride)[x];                             //直接向下边界扩展
    }
    dst += stride;                                            //移动目的数据指针到下一行
  }
  delete[] buf;                                               //释放缓冲区
  return true;
}


/**
 * Write width*height pixels info fd from src.
 *
 * @param fd      output file stream
 * @param src     source image
 * @param is16bit true if input file carries > 8bit data, false otherwise.
 * @param stride  distance between vertically adjacent pixels of src.
 * @param width   width of active area in src.
 * @param height  height of active area in src.
 * @return true for success, false in case of error
 */
说明:写图像某个分量数据到文件
参数:
  ostream& fd:             输出数据流
  Pel* src:               图像源数据指针
  Bool is16bit:            是否16位
  UInt stride:             图像行偏移量
  UInt width:              图像宽度
  UInt height:             图像高度

static Bool writePlane(ostream& fd, Pel* src, Bool is16bit,
                       UInt stride,
                       UInt width, UInt height)
{
  Int write_len = width * (is16bit ? 2 : 1);  //写一行数据量
  UChar *buf = new UChar[write_len];          //申请一行图像缓冲区
  for (Int y = 0; y < height; y++)            //遍历图像行
  {
    if (!is16bit)                            //单字节数据
    {
      for (Int x = 0; x < width; x++)        //遍历图像列
      {
        buf[x] = (UChar) src[x];             //直接取数据低字节
      }
    }
    else                                     //双字节数据
    {
      for (Int x = 0; x < width; x++)        //遍历图像列
      {
        buf[2*x] = src[x] & 0xff;            //取低字节
        buf[2*x+1] = (src[x] >> 8) & 0xff;    //取高字节
      }
    }

    fd.write(reinterpret_cast(buf), write_len);   //写一行图像到文件
    if (fd.eof() || fd.fail() )                           //判断文件结束和操作失败
    {
      delete[] buf;                //释放缓冲区
      return false;
    }
    src += stride;                 //图像数据源指针移动到下一行
  }
  delete[] buf;                    //释放缓冲区
  return true;
}


/**
 * Read one Y'CbCr frame, performing any required input scaling to change
 * from the bitdepth of the input file to the internal bit-depth.
 *
 * If a bit-depth reduction is required, and internalBitdepth >= 8, then
 * the input file is assumed to be ITU-R BT.601/709 compliant, and the
 * resulting data is clipped to the appropriate legal range, as if the
 * file had been provided at the lower-bitdepth compliant to Rec601/709.
 *
 * @param pPicYuv      input picture YUV buffer class pointer
 * @param aiPad        source padding size, aiPad[0] = horizontal, aiPad[1] = vertical
 * @return true for success, false in case of error
 */
说明:读一帧图像数据,由读取一个Y分量和两个C分量实现
参数:
  TComPicYuv*  pPicYuv:             输入图像指针
  Int aiPad[2]:                      图像对齐参数
	
Bool TVideoIOYuv::read ( TComPicYuv*  pPicYuv, Int aiPad[2] )
{
  // check end-of-file
  if ( isEof() ) return false;                       //判断是否文件结束
  
  Int   iStride = pPicYuv->getStride();              //获取图像行偏移量
  
  // compute actual YUV width & height excluding padding size
  UInt pad_h = aiPad[0];                             //获取水平(列)对齐填充量
  UInt pad_v = aiPad[1];                             //获取竖直(行)对齐填充量
  UInt width_full = pPicYuv->getWidth();             //获取图像完全宽度
  UInt height_full = pPicYuv->getHeight();           //获取图像完全高度
  UInt width  = width_full - pad_h;	                 //有效图像宽度
  UInt height = height_full - pad_v;                 //有效图像高度
  Bool is16bit = m_fileBitDepthY > 8 || m_fileBitDepthC > 8;   //是否需要双字节表示

  Int desired_bitdepthY = m_fileBitDepthY + m_bitDepthShiftY;	  //期望位深Y=文件位深+移动
  Int desired_bitdepthC = m_fileBitDepthC + m_bitDepthShiftC;  //期望位深C=文件位深+移动
  Pel minvalY = 0;                                   //Y分量像素最小值
  Pel minvalC = 0;                                   //C分量像素最小值
  Pel maxvalY = (1 << desired_bitdepthY) - 1;        //Y分量像素最大值
  Pel maxvalC = (1 << desired_bitdepthC) - 1;        //C分量像素最大值
#if CLIP_TO_709_RANGE
  if (m_bitdepthShiftY < 0 && desired_bitdepthY >= 8)          //Y分量缩小处理
  {
    /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
    minvalY = 1 << (desired_bitdepthY - 8);	                   //Y最小值
    maxvalY = (0xff << (desired_bitdepthY - 8)) -1;            //Y最大值
  }
  if (m_bitdepthShiftC < 0 && desired_bitdepthC >= 8)          //C分量缩小处理
  {
    /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
	  minvalC = 1 << (desired_bitdepthC - 8);             //Y最小值
	  maxvalC = (0xff << (desired_bitdepthC - 8)) -1;     //Y最大值
  }
#endif
  
  if (! readPlane(pPicYuv->getLumaAddr(), m_cHandle, is16bit, iStride, width, height, pad_h, pad_v))//读取单帧图像中一个Y分量数据
    return false;		//读取失败返回
  scalePlane(pPicYuv->getLumaAddr(), iStride, width_full, height_full, m_bitDepthShiftY, minvalY, maxvalY); //对Y分量进行缩放处理

  iStride >>= 1;		//C分量行偏移(420采样)
  width_full >>= 1;	//C分量图像完全宽度
  height_full >>= 1;	//C分量图像完全高度
  width >>= 1;		//C分量图像有效宽度
  height >>= 1;		//C分量图像有效高度
  pad_h >>= 1;		//C分量水平(列)填充量
  pad_v >>= 1;		//C分量竖直(行)填充量

  if (! readPlane(pPicYuv->getCbAddr(), m_cHandle, is16bit, iStride, width, height, pad_h, pad_v))  //读取一个C分量(Cb)全部数据
    return false;		//失败返回
  scalePlane(pPicYuv->getCbAddr(), iStride, width_full, height_full, m_bitDepthShiftC, minvalC, maxvalC);	 //对C分量进行缩放处理

  if (! readPlane(pPicYuv->getCrAddr(), m_cHandle, is16bit, iStride, width, height, pad_h, pad_v))  //读取下一个C分量(Cr)全部数据
    return false;		//失败返回
  scalePlane(pPicYuv->getCrAddr(), iStride, width_full, height_full, m_bitDepthShiftC, minvalC, maxvalC); //对C分量进行缩放处理

  return true;
}

/**
 * Write one Y'CbCr frame. No bit-depth conversion is performed, pcPicYuv is
 * assumed to be at TVideoIO::m_fileBitdepth depth.
 *
 * @param pPicYuv     input picture YUV buffer class pointer
 * @param aiPad       source padding size, aiPad[0] = horizontal, aiPad[1] = vertical
 * @return true for success, false in case of error
 */
说明:写一帧图像数据,由写一个Y分量和两个C分量实现
参数:
  TComPicYuv* pPicYuv:              输出图像指针
  Int confLeft:                      左边界偏移
  Int confRight:                     右边界偏移
  Int confTop:                       上边界偏移
  Int confBottom:                    下边界偏移

Bool TVideoIOYuv::write( TComPicYuv* pPicYuv, Int confLeft, Int confRight, Int confTop, Int confBottom )
{
  // compute actual YUV frame size excluding padding size
  Int   iStride = pPicYuv->getStride();                        //图像行偏移(Y分量)
  UInt  width  = pPicYuv->getWidth()  - confLeft - confRight;	  //有效图像宽度(去掉边界)
  UInt  height = pPicYuv->getHeight() - confTop  - confBottom; //有效图像高度(去掉边界)
  Bool is16bit = m_fileBitDepthY > 8 || m_fileBitDepthC > 8;   //判断是否需要双字节表示
  TComPicYuv *dstPicYuv = NULL;    //定义目的图像指针
  Bool retval = true;              //返回值

  if (m_bitDepthShiftY != 0 || m_bitDepthShiftC != 0)          //位深移动不为0,需要位深处理
  {
    dstPicYuv = new TComPicYuv;    //申请目的图像
    dstPicYuv->create( pPicYuv->getWidth(), pPicYuv->getHeight(), 1, 1, 0 );//创建缓冲区
    pPicYuv->copyToPic(dstPicYuv);  //图像拷贝到目的图像

    Pel minvalY = 0;                //Y分量像素最小值
    Pel minvalC = 0;                //C分量像素最小值
    Pel maxvalY = (1 << m_fileBitDepthY) - 1;         //Y分量像素最大值
    Pel maxvalC = (1 << m_fileBitDepthC) - 1;         //C分量像素最大值
#if CLIP_TO_709_RANGE
    if (-m_bitDepthShiftY < 0 && m_fileBitDepthY >= 8)   //Y分量需要做位深处理
    {
      /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
      minvalY = 1 << (m_fileBitDepthY - 8);	              //Y分量像素最小值
      maxvalY = (0xff << (m_fileBitDepthY - 8)) -1;        //Y分量像素最大值
    }
    if (-m_bitDepthShiftC < 0 && m_fileBitDepthC >= 8)    //C分量需要做位深处理
    {
      /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
		minvalC = 1 << (m_fileBitDepthC - 8);               //C分量像素最小值
		maxvalC = (0xff << (m_fileBitDepthC - 8)) -1;       //C分量像素最大值
    }
#endif
    scalePlane(dstPicYuv->getLumaAddr(), dstPicYuv->getStride(), dstPicYuv->getWidth(), dstPicYuv->getHeight(), -m_bitDepthShiftY, minvalY, maxvalY);        //Y分量缩放处理
    scalePlane(dstPicYuv->getCbAddr(), dstPicYuv->getCStride(), dstPicYuv->getWidth()>>1, dstPicYuv->getHeight()>>1, -m_bitDepthShiftC, minvalC, maxvalC);        //Cb分量缩放处理
    scalePlane(dstPicYuv->getCrAddr(), dstPicYuv->getCStride(), dstPicYuv->getWidth()>>1, dstPicYuv->getHeight()>>1, -m_bitDepthShiftC, minvalC, maxvalC);        //Cr分量缩放处理
  }
  else                             //不需要做缩放处理
  {
    dstPicYuv = pPicYuv;           //直接获取图像
  }
  // location of upper left pel in a plane
  Int planeOffset = confLeft + confTop * iStride;      //Y分量偏移
  
  if (! writePlane(m_cHandle, dstPicYuv->getLumaAddr() + planeOffset, is16bit, iStride, width, height))  //写图像L分量数据到文件
  {
    retval=false;                 //失败退出
    goto exit;
  }

  width >>= 1;		//图像C分量有效宽度(420)
  height >>= 1;		//图像C分量有效高度
  iStride >>= 1;		//图像C分量行偏移
  confLeft >>= 1;		//图像C分量左边距
  confRight >>= 1;	//图像C分量右边距
  confTop >>= 1;		//图像C分量上边距
  confBottom >>= 1;	//图像C分量下边距

  planeOffset = confLeft + confTop * iStride;        //C分量偏移

  if (! writePlane(m_cHandle, dstPicYuv->getCbAddr() + planeOffset, is16bit, iStride, width, height)) //写图像Cb分量数据到文件
  {
	  retval=false; 	//失败退出
	  goto exit;
  }
  if (! writePlane(m_cHandle, dstPicYuv->getCrAddr() + planeOffset, is16bit, iStride, width, height)) //写图像Cr分量数据到文件
  {
	  retval=false; 	//失败退出
	  goto exit;
  }
  
exit:
  if (m_bitDepthShiftY != 0 || m_bitDepthShiftC != 0)  //做过位深处理
  {
      dstPicYuv->destroy();                       //销毁图像
       delete dstPicYuv;                           //释放缓冲区
  }  
  return retval;
}


 

你可能感兴趣的:(HEVC)