H.266/VVC code learning: reading and writing YUV

1. The IO interface of the YUV file in VTM

In VTM, the reading and writing of YUV files is controlled by the VideoIOYuv class, which is defined in the VideoIOYuv.h file.

The member variables of VideoIOYuv include the following types, among which m_cHandle is a variable of fstream type, mainly used to open/create input/output YUV files, m_fileBitdepth indicates the bit depth of input/output files; m_bitdepthShift before writing/reading/ Afterwards you need to increase or decrease the bit depth.

private:
  fstream   m_cHandle;                                      ///< file handle 处理文件流
  int       m_fileBitdepth[MAX_NUM_CHANNEL_TYPE]; ///< bitdepth of input/output video file 输入/输出比特深度
  // 添加值为 0 的 MSB 后亮度分量的位深度(用于合成高动态范围source material)(默认:InputBitDepth)
  int       m_MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE];  ///< bitdepth after addition of MSBs (with value 0) CFG中配置
  int       m_bitdepthShift[MAX_NUM_CHANNEL_TYPE];  ///< number of bits to increase or decrease image by before/after write/read 在写入/读取之前/之后增加或减少图像的位数

The benefits of this article, free C++ audio and video learning materials package, technical video/code, including (audio and video development, interview questions, FFmpeg, webRTC, rtmp, hls, rtsp, ffplay, codec, push-pull stream, srs)↓↓↓ ↓↓↓See below↓↓Click at the bottom of the article to get it for free↓↓

The definition of VideoIOYuv is as follows,

class VideoIOYuv
{
private:
  fstream   m_cHandle;                                      ///< file handle 处理文件流
  int       m_fileBitdepth[MAX_NUM_CHANNEL_TYPE]; ///< bitdepth of input/output video file 输入/输出比特深度
  // 添加值为 0 的 MSB 后亮度分量的位深度(用于合成高动态范围source material)(默认:InputBitDepth)
  int       m_MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE];  ///< bitdepth after addition of MSBs (with value 0) CFG中配置
  int       m_bitdepthShift[MAX_NUM_CHANNEL_TYPE];  ///< number of bits to increase or decrease image by before/after write/read 在写入/读取之前/之后增加或减少图像的位数
 
public:
  VideoIOYuv()           {}
  virtual ~VideoIOYuv()  {}
 
  // 打开或者创建文件
  void  open  ( const std::string &fileName, bool bWriteMode, const int fileBitDepth[MAX_NUM_CHANNEL_TYPE], const int MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], const int internalBitDepth[MAX_NUM_CHANNEL_TYPE] ); ///< open or create file
  void  close ();                                           ///< close file 关闭文件
#if EXTENSION_360_VIDEO
  void skipFrames(int numFrames, uint32_t width, uint32_t height, ChromaFormat format);
#else
  void skipFrames(uint32_t numFrames, uint32_t width, uint32_t height, ChromaFormat format);
#endif
  // if fileFormat<NUM_CHROMA_FORMAT, the format of the file is that format specified, else it is the format of the PicYuv.
  // If fileFormat=NUM_CHROMA_FORMAT, use the format defined by pPicYuvTrueOrg
  // 使用填充参数读取一帧
  bool  read ( PelUnitBuf& pic, PelUnitBuf& picOrg, const InputColourSpaceConversion ipcsc, int aiPad[2], ChromaFormat fileFormat=NUM_CHROMA_FORMAT, const bool bClipToRec709=false );     ///< read one frame with padding parameter
 
  // If fileFormat=NUM_CHROMA_FORMAT, use the format defined by pPicYuv
  bool  write( uint32_t orgWidth, uint32_t orgHeight, const CPelUnitBuf& pic,
               const InputColourSpaceConversion ipCSC,
               const bool bPackedYUVOutputMode,
               int confLeft = 0, int confRight = 0, int confTop = 0, int confBottom = 0, ChromaFormat format = NUM_CHROMA_FORMAT, const bool bClipToRec709 = false, const bool subtractConfWindowOffsets = true ); ///< write one YUV frame with padding parameter
 
  // If fileFormat=NUM_CHROMA_FORMAT, use the format defined by pPicYuvTop and pPicYuvBottom
  bool  write( const CPelUnitBuf& picTop, const CPelUnitBuf& picBot,
               const InputColourSpaceConversion ipCSC,
               const bool bPackedYUVOutputMode,
               int confLeft = 0, int confRight = 0, int confTop = 0, int confBottom = 0, ChromaFormat format = NUM_CHROMA_FORMAT, const bool isTff = false, const bool bClipToRec709 = false );
  
  // 色彩格式转换
  static void ColourSpaceConvert(const CPelUnitBuf &src, PelUnitBuf &dest, const InputColourSpaceConversion conversion, bool bIsForwards);
 
  bool  isEof ();                                           ///< check for end-of-file
  bool  isFail();                                           ///< check for failure
  bool  isOpen() { return m_cHandle.is_open(); }
 
  bool  writeUpscaledPicture( const SPS& sps, const PPS& pps, const CPelUnitBuf& pic,
    const InputColourSpaceConversion ipCSC, const bool bPackedYUVOutputMode, int outputChoice = 0, ChromaFormat format = NUM_CHROMA_FORMAT, const bool bClipToRec709 = false ); ///< write one upsaled YUV frame
 
};

Core member functions include:

open: used to open/create YUV files

close: close the YUV file

skipFrames: Skip the first numFrames frames

read: read YUV

write: Transcription YUV

Next, these functions are introduced one by one.

1. open function

The open function mainly sets the file open mode (write/read) according to bWriteMode, and sets the bit depth m_fileBitdepth of the file and the offset m_bitdepthShift of the bit depth. Opening a file is very simple, just use the m_cHandle.open() function, and you need to open a binary file when reading and writing a file.

/**
 * Open file for reading/writing Y'CbCr frames. 打开需要读写的YCbCr
 *
 * Frames read/written have bitdepth fileBitDepth, and are automatically
 * formatted as 8 or 16 bit word values (see VideoIOYuv::write()).
 * 读取/写入的帧具有位深度 fileBitDepth,并自动格式化为 8 位或 16 位字值(请参阅 VideoIOYuv::write())。
 * Image data read or written is converted to/from internalBitDepth
 * 读取或写入的图像数据与 internalBitDepth 相互转换
 * (See scalePlane(), VideoIOYuv::read() and VideoIOYuv::write() for
 * further details).
 *
 * \param pchFile          file name string 文件名
 * \param bWriteMode       file open mode: true=write, false=read 文件打开模式 true是写,false是读
 * \param fileBitDepth     bit-depth array of input/output file data. 输入/输出文件的比特深度
 * \param MSBExtendedBitDepth
 * \param internalBitDepth bit-depth array to scale image data to/from when reading/writing. 位深度数组,用于在读取/写入时缩放图像数据。
 */
void VideoIOYuv::open( const std::string &fileName, bool bWriteMode, const int fileBitDepth[MAX_NUM_CHANNEL_TYPE], const int MSBExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], const int internalBitDepth[MAX_NUM_CHANNEL_TYPE] )
{
  //NOTE: files cannot have bit depth greater than 16
  for(uint32_t ch=0; ch<MAX_NUM_CHANNEL_TYPE; ch++)
  {
    m_fileBitdepth       [ch] = std::min<uint32_t>(fileBitDepth[ch], 16);
    m_MSBExtendedBitDepth[ch] = MSBExtendedBitDepth[ch];
    m_bitdepthShift      [ch] = internalBitDepth[ch] - m_MSBExtendedBitDepth[ch];
 
    if (m_fileBitdepth[ch] > 16)
    {
      if (bWriteMode)
      {
        std::cerr << "\nWARNING: Cannot write a yuv file of bit depth greater than 16 - output will be right-shifted down to 16-bit precision\n" << std::endl;
      }
      else
      {
        EXIT( "ERROR: Cannot read a yuv file of bit depth greater than 16" );
      }
    }
  }
 
  if ( bWriteMode ) // 以写入模式打开文件
  {
    m_cHandle.open( fileName.c_str(), ios::binary | ios::out ); // 二进制写入
 
    if( m_cHandle.fail() )
    {
      EXIT( "Failed to write reconstructed YUV file: " << fileName.c_str() );
    }
  }
  else // 以读取模式打开文件
  {
    m_cHandle.open( fileName.c_str(), ios::binary | ios::in ); // 二进制读取
 
    if( m_cHandle.fail() )
    {
      EXIT( "Failed to open input YUV file: " << fileName.c_str() );
    }
  }
 
  return;
}

2. skipFrames function

The skipFrames function mainly skips the first numFrames frames (you can set FrameSkip in cfg), calculates the number of bytes in the first numFrames, and then moves the file stream through seekg.

/**
 * Skip numFrames in input. 跳过输入的num帧
 *
 * This function correctly handles cases where the input file is not
 * seekable, by consuming bytes.
 * 此函数通过消耗字节正确处理输入文件不可查找的情况
 */
#if EXTENSION_360_VIDEO
void VideoIOYuv::skipFrames(int numFrames, uint32_t width, uint32_t height, ChromaFormat format)
#else
void VideoIOYuv::skipFrames(uint32_t numFrames, uint32_t width, uint32_t height, ChromaFormat format)
#endif
{
  if (!numFrames)
  {
    return;
  }
 
  //------------------
  //set the frame size according to the chroma format 根据色度格式设置帧大小
  streamoff frameSize = 0;
  uint32_t wordsize=1; // default to 8-bit, unless a channel with more than 8-bits is detected. 默认为 8 位,除非检测到超过 8 位的通道。
  // 遍历所有颜色分量
  for (uint32_t component = 0; component < getNumberValidComponents(format); component++)
  {
    ComponentID compID=ComponentID(component);
    // 计算一帧的大小 
    // 单个颜色分量的大小为width*height
    frameSize += (width >> getComponentScaleX(compID, format)) * (height >> getComponentScaleY(compID, format));
    if (m_fileBitdepth[toChannelType(compID)] > 8)
    {
      wordsize=2;
    }
  }
  frameSize *= wordsize; // 如果大于8位,需要乘以2,否则乘以1
  //------------------
 
  const streamoff offset = frameSize * numFrames; // 需要偏移的大小
 
  /* attempt to seek */
  if (!!m_cHandle.seekg(offset, ios::cur)) // 从当前位置开始,偏移offset
  {
    return; /* success 成功 */
  }
  m_cHandle.clear();
 
  /* fall back to consuming the input */
  char buf[512];
  const streamoff 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);
}

3. read function

The read function is mainly used to read a Y'CbCr frame and change the bit depth of the input file to the internal bit depth by scaling. This function mainly reads a single component by calling the readPlane function, and scales the input YUV file to the internalBitDepth depth through the scalePlane function.

The input to this function includes m_orgPic and m_trueOrgPic. I have been wondering about the difference between the two before. After reading YUV in the read function, the values ​​of the two are exactly the same. After the read function is completed, there is a filter function filter, this function filters m_orgPic, so I think m_trueOrgPic is the real original pixel (that is, without any preprocessing), and m_orgPic is the original pixel after preprocessing.

/**
 * Read one Y'CbCr frame, performing any required input scaling to change
 * from the bitdepth of the input file to the internal bit-depth.
 * 读取一个 Y'CbCr 帧,执行任何所需的输入缩放以将输入文件的比特深度更改为internal比特深度。
 * 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 pPicYuvUser      input picture YUV buffer class pointer
 * @param pPicYuvTrueOrg
 * @param ipcsc
 * @param aiPad            source padding size, aiPad[0] = horizontal, aiPad[1] = vertical
 * @param format           chroma format
 *  bClipToRec709 如果为 true,则将输入视频clip到 Rec.709
 * @return true for success, false in case of error
 */
bool VideoIOYuv::read ( PelUnitBuf& pic, PelUnitBuf& picOrg, const InputColourSpaceConversion ipcsc, int aiPad[2], ChromaFormat format, const bool bClipToRec709 )
{
  // check end-of-file
  if ( isEof() )
  {
    return false;
  }
 
  if (format >= NUM_CHROMA_FORMAT)
  {
    format = picOrg.chromaFormat;
  }
 
  bool is16bit = false; // 是否是16比特
 
  for(uint32_t ch=0; ch<MAX_NUM_CHANNEL_TYPE; ch++)
  {
    if (m_fileBitdepth[ch] > 8) // 输入/输出文件大于8比特
    {
      is16bit=true; 
    }
  }
 
  const PelBuf areaBufY = picOrg.get(COMPONENT_Y);
#if !EXTENSION_360_VIDEO
  const uint32_t stride444      = areaBufY.stride;
#endif
  // compute actual YUV width & height excluding padding size
  // 计算实际 YUV 宽度和高度,不包括填充大小
  const uint32_t pad_h444       = aiPad[0];
  const uint32_t pad_v444       = aiPad[1];
 
  const uint32_t width_full444  = areaBufY.width; // Y分量的宽度
  const uint32_t height_full444 = areaBufY.height; // Y分量的高度
 
  const uint32_t width444       = width_full444 - pad_h444;
  const uint32_t height444      = height_full444 - pad_v444;
 
  // 遍历全部的颜色分量
  for( uint32_t comp=0; comp < ::getNumberValidComponents(format); comp++)
  {
    const ComponentID compID = ComponentID(comp);
    const ChannelType chType=toChannelType(compID);
 
    const int desired_bitdepth = m_MSBExtendedBitDepth[chType] + m_bitdepthShift[chType]; // 目标比特深度
 
    const bool b709Compliance=(bClipToRec709) && (m_bitdepthShift[chType] < 0 && desired_bitdepth >= 8);     /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
    // 转换比特深度后的最大值和最小值
    const Pel minval = b709Compliance? ((   1 << (desired_bitdepth - 8))   ) : 0;
    const Pel maxval = b709Compliance? ((0xff << (desired_bitdepth - 8)) -1) : (1 << desired_bitdepth) - 1;
    const bool processComponent = (size_t)compID < picOrg.bufs.size(); // 当前待处理的颜色分量
    Pel* const dst = processComponent ? picOrg.get(compID).bufAt(0,0) : nullptr;
#if EXTENSION_360_VIDEO
    const uint32_t stride444 = picOrg.get(compID).stride;
#endif
    // 读取单个颜色分量
    if ( ! readPlane( dst, m_cHandle, is16bit, stride444, width444, height444, pad_h444, pad_v444, compID, picOrg.chromaFormat, format, m_fileBitdepth[chType]))
    {
      return false;
    }
 
    if (processComponent)
    {
      if (! verifyPlane( dst, stride444, width444, height444, pad_h444, pad_v444, compID, format, m_fileBitdepth[chType]) )
      {
         EXIT("Source image contains values outside the specified bit range!");
      }
      // 将输入的YUV转换到internalBitDepth
      scalePlane( picOrg.get(compID), m_bitdepthShift[chType], minval, maxval);
    }
  }
 
#if EXTENSION_360_VIDEO
  if (pic.chromaFormat != NUM_CHROMA_FORMAT)
    ColourSpaceConvert(picOrg, pic, ipcsc, true);
#else
  ColourSpaceConvert( picOrg, pic, ipcsc, true); // 转换颜色空间,总共四种情况,在InputColourSpaceConversion中定义
#endif
 
  picOrg.copyFrom(pic);
 
  return true;
}

3.1 readPlane function

The readPlane function is used to read a single YUV color component

When the input or output file is in YUV400 format, and the current color component is not a brightness component, directly fill in 1<<(fileBitDepth-1);

Otherwise, the file is read line by line, padding to the right or bottom as necessary.

It should be noted here that the std::vector<uint8_t> bufVec variable is uniformly used to store the read bytes when reading the file. If an 8bit video is input, the number of bytes in the corresponding row is the width of the current color component; otherwise, The number of bytes in a line that needs to be read is the width of the current color component * 2;

When reading the high-bit video of the video (greater than 8 bits), since each pixel needs to be stored in two bytes, we need to convert the high 8-bit value when converting it to the corresponding 16-bit value The value is shifted left by 8 bits and then the value of the lower 8 bits is added. As follows

pDstBuf[x] = Pel(buf[(x>>sx)*2+0]) | (Pel(buf[(x>>sx)*2+1])<<8);
// 等价于(buf[(x >> sx) * 2 + 0]) + (Pel(buf[(x >> sx) * 2 + 1]) << 8))
// buf[(x >> sx) * 2 + 1]为高8位 buf[(x>>sx)*2+0]为低8位
/**
 * 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.
 * 将width*height的像素从 fd 读入 dst,可选择通过边缘扩展来填充左右边缘。 
 * 输入可以是 8 位或 16 位lsb-aligned的字。
 * @param dst          destination image plane 目标分量
 * @param fd           input file stream 输入文件流
 * @param is16bit      true if input file carries > 8bit data, false otherwise. 是否大于8bit
 * @param stride444    distance between vertically adjacent pixels of dst.dst 垂直相邻像素之间的距离。
 * @param width444     width of active area in dst. dst 中活动区域的宽度
 * @param height444    height of active area in dst.dst 中活动区域的高度
 * @param pad_x444     length of horizontal padding. 水平pandding的长度
 * @param pad_y444     length of vertical padding. 垂直padding的长度
 * @param compID       chroma component
 * @param destFormat   chroma format of image 图像色度格式
 * @param fileFormat   chroma format of file 文件格式
 * @param fileBitDepth component bit depth in file 读取文件的比特深度
 * @return true for success, false in case of error
 */
static bool readPlane(Pel* dst,
                      istream& fd,
                      bool is16bit,
                      uint32_t stride444,
                      uint32_t width444,
                      uint32_t height444,
                      uint32_t pad_x444,
                      uint32_t pad_y444,
                      const ComponentID compID,
                      const ChromaFormat destFormat,
                      const ChromaFormat fileFormat,
                      const uint32_t fileBitDepth)
{
  const uint32_t csx_file =getComponentScaleX(compID, fileFormat); 
  const uint32_t csy_file =getComponentScaleY(compID, fileFormat);
  const uint32_t csx_dest =getComponentScaleX(compID, destFormat);
  const uint32_t csy_dest =getComponentScaleY(compID, destFormat);
 
  const uint32_t width_dest       = width444 >>csx_dest;
  const uint32_t height_dest      = height444>>csy_dest;
  const uint32_t pad_x_dest       = pad_x444>>csx_dest;
  const uint32_t pad_y_dest       = pad_y444>>csy_dest;
#if EXTENSION_360_VIDEO
  const uint32_t stride_dest = stride444;
#else
  const uint32_t stride_dest      = stride444>>csx_dest;
#endif
  const uint32_t full_width_dest  = width_dest+pad_x_dest; // 目标文件的宽度
  const uint32_t full_height_dest = height_dest+pad_y_dest; // 目标文件的高度
 
  const uint32_t stride_file      = (width444 * (is16bit ? 2 : 1)) >> csx_file; // 读取一行的大小
  std::vector<uint8_t> bufVec(stride_file);
  uint8_t *buf=&(bufVec[0]);
 
  Pel  *pDstPad              = dst + stride_dest * height_dest;
  Pel  *pDstBuf              = dst;
  const int dstbuf_stride    = stride_dest;
 
  if (compID!=COMPONENT_Y && (fileFormat==CHROMA_400 || destFormat==CHROMA_400))
  {
    if (destFormat!=CHROMA_400)
    {
      // set chrominance data to mid-range: (1<<(fileBitDepth-1))
      // 将色度数据设置为中间范围:(1<<(fileBitDepth-1))
      const Pel value=Pel(1<<(fileBitDepth-1));
      for (uint32_t y = 0; y < full_height_dest; y++, pDstBuf+= dstbuf_stride)
      {
        for (uint32_t x = 0; x < full_width_dest; x++)
        {
          pDstBuf[x] = value;
        }
      }
    }
 
    if (fileFormat!=CHROMA_400)
    {
      const uint32_t height_file      = height444>>csy_file;
      fd.seekg(height_file*stride_file, ios::cur);
      if (fd.eof() || fd.fail() )
      {
        return false;
      }
    }
  }
  else
  {
    const uint32_t mask_y_file=(1<<csy_file)-1;
    const uint32_t mask_y_dest=(1<<csy_dest)-1;
    for(uint32_t y444=0; y444<height444; y444++) // 遍历高度
    {
      if ((y444&mask_y_file)==0)
      {
        // read a new line
        fd.read(reinterpret_cast<char*>(buf), stride_file); // 读取新的一行
        if (fd.eof() || fd.fail() )
        {
          return false;
        }
      }
 
      if ((y444&mask_y_dest)==0)
      {
        // process current destination line 处理当前目标行
        if (csx_file < csx_dest)
        {
          // eg file is 444, dest is 422. 比如输入文件格式是444,但是目标文件格式是422
          const uint32_t sx=csx_dest-csx_file;
          if (!is16bit) // 8比特情况下
          {
            for (uint32_t x = 0; x < width_dest; x++)
            {
              pDstBuf[x] = buf[x<<sx]; //此时每行读取时,应该间隔像素着读取
            }
          }
          else
          {
            for (uint32_t x = 0; x < width_dest; x++)
            {
              pDstBuf[x] = Pel(buf[(x<<sx)*2+0]) | (Pel(buf[(x<<sx)*2+1])<<8);
            }
          }
        }
        else
        {
          // eg file is 422, dest is 444.
          const uint32_t sx=csx_file-csx_dest;
          if (!is16bit) // 对于8bit的YUV,直接赋给pDstBuf就好
          {
            for (uint32_t x = 0; x < width_dest; x++)
            {
              pDstBuf[x] = buf[x>>sx];
            }
          }
          else // 对于10bit的YUV,由于每个像素的使用两个字节存储,因此将后8个字节需要左移8位再与前8个字节按位或
          {
            for (uint32_t x = 0; x < width_dest; x++)
            {
              // 等价于(buf[(x >> sx) * 2 + 0]) + (Pel(buf[(x >> sx) * 2 + 1]) << 8))
              // buf[(x >> sx) * 2 + 1]为高8位 buf[(x>>sx)*2+0]为低8位
              pDstBuf[x] = Pel(buf[(x>>sx)*2+0]) | (Pel(buf[(x>>sx)*2+1])<<8);
            }
          }
        }
 
        // process right hand side padding 处理右侧填充,填充的时候最右侧的像素
        const Pel val=dst[width_dest-1];
        for (uint32_t x = width_dest; x < full_width_dest; x++)
        {
          pDstBuf[x] = val;
        }
 
        pDstBuf+= dstbuf_stride;
      }
    }
 
    // process lower padding 处理下填充
    for (uint32_t y = height_dest; y < full_height_dest; y++, pDstPad+=stride_dest)
    {
      for (uint32_t x = 0; x < full_width_dest; x++)
      {
        pDstPad[x] = (pDstPad - stride_dest)[x]; // 复制相邻上一行
      }
    }
  }
  return true;
}

3.2 scalePlane function

The scalePlane function is shifted and scaled according to m_bitdepthShift. Take the conversion of 8bit video to 10bit as an example, just shift the original pixel to the left by 2 bits.

/**
 * Scale all pixels in img depending upon sign of shiftbits by a factor of
 * 2<sup>shiftbits</sup>.
 * 根据移位的符号缩放 img 中的所有像素
 * @param areabuf buffer to be scaled 要缩放的 areabuf 缓冲区
 * @param shiftbits if zero, no operation performed
 *                  if > 0, multiply by 2<sup>shiftbits</sup>, see scalePlane()
 *                  if < 0, divide and round by 2<sup>shiftbits</sup> and clip,
 *                          see invScalePlane().
 * @param minval  minimum clipping value when dividing.
 * @param maxval  maximum clipping value when dividing.
 */
static void scalePlane( PelBuf& areaBuf, const int shiftbits, const Pel minval, const Pel maxval)
{
  const unsigned width  = areaBuf.width;
  const unsigned height = areaBuf.height;
  const unsigned stride = areaBuf.stride;
        Pel*        img = areaBuf.bufAt(0,0);
 
  if( 0 == shiftbits )
  {
    return;
  }
 
  if( shiftbits > 0)
  {
    for( unsigned y = 0; y < height; y++, img+=stride)
    {
      for( unsigned x = 0; x < width; x++)
      {
        img[x] <<= shiftbits;
      }
    }
  }
  else if (shiftbits < 0)
  {
    const int shiftbitsr =- shiftbits;
    const Pel rounding = 1 << (shiftbitsr-1);
 
    for( unsigned y = 0; y < height; y++, img+=stride)
    {
      for( unsigned x = 0; x < width; x++)
      {
        img[x] = Clip3(minval, maxval, Pel((img[x] + rounding) >> shiftbitsr));
      }
    }
  }
}

The benefits of this article, free C++ audio and video learning materials package, technical video/code, including (audio and video development, interview questions, FFmpeg, webRTC, rtmp, hls, rtsp, ffplay, codec, push-pull stream, srs)↓↓↓ ↓↓↓See below↓↓Click at the bottom of the article to get it for free↓↓

4. write function

The write function is used to write a Y'CbCr frame. The function first scales the pixels through the scalePlane function, and then writes a single component through the writePlane function.

/**
 * Write one Y'CbCr frame. No bit-depth conversion is performed, pcPicYuv is
 * assumed to be at TVideoIO::m_fileBitdepth depth.
 * 写一个 Y'CbCr 帧。 不执行位深转换,假定 pcPicYuv 处于 TVideoIO::m_fileBitdepth 深度。
 * @param pPicYuvUser      input picture YUV buffer class pointer
 * @param ipCSC
 * @param confLeft         conformance window left border
 * @param confRight        conformance window right border
 * @param confTop          conformance window top border
 * @param confBottom       conformance window bottom border
 * @param format           chroma format
 * @return true for success, false in case of error
 */
 // here orgWidth and orgHeight are for luma
bool VideoIOYuv::write( uint32_t orgWidth, uint32_t orgHeight, const CPelUnitBuf& pic,
                        const InputColourSpaceConversion ipCSC,
                        const bool bPackedYUVOutputMode,
                        int confLeft, int confRight, int confTop, int confBottom, ChromaFormat format, const bool bClipToRec709, const bool subtractConfWindowOffsets )
{
  PelStorage interm;
 
  if (ipCSC!=IPCOLOURSPACE_UNCHANGED)
  {
    interm.create( pic.chromaFormat, Area( Position(), pic.Y()) );
    ColourSpaceConvert(pic, interm, ipCSC, false);
  }
 
  const CPelUnitBuf& picC = (ipCSC==IPCOLOURSPACE_UNCHANGED) ? pic : interm;
 
  // compute actual YUV frame size excluding padding size
  bool is16bit = false;
  bool nonZeroBitDepthShift=false;
 
  for(uint32_t ch=0; ch<MAX_NUM_CHANNEL_TYPE; ch++)
  {
    if (m_fileBitdepth[ch] > 8)
    {
      is16bit=true;
    }
    if (m_bitdepthShift[ch] != 0)
    {
      nonZeroBitDepthShift=true;
    }
  }
 
  bool retval = true;
  if (format>=NUM_CHROMA_FORMAT)
  {
    format= picC.chromaFormat;
  }
 
  PelStorage picZ;
  if (nonZeroBitDepthShift)
  {
    picZ.create( picC.chromaFormat, Area( Position(), picC.Y() ) );
    picZ.copyFrom( picC );
 
    for(uint32_t comp=0; comp < ::getNumberValidComponents( picZ.chromaFormat ); comp++)
    {
      const ComponentID compID=ComponentID(comp);
      const ChannelType ch=toChannelType(compID);
      const bool b709Compliance = bClipToRec709 && (-m_bitdepthShift[ch] < 0 && m_MSBExtendedBitDepth[ch] >= 8);     /* ITU-R BT.709 compliant clipping for converting say 10b to 8b */
      const Pel minval = b709Compliance? ((   1 << (m_MSBExtendedBitDepth[ch] - 8))   ) : 0;
      const Pel maxval = b709Compliance? ((0xff << (m_MSBExtendedBitDepth[ch] - 8)) -1) : (1 << m_MSBExtendedBitDepth[ch]) - 1;
 
      scalePlane( picZ.get(compID), -m_bitdepthShift[ch], minval, maxval);
    }
  }
 
  const CPelUnitBuf& picO = nonZeroBitDepthShift ? picZ : picC;
 
  const CPelBuf areaY     = picO.get(COMPONENT_Y);
  const uint32_t    width444  = areaY.width - confLeft - confRight;
  const uint32_t    height444 = areaY.height -  confTop  - confBottom;
 
  if( subtractConfWindowOffsets )
  {
    orgWidth -= confLeft + confRight;
    orgHeight -= confTop + confBottom;
  }
 
  if ((width444 == 0) || (height444 == 0))
  {
    msg( WARNING, "\nWarning: writing %d x %d luma sample output picture!", width444, height444);
  }
 
  for(uint32_t comp=0; retval && comp < ::getNumberValidComponents(format); comp++)
  {
    const ComponentID compID      = ComponentID(comp);
    const ChannelType ch          = toChannelType(compID);
    const uint32_t    csx         = ::getComponentScaleX(compID, format);
    const uint32_t    csy         = ::getComponentScaleY(compID, format);
    const CPelBuf     area        = picO.get(compID);
    const int         planeOffset = (confLeft >> csx) + (confTop >> csy) * area.stride;
    if( !writePlane( orgWidth, orgHeight, m_cHandle, area.bufAt( 0, 0 ) + planeOffset, is16bit, area.stride,
                     width444, height444, compID, picO.chromaFormat, format, m_fileBitdepth[ch],
                     bPackedYUVOutputMode ? 1 : 0))
    {
      retval = false;
    }
  }
 
  return retval;
}

4.1 writePlane function

The writePlane function is used to write a single color component to the output file stream, pay attention to the data processing when writing:

          if (!is16bit) // 不是16bit时
          {
            for (uint32_t x = 0; x < width_file; x++)
            {
              buf[x] = (uint8_t)(pSrcBuf[x<<sx]);
            }
          }
          else // 16bit的数据时
          {
            for (uint32_t x = 0; x < width_file; x++)
            {
              buf[2*x  ] = (pSrcBuf[x<<sx]>>0) & 0xff; // 低8位的数据
              buf[2*x+1] = (pSrcBuf[x<<sx]>>8) & 0xff; // 高8位的数据
            }
          }

Write to the stream through wirte, and buf points to the std::vector<uint8_t> bufVec variable.

        fd.write (reinterpret_cast<const char*>(buf), stride_file); // 写入文件
/**
 * Write an image plane (width444*height444 pixels) from src into output stream fd.
 *
 * @param fd         output file stream 输出文件流
 * @param src        source image 源图像
 * @param is16bit    true if input file carries > 8bit data, false otherwise. 是否大于8比特
 * @param stride444  distance between vertically adjacent pixels of src.
 * @param width444   width of active area in src.
 * @param height444  height of active area in src.
 * @param compID       chroma component 色度分量
 * @param srcFormat    chroma format of image 源文件的色度格式
 * @param fileFormat   chroma format of file 输出文件的色度格式
 * @param fileBitDepth component bit depth in file
 * @return true for success, false in case of error
 * packedYUVOutputMode 如果为 true,则将 10 位和 12 位 YUV 数据输出为 5 字节和 3 字节(分别)打包的 YUV 数据
 */
static bool writePlane( uint32_t orgWidth, uint32_t orgHeight, ostream& fd, const Pel* src,
                       const bool is16bit,
                       const uint32_t stride_src,
                       uint32_t width444, uint32_t height444,
                       const ComponentID compID,
                       const ChromaFormat srcFormat,
                       const ChromaFormat fileFormat,
                       const uint32_t fileBitDepth,
                       const uint32_t packedYUVOutputMode = 0)
{
  const uint32_t csx_file =getComponentScaleX(compID, fileFormat);
  const uint32_t csy_file =getComponentScaleY(compID, fileFormat);
  const uint32_t csx_src  =getComponentScaleX(compID, srcFormat);
  const uint32_t csy_src  =getComponentScaleY(compID, srcFormat);
 
  const uint32_t width_file  = width444  >> csx_file;
  const uint32_t height_file = height444 >> csy_file;
  const bool     writePYUV   = (packedYUVOutputMode > 0) && (fileBitDepth == 10 || fileBitDepth == 12) && ((width_file & (1 + (fileBitDepth & 3))) == 0);
 
  CHECK( csx_file != csx_src, "Not supported" );
  const uint32_t stride_file = writePYUV ? ( orgWidth * fileBitDepth ) >> ( csx_file + 3 ) : ( orgWidth * ( is16bit ? 2 : 1 ) ) >> csx_file;
 
  std::vector<uint8_t> bufVec(stride_file);
  uint8_t *buf=&(bufVec[0]);
 
  const Pel *pSrcBuf         = src;
  const int srcbuf_stride    = stride_src;
 
  if (writePYUV)
  {
    const uint32_t mask_y_file = (1 << csy_file) - 1;
    const uint32_t mask_y_src  = (1 << csy_src ) - 1;
    const uint32_t widthS_file = width_file >> (fileBitDepth == 12 ? 1 : 2);
 
    for (uint32_t y444 = 0; y444 < height444; y444++)
    {
      if ((y444 & mask_y_file) == 0)  // write a new line to file
      {
        if (csx_file < csx_src)
        {
          // eg file is 444, source is 422.
          const uint32_t sx = csx_src - csx_file;
 
          if (fileBitDepth == 10)  // write 4 values into 5 bytes
          {
            for (uint32_t x = 0; x < widthS_file; x++)
            {
              const uint32_t src0 = pSrcBuf[(4*x  ) >> sx];
              const uint32_t src1 = pSrcBuf[(4*x+1) >> sx];
              const uint32_t src2 = pSrcBuf[(4*x+2) >> sx];
              const uint32_t src3 = pSrcBuf[(4*x+3) >> sx];
 
              buf[5*x  ] = ((src0     ) & 0xff); // src0:76543210
              buf[5*x+1] = ((src1 << 2) & 0xfc) + ((src0 >> 8) & 0x03);
              buf[5*x+2] = ((src2 << 4) & 0xf0) + ((src1 >> 6) & 0x0f);
              buf[5*x+3] = ((src3 << 6) & 0xc0) + ((src2 >> 4) & 0x3f);
              buf[5*x+4] = ((src3 >> 2) & 0xff); // src3:98765432
            }
          }
          else if (fileBitDepth == 12) //...2 values into 3 bytes
          {
            for (uint32_t x = 0; x < widthS_file; x++)
            {
              const uint32_t src0 = pSrcBuf[(2*x  ) >> sx];
              const uint32_t src1 = pSrcBuf[(2*x+1) >> sx];
 
              buf[3*x  ] = ((src0     ) & 0xff); // src0:76543210
              buf[3*x+1] = ((src1 << 4) & 0xf0) + ((src0 >> 8) & 0x0f);
              buf[3*x+2] = ((src1 >> 4) & 0xff); // src1:BA987654
            }
          }
        }
        else
        {
          // eg file is 422, source is 444.
          const uint32_t sx = csx_file - csx_src;
 
          if (fileBitDepth == 10)  // write 4 values into 5 bytes
          {
            for (uint32_t x = 0; x < widthS_file; x++)
            {
              const uint32_t src0 = pSrcBuf[(4*x  ) << sx];
              const uint32_t src1 = pSrcBuf[(4*x+1) << sx];
              const uint32_t src2 = pSrcBuf[(4*x+2) << sx];
              const uint32_t src3 = pSrcBuf[(4*x+3) << sx];
 
              buf[5*x  ] = ((src0     ) & 0xff); // src0:76543210
              buf[5*x+1] = ((src1 << 2) & 0xfc) + ((src0 >> 8) & 0x03);
              buf[5*x+2] = ((src2 << 4) & 0xf0) + ((src1 >> 6) & 0x0f);
              buf[5*x+3] = ((src3 << 6) & 0xc0) + ((src2 >> 4) & 0x3f);
              buf[5*x+4] = ((src3 >> 2) & 0xff); // src3:98765432
            }
          }
          else if (fileBitDepth == 12) //...2 values into 3 bytes
          {
            for (uint32_t x = 0; x < widthS_file; x++)
            {
              const uint32_t src0 = pSrcBuf[(2*x  ) << sx];
              const uint32_t src1 = pSrcBuf[(2*x+1) << sx];
 
              buf[3*x  ] = ((src0     ) & 0xff); // src0:76543210
              buf[3*x+1] = ((src1 << 4) & 0xf0) + ((src0 >> 8) & 0x0f);
              buf[3*x+2] = ((src1 >> 4) & 0xff); // src1:BA987654
            }
          }
        }
 
        fd.write (reinterpret_cast<const char*>(buf), stride_file);
        if (fd.eof() || fd.fail())
        {
          return false;
        }
      }
 
      if ((y444 & mask_y_src) == 0)
      {
        pSrcBuf += srcbuf_stride;
      }
    }
 
    // here height444 and orgHeight are luma heights
    if ((compID == COMPONENT_Y) || (fileFormat != CHROMA_400 && srcFormat != CHROMA_400))
    {
      for (uint32_t y444 = height444; y444 < orgHeight; y444++)
      {
        if ((y444 & mask_y_file) == 0) // if this is chroma, determine whether to skip every other row
        {
          memset (reinterpret_cast<char*>(buf), 0, stride_file);
 
          fd.write (reinterpret_cast<const char*>(buf), stride_file);
          if (fd.eof() || fd.fail())
          {
            return false;
          }
        }
 
        if ((y444 & mask_y_src) == 0)
        {
          pSrcBuf += srcbuf_stride;
        }
      }
    }
  }
  else // !writePYUV
  if (compID!=COMPONENT_Y && (fileFormat==CHROMA_400 || srcFormat==CHROMA_400))
  {
    // 色度分量填充固定值
    if (fileFormat!=CHROMA_400)
    {
      const uint32_t value = 1 << (fileBitDepth - 1);
 
      for (uint32_t y = 0; y < height_file; y++)
      {
        if (!is16bit)
        {
          uint8_t val(value);
          for (uint32_t x = 0; x < width_file; x++)
          {
            buf[x]=val;
          }
        }
        else
        {
          uint16_t val(value);
          for (uint32_t x = 0; x < width_file; x++)
          {
            buf[2*x  ]= (val>>0) & 0xff;
            buf[2*x+1]= (val>>8) & 0xff;
          }
        }
 
        fd.write(reinterpret_cast<const char*>(buf), stride_file);
        if (fd.eof() || fd.fail() )
        {
          return false;
        }
      }
    }
  }
  else
  {
    const uint32_t mask_y_file = (1 << csy_file) - 1;
    const uint32_t mask_y_src  = (1 << csy_src ) - 1;
 
    for (uint32_t y444 = 0; y444 < height444; y444++)
    {
      if ((y444 & mask_y_file) == 0)
      {
        // write a new line 写入新的一行
        if (csx_file < csx_src)
        {
          // eg file is 444, source is 422. 例如输出文件是444,源文件是422
          const uint32_t sx = csx_src - csx_file;
          if (!is16bit)
          {
            for (uint32_t x = 0; x < width_file; x++)
            {
              buf[x] = (uint8_t)(pSrcBuf[x>>sx]); //重复输出
            }
          }
          else
          {
            for (uint32_t x = 0; x < width_file; x++)
            {
              buf[2*x  ] = (pSrcBuf[x>>sx]>>0) & 0xff;
              buf[2*x+1] = (pSrcBuf[x>>sx]>>8) & 0xff;
            }
          }
        }
        else
        {
          // eg file is 422, source is 444.
          const uint32_t sx = csx_file - csx_src;
          if (!is16bit) // 不是16bit时
          {
            for (uint32_t x = 0; x < width_file; x++)
            {
              buf[x] = (uint8_t)(pSrcBuf[x<<sx]);
            }
          }
          else // 16bit的数据时
          {
            for (uint32_t x = 0; x < width_file; x++)
            {
              buf[2*x  ] = (pSrcBuf[x<<sx]>>0) & 0xff; // 低8位的数据
              buf[2*x+1] = (pSrcBuf[x<<sx]>>8) & 0xff; // 高8位的数据
            }
          }
        }
 
        fd.write (reinterpret_cast<const char*>(buf), stride_file); // 写入文件
        if (fd.eof() || fd.fail())
        {
          return false;
        }
      }
 
      if ((y444 & mask_y_src) == 0)
      {
        pSrcBuf += srcbuf_stride;
      }
    }
 
    // here height444 and orgHeight are luma heights
    for( uint32_t y444 = height444; y444 < orgHeight; y444++ )
    {
      if( ( y444 & mask_y_file ) == 0 ) // if this is chroma, determine whether to skip every other row
      {
        if( !is16bit )
        {
          for( uint32_t x = 0; x < ( orgWidth >> csx_file ); x++ )
          {
            buf[x] = 0;
          }
        }
        else
        {
          for( uint32_t x = 0; x < ( orgWidth >> csx_file ); x++ )
          {
            buf[2 * x] = 0;
            buf[2 * x + 1] = 0;
          }
        }
        fd.write( reinterpret_cast<const char*>( buf ), stride_file );
        if( fd.eof() || fd.fail() )
        {
          return false;
        }
      }
 
      if( ( y444 & mask_y_src ) == 0 )
      {
        pSrcBuf += srcbuf_stride;
      }
    }
 
  }
  return true;
}

The benefits of this article, free C++ audio and video learning materials package, technical video/code, including (audio and video development, interview questions, FFmpeg, webRTC, rtmp, hls, rtsp, ffplay, codec, push-pull stream, srs)↓↓↓ ↓↓↓See below↓↓Click at the bottom of the article to get it for free↓↓

Guess you like

Origin blog.csdn.net/m0_60259116/article/details/131773938