Image Stride (Bildschritt)
Wenn ein Videobild im Arbeitsspeicher gespeichert wird, enthält der Speicherpuffer möglicherweise zusätzliche Auffüllbytes nach jeder Pixelzeile. Die Auffüllbytes wirken sich darauf aus, wie das Bild im Arbeitsspeicher gespeichert wird. Sie haben jedoch keine Auswirkungen darauf, wie das Bild angezeigt wird.
Der Stride ist die Anzahl der Bytes im Arbeitsspeicher von einer Pixelzeile zur nächsten. Der Stride wird auch als Pitch bezeichnet. Wenn Auffüllbytes vorhanden sind, ist der Stride breiter als die Breite des Bilds, wie in der folgenden Abbildung gezeigt wird.
Zwei Puffer, die Videoframes gleicher Dimensionen enthalten, können unterschiedliche Strides aufweisen. Wenn Sie ein Videobild verarbeiten, müssen Sie den Stride berücksichtigen.
Darüber hinaus gibt es zwei Möglichkeiten, wie ein Bild im Arbeitsspeicher angeordnet werden kann. In einem Top-down-Bild erscheint die oberste Pixelzeile im Bild zuerst im Arbeitsspeicher. In einem Bottom-up-Bild erscheint die letzte Pixelzeile zuerst im Arbeitsspeicher. Die folgende Abbildung zeigt den Unterschied zwischen einem Top-down- und einem Bottom-up-Bild.
Ein Bottom-up-Bild weist einen negativen Stride auf, da er als die Anzahl der Bytes definiert ist, die eine Pixelreihe relativ zum angezeigten Bild nach unten verschoben werden muss. YUV-Bilder sollten stets Top-down-Bilder sein. Gleiches gilt für Bilder, die in einer Direct3D-Oberfläche enthalten sind. RGB-Bilder im Systemspeicher sind in der Regel Bottom-up-Bilder.
Insbesondere Videotransformationen müssen Puffer mit nicht übereinstimmenden Strides verarbeiten, da der Eingabepuffer möglicherweise nicht mit dem Ausgabepuffer übereinstimmt. Nehmen wir beispielsweise an, Sie möchten ein Quellbild konvertieren und das Ergebnis in ein Zielbild schreiben. Gehen Sie weiter davon aus, dass beide Bilder dieselbe Breite und Höhe, möglicherweise aber nicht dasselbe Pixelformat oder denselben Stride aufweisen.
Der folgende Beispielcode zeigt einen allgemeinen Ansatz zum Schreiben einer derartigen Funktion. Dies ist kein vollständiges Arbeitsbeispiel, da es von vielen der spezifischen Details abstrahiert.
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;
}
}
Diese Funktion verfügt über sechs Parameter:
- Einen Zeiger, der auf den Anfang der Scanzeile 0 im Zielbild verweist.
- Den Stride des Zielbilds.
- Einen Zeiger, der auf den Anfang der Scanzeile 0 im Quellbild verweist.
- Den Stride des Quellbilds.
- Die Breite des Bilds in Pixeln.
- Die Höhe des Bilds in Pixeln.
Die grundsätzliche Idee besteht darin, jeweils eine Zeile zu verarbeiten, wobei jedes Pixel in der Zeile durchlaufen wird. Angenommen, SOURCE_PIXEL_TYPE und DEST_PIXEL_TYPE sind Strukturen, die das Pixellayout für das Quell- bzw. Zielbild darstellen. (32-Bit-RGB verwendet beispielsweise die RGBQUAD-Struktur. Nicht jedes Pixelformat verfügt über eine vordefinierte Struktur.) Wenn Sie den Arrayzeiger in den Strukturtyp umwandeln, können Sie auf die RGB- oder YUV-Komponenten jedes Pixels zugreifen. Am Anfang jeder Zeile speichert die Funktion einen Zeiger auf die Zeile. Am Ende der Zeile erhöht sie den Zeiger um die Breite des Strides, wodurch der Zeiger zur nächsten Zeile wechselt.
In diesem Beispiel wird für jedes Pixel eine hypothetische Funktion namens „TransformPixelValue“ aufgerufen. Dies kann eine beliebige Funktion sein, die aus einem Quellpixel ein Zielpixel berechnet. Die Details hängen dabei natürlich von der jeweiligen Aufgabe ab. Wenn beispielsweise ein planares YUV-Format vorliegt, müssen Sie unabhängig von der Lumaebene auf die Chromaebenen zugreifen, bei Interlaced-Video müssen Sie die Felder möglicherweise separat verarbeiten usw.
Hier ein konkreteres Beispiel: Der folgende Code konvertiert ein 32-Bit-RGB-Bild in ein AYUV-Bild. Auf die RGB-Pixel wird mithilfe einer RGBQUAD- und auf die AYUV-Pixel mithilfe einer DXVA2_AYUVSample8-Struktur zugegriffen.
//-------------------------------------------------------------------
// 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;
}
}
Im nächsten Beispiel wird ein 32-Bit-RGB-Bild in ein YV12-Bild konvertiert. Dieses Beispiel zeigt, wie ein planares YUV-Format verarbeitet wird. (YV12 ist ein 4:2:0-Planarformat.) In diesem Beispiel verwaltet die Funktion drei separate Zeiger für die drei Ebenen im Zielbild. Der grundlegende Ansatz ist jedoch mit dem vorherigen Beispiel identisch.
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);
}
}
In all diesen Beispielen wird davon ausgegangen, dass die Anwendung den Stride bereits ermittelt hat. Manchmal können Sie diese Information aus dem Medienpuffer abrufen. Andernfalls müssen Sie sie basierend auf dem Videoformat berechnen. Weitere Informationen zum Berechnen des Strides und zum Arbeiten mit Medienpuffern für Videos finden Sie unter Nicht komprimierte Videopuffer.
Zugehörige Themen