影像步幅
當視訊影像儲存在記憶體中時,記憶體緩衝區可能包含每個像素數據列之後的額外填補位元組。 填補位元節會影響影像儲存在記憶體中的方式,但不會影響影像的顯示方式。
stride 是記憶體中一列圖元到記憶體中下一列圖元的位元組數目。 大步也稱為 投球。 如果填補位元組存在,則步幅會比影像的寬度寬,如下圖所示。
包含相同維度之視訊畫面的兩個緩衝區可以有兩個不同的步幅。 如果您處理視訊影像,則必須將步幅納入考慮。
此外,還有兩種方式可將影像排列在記憶體中。 在由上而下影像中,影像中的像素頂端數據列會先出現在記憶體中。 在從下到下影像中,最後一列圖元會先出現在記憶體中。 下圖顯示由上而下影像與由下而下影像之間的差異。
下拉影像有負幅,因為步幅定義為相對於所顯示影像,需要向下移動一列圖元的位元組數目。 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);
}
}
在所有這些範例中,假設應用程式已判斷影像步幅。 您有時可以從媒體緩衝區取得此資訊。 否則,您必須根據視訊格式計算。 如需計算影像步幅和使用視訊媒體緩衝區的詳細資訊,請參閱 未壓縮的視訊緩衝區。
相關主題