官方英文文档链接:https://docs.microsoft.com/en-us/windows/desktop/medfound/image-stride
基于05/31/2018
当视频图像存储在内存中时,内存缓冲区可能在每行像素后面有额外的填充字节,填充字节会影响图像在内存中的存储方式,但不会影响图像的显示方式。
stride 是从内存中一行像素开头到内存中的下一行像素间隔的字节数。Stride 也可以叫做 pitch。如果存在填充字节,则 stride 就会大于图像宽度,如下图所示:
两个包含相同尺寸图像的 buffer 可能有不同 stride,所以在处理视频图像时必须考虑 stride。
另外,图像在内存中的排列方式有两种。top-down 图像的首行第一个像素先储存在内存中。bottom-up 图像的最后一行像素先存储在内存中。下图显示了两者的区别:
bottom-up 图像具有负的 stride,因为 stride 被定义为从第一行像素到第二行像素需要向后移动的距离。YUV 图像应该始终是top-down,Direct3D surface 则必须是 top-down。而内存中的 RGB 图像则通常是 bottom-up。
做视频转换时尤其需要处理 stride 不匹配的 buffer,(剩下的废话。。), because the input buffer might not match the output buffer. For example, suppose that you want to convert a source image and write the result to a destination image. Assume that both images have the same width and height, but might not have the same pixel format or the same image stride.
下面的代码展示了编写此类函数的一般写法。这不是一个完整的示例,因为这里抽象了很多实现细节。
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*)pDestScanLine0;
DEST_PIXEL_TYPE *pDestPixel = (DEST_PIXEL_TYPE*)pSrcScanLine0;
for (DWORD x = 0; x < dwWidthInPixels; x +=2)
{
pDestPixel[x] = TransformPixelValue(pSrcPixel[x]);
}
pDestScanLine0 += lDestStride;
pSrcScanLine0 += lSrcStride;
}
}
这个函数需要 6 个参数:
- 指向目标图像首行像素开头位置(scan line 0)的指针
- 目标图像的 stride
- 指向源图像首行像素开头位置(scan line 0)的指针
- 源图像的 stride
- 图像宽度(像素)
- 图像高度(像素)
通常情况下每次处理一行,每次遍历行中的每个像素。假设 SOURCE_PIXEL_TYPE 和 DEST_PIXEL_TYPE 分别是表示源图像和目标图像像素的结构体(例如 32-bit RGB 使用 RGBQUAD structure。不一定每种像素格式都有一个定义好的结构体),则可以将数组指针转换为该结构体的指针,以访问每个像素的 RGB 或 YUV 分量。每行结束时,指针按照 stride 递增,使指针指向下一行。
本例中为每个像素都调用了一个假设的名为 TransformPixelValue 的转换函数。这个可以是任何能把源像素转换为目标像素的函数,当然具体细节取决于需求。例如对于 planar YUV 格式,你必须分别独立处理 luma plane 和 chroma plane;对于视频,可能需要分别处理 field,等等。
下面给出了一个具体的例子,将 32-bit 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-bit RGB 图像转换为 YV12 图像。这里展示了如何处理 planar YUV 格式(YV12 是 planar 4:2:0 格式,planar后面会讲《Recommended 8-Bit YUV Formats for Video Rendering》)。本例中的函数中,分别为三个 plane 维护了三个单独的指针,但基本方法与前面的示例相同。
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);
}
}
I在这篇的所有示例中,都假设应用程序已经确定了 stride。有时可以从 media buffer 中得到这些信息,否则必须根据视频格式进行计算。有关图像 stride 的计算和如何使用 media buffer,参考 Uncompressed Video Buffers。