影像步幅

當視訊影像儲存在記憶體中時,記憶體緩衝區可能包含每個像素數據列之後的額外填補位元組。 填補位元節會影響影像儲存在記憶體中的方式,但不會影響影像的顯示方式。

stride 是記憶體中一列圖元到記憶體中下一列圖元的位元組數目。 大步也稱為 投球。 如果填補位元組存在,則步幅會比影像的寬度寬,如下圖所示。

diagram showing an image plus padding.

包含相同維度之視訊畫面的兩個緩衝區可以有兩個不同的步幅。 如果您處理視訊影像,則必須將步幅納入考慮。

此外,還有兩種方式可將影像排列在記憶體中。 在由上而下影像中,影像中的像素頂端數據列會先出現在記憶體中。 在從下到下影像中,最後一列圖元會先出現在記憶體中。 下圖顯示由上而下影像與由下而下影像之間的差異。

diagram showing top-down and bottom-up images.

下拉影像有負幅,因為步幅定義為相對於所顯示影像,需要向下移動一列圖元的位元組數目。 YUV 影像應該一律由上而下,且 Direct3D 介面中包含的任何影像都必須由上而下。 系統記憶體中的 RGB 影像通常是由下而下。

視訊轉換特別需要處理具有不相符步幅的緩衝區,因為輸入緩衝區可能不符合輸出緩衝區。 例如,假設您想要轉換來源映像,並將結果寫入目的地映像。 假設這兩個影像的寬度和高度相同,但可能沒有相同的圖元格式或相同的影像步幅。

下列範例程式代碼示範撰寫這類函式的一般化方法。 這不是完整的工作範例,因為它會抽象化許多特定詳細數據。

void ProcessVideoImage(
    BYTE*       pDestScanLine0,     
    LONG        lDestStride,        
    const BYTE* pSrcScanLine0,      
    LONG        lSrcStride,         
    DWORD       dwWidthInPixels,     
    DWORD       dwHeightInPixels
    )
{
    for (DWORD y = 0; y < dwHeightInPixels; y++)
    {
        SOURCE_PIXEL_TYPE *pSrcPixel = (SOURCE_PIXEL_TYPE*)pSrcScanLine0;
        DEST_PIXEL_TYPE *pDestPixel = (DEST_PIXEL_TYPE*)pDestScanLine0;

        for (DWORD x = 0; x < dwWidthInPixels; x +=2)
        {
            pDestPixel[x] = TransformPixelValue(pSrcPixel[x]);
        }
        pDestScanLine0 += lDestStride;
        pSrcScanLine0 += lSrcStride;
    }
}

此函式接受六個參數:

  • 目的地影像中掃描第 0 行開頭的指標。
  • 目的地影像的步幅。
  • 來源影像中掃描第 0 行開頭的指標。
  • 來源影像的步幅。
  • 影像的寬度,以像素為單位。
  • 影像的高度,以像素為單位。

一般概念是一次處理一個數據列,逐一查看數據列中的每個圖元。 假設SOURCE_PIXEL_TYPE和DEST_PIXEL_TYPE是分別代表來源和目的地影像圖元配置的結構。 (例如,32 位 RGB 使用 RGBQUAD 結構。並非每個圖元格式都有預先定義的結構。將數位指標轉換成結構類型,可讓您存取每個圖元的 RGB 或 YUV 元件。 在每個數據列的開頭,函式會儲存數據列的指標。 在數據列結尾,它會以影像步幅的寬度遞增指標,將指標往前移至下一個數據列。

此範例會針對每個像素呼叫名為 TransformPixelValue 的假設函式。 這可能是從來源圖元計算目標圖元的任何函式。 當然,確切的詳細數據將取決於特定工作。 例如,如果您有平面 YUV 格式,則必須從 luma 平面獨立存取色度平面;使用交錯式視訊,您可能需要個別處理欄位;等等。

為了提供更具體的範例,下列程式代碼會將 32 位 RGB 影像轉換成 AYUV 影像。 RGB 像素是使用 RGBQUAD 結構來存取,而 AYUV 像素則會使用 DXVA2_AYUVSample8 結構來存取。

//-------------------------------------------------------------------
// Name: RGB32_To_AYUV
// Description: Converts an image from RGB32 to AYUV
//-------------------------------------------------------------------
void RGB32_To_AYUV(
    BYTE*       pDest,
    LONG        lDestStride,
    const BYTE* pSrc,
    LONG        lSrcStride,
    DWORD       dwWidthInPixels,
    DWORD       dwHeightInPixels
    )
{
    for (DWORD y = 0; y < dwHeightInPixels; y++)
    {
        RGBQUAD             *pSrcPixel = (RGBQUAD*)pSrc;
        DXVA2_AYUVSample8   *pDestPixel = (DXVA2_AYUVSample8*)pDest;
        
        for (DWORD x = 0; x < dwWidthInPixels; x++)
        {
            pDestPixel[x].Alpha = 0x80;
            pDestPixel[x].Y = RGBtoY(pSrcPixel[x]);   
            pDestPixel[x].Cb = RGBtoU(pSrcPixel[x]);   
            pDestPixel[x].Cr = RGBtoV(pSrcPixel[x]);   
        }
        pDest += lDestStride;
        pSrc += lSrcStride;
    }
}

下一個範例會將 32 位 RGB 影像轉換成 YV12 影像。 此範例示範如何處理平面 YUV 格式。 (YV12 是平面 4:2:0 格式。在此範例中,函式會維護目標影像中三個平面的三個個別指標。 不過,基本方法與上一個範例相同。

void RGB32_To_YV12(
    BYTE*       pDest,
    LONG        lDestStride,
    const BYTE* pSrc,
    LONG        lSrcStride,
    DWORD       dwWidthInPixels,
    DWORD       dwHeightInPixels
    )
{
    assert(dwWidthInPixels % 2 == 0);
    assert(dwHeightInPixels % 2 == 0);

    const BYTE *pSrcRow = pSrc;
    
    BYTE *pDestY = pDest;

    // Calculate the offsets for the V and U planes.

    // In YV12, each chroma plane has half the stride and half the height  
    // as the Y plane.
    BYTE *pDestV = pDest + (lDestStride * dwHeightInPixels);
    BYTE *pDestU = pDest + 
                   (lDestStride * dwHeightInPixels) + 
                   ((lDestStride * dwHeightInPixels) / 4);

    // Convert the Y plane.
    for (DWORD y = 0; y < dwHeightInPixels; y++)
    {
        RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow;
        
        for (DWORD x = 0; x < dwWidthInPixels; x++)
        {
            pDestY[x] = RGBtoY(pSrcPixel[x]);    // Y0
        }
        pDestY += lDestStride;
        pSrcRow += lSrcStride;
    }

    // Convert the V and U planes.

    // YV12 is a 4:2:0 format, so each chroma sample is derived from four 
    // RGB pixels.
    pSrcRow = pSrc;
    for (DWORD y = 0; y < dwHeightInPixels; y += 2)
    {
        RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow;
        RGBQUAD *pNextSrcRow = (RGBQUAD*)(pSrcRow + lSrcStride);

        BYTE *pbV = pDestV;
        BYTE *pbU = pDestU;

        for (DWORD x = 0; x < dwWidthInPixels; x += 2)
        {
            // Use a simple average to downsample the chroma.

            *pbV++ = ( RGBtoV(pSrcPixel[x]) +
                       RGBtoV(pSrcPixel[x + 1]) +       
                       RGBtoV(pNextSrcRow[x]) +         
                       RGBtoV(pNextSrcRow[x + 1]) ) / 4;        

            *pbU++ = ( RGBtoU(pSrcPixel[x]) +
                       RGBtoU(pSrcPixel[x + 1]) +       
                       RGBtoU(pNextSrcRow[x]) +         
                       RGBtoU(pNextSrcRow[x + 1]) ) / 4;    
        }
        pDestV += lDestStride / 2;
        pDestU += lDestStride / 2;
        
        // Skip two lines on the source image.
        pSrcRow += (lSrcStride * 2);
    }
}

在所有這些範例中,假設應用程式已判斷影像步幅。 您有時可以從媒體緩衝區取得此資訊。 否則,您必須根據視訊格式計算。 如需計算影像步幅和使用視訊媒體緩衝區的詳細資訊,請參閱 未壓縮的視訊緩衝區

視訊媒體類型

媒體類型