[FFMpeg] 非標準解析度視訊Dump YUV注意事項

Suiyek發表於2021-10-10

背景

做視訊編解碼相關開發的過程中我們經常會遇到要把視訊原始YUV資料儲存下來檢視的情況。
使用FFMpeg對視訊解碼之後原始圖片資料都是儲存在AVFrame這一結構中,很多時候我們都按照影像的長寬來對資料進行儲存,這在絕大部分標準解析度中都是可行的,因為影像的寬度在記憶體中能剛好滿足CPU的位元組對齊。
而對於非標準解析度的視訊來說,應該注意AVFramelinesize可能比實際的影像寬度要大。
如我在解碼一個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

相關文章