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