背景
做視訊編解碼相關開發的過程中我們經常會遇到要把視訊原始YUV資料儲存下來檢視的情況。
使用FFMpeg
對視訊解碼之後原始圖片資料都是儲存在AVFrame
這一結構中,很多時候我們都按照影像的長寬來對資料進行儲存,這在絕大部分標準解析度中都是可行的,因為影像的寬度在記憶體中能剛好滿足CPU的位元組對齊。
而對於非標準解析度的視訊來說,應該注意AVFrame
的linesize
可能比實際的影像寬度要大。
如我在解碼一個858x360解析度的視訊成YUV420P的時候,其解碼出來的幀的linesize
分別為{896,448,448}
,可以看到linesize[0]
大於858,相關解釋可以見AVFrame
結構體定義的備註:
/**
* For video, size in bytes of each picture line.
* For audio, size in bytes of each plane.
*
* For audio, only linesize[0] may be set. For planar audio, each channel
* plane must be the same size.
*
* For video the linesizes should be multiples of the CPUs alignment
* preference, this is 16 or 32 for modern desktop CPUs. // <-- Suiyek: Ref Here
* Some code requires such alignment other code can be slower without
* correct alignment, for yet other it makes no difference.
*
* @note The linesize may be larger than the size of usable data -- there
* may be extra padding present for performance reasons.
*/
int linesize[AV_NUM_DATA_POINTERS];
不難看出,AVFrame.data
的資料存放格式是這樣的:
data[i]:
[ (width >> component-shift) data | padding data ]
|<- - linesize[i] - ->|
所以我們在dump YUV的時候不能僅僅使用寬高來計算資料大小,應該去掉linesize
的對齊長度(以YUV420P為例):
void DumpYUV420P(const AVFrame* frame, const char* name) {
if (!frame->width || !frame->height)
return;
int ws, hs;
// 取得當前畫素格式的U/V分量(若有)的右移係數(其實就是除以多少..)
av_pix_fmt_get_chroma_sub_sample(static_cast<AVPixelFormat>(frame->format), &ws, &hs);
int offset = 0;
std::ofstream ofs(name, std::ios::binary | std::ios::app);
for (int i = 0; i < frame->height; i++)
{
ofs.write((char*)(frame->data[0] + offset), frame->width);
offset += frame->linesize[0];
}
offset = 0;
for (int i = 0; i < frame->height >> hs; i++)
{
ofs.write((char*)(frame->data[1] + offset), frame->width >> ws);
offset += frame->linesize[1];
}
offset = 0;
for (int i = 0; i < frame->height >> hs; i++)
{
ofs.write((char*)(frame->data[2] + offset), frame->width >> ws);
offset += frame->linesize[2];
}
ofs.close();
}
參考
FFMpeg在很多地方都有做資料位元組對齊的優化,位元組對齊是很常見的計算機軟體優化手段,可以參考:Data structure alignment