一、VTM中的YUV文件的IO接口
VTM中是通过VideoIOYuv类控制YUV文件的读取和写入的,VideoIOYuv类是在VideoIOYuv.h文件中定义的。
VideoIOYuv的成员变量包括以下几种,其中m_cHandle是fstream类型的变量,主要是用来打开/创建输入/输出的YUV文件,m_fileBitdepth表示输入/输出文件的比特深度; m_bitdepthShift在写入/读取之前/之后需要增加或减少的比特深度。
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 在写入/读取之前/之后增加或减少图像的位数
VideoIOYuv的定义如下所示,
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
};
核心成员函数包括:
- open:用于打开/创建YUV文件
- close:关闭YUV文件
- skipFrames:跳过前numFrames帧
- read:读取YUV
- write:写入YUV
接下来挨个介绍这些函数。
1. open函数
open函数主要是根据bWriteMode设置文件打开模式(写/读),并设置文件的比特深度m_fileBitdepth和比特深度的偏移m_bitdepthShift。打开文件很简单,通过m_cHandle.open()函数即可,其中读取文件和写入文件的时候都需要通过二进制文件打开。
/**
* 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函数
skipFrames函数主要是跳过前numFrames帧(可以在cfg中设置FrameSkip),计算前numFrames的字节数再通过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函数
read函数主要是用来读取一个 Y'CbCr 帧,并通过缩放将输入文件的比特深度更改为internal比特深度。该函数主要是通过调用readPlane函数读取单个分量,并通过scalePlane函数将输入的YUV文件缩放到internalBitDepth深度。
传入该函数的包括m_orgPic和m_trueOrgPic,之前一直疑惑这两者之间的差别,在read函数中读取YUV后,二者的值是完全一样的,在做完read函数之后,有一个滤波函数filter,该函数针对m_orgPic进行了滤波,因此我觉得m_trueOrgPic是真正的原始像素(即未通过任何预处理),而m_orgPic是通过预处理之后的原始像素。
/**
* 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函数
readPlane函数用于读取YUV单个颜色分量
- 当输入或者输出文件是YUV400格式时,且当前颜色分量不是亮度分量时,直接填充1<<(fileBitDepth-1);
- 否则,按行读取文件,并按照需要对右侧或者下侧进行填充。
这里需要注意的是,读取文件时统一使用std::vector<uint8_t> bufVec变量存储读取的字节,如果输入8bit视频时,对应的一行的字节数是当前颜色分量的宽度;否则,需要读取的一行的字节数是当前颜色分量的宽度 * 2;
当读取的视频的高比特视频时(大于8比特),由于每个像素都是需要使用两个字节存储的,我们在将其转换为相应的16bit的值时,需要将高8位的值左移8位后再加上低8位的值。如下所示
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函数
scalePlane函数是根据m_bitdepthShift进行移位缩放。以8bit视频转换为10bit为例,将原始像素左移2位即可。
/**
* 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));
}
}
}
}
4. write函数
write函数是用于写入一个Y'CbCr 帧,该函数先通过scalePlane函数将像素进行缩放,然后再通过writePlane函数写入单个分量。
/**
* 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函数
writePlane函数用于将的单个颜色分量写入输出文件流,注意写入时对于数据的处理:
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位的数据
}
}
通过wirte写入流,buf指向的是 std::vector<uint8_t> bufVec变量。
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;
}