Microsoft Media Foundation官方文档翻译(14)《Image Stride》
官方英文文档链接:https://docs.microsoft.com/en-us/windows/desktop/medfound/image-stride
基于05/31/2018
SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。
当视频图像存储在内存中时,内存缓冲区可能在每行像素后面有额外的填充字节,填充字节会影响图像在内存中的存储方式,但不会影响图像的显示方式。
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.
下面的代码展示了编写此类函数的一般写法。这不是一个完整的示例,因为这里抽象了很多实现细节。
1 void ProcessVideoImage( 2 BYTE* pDestScanLine0, 3 LONG lDestStride, 4 const BYTE* pSrcScanLine0, 5 LONG lSrcStride, 6 DWORD dwWidthInPixels, 7 DWORD dwHeightInPixels 8 ) 9 { 10 for (DWORD y = 0; y < dwHeightInPixels; y++) 11 { 12 SOURCE_PIXEL_TYPE *pSrcPixel = (SOURCE_PIXEL_TYPE*)pDestScanLine0; 13 DEST_PIXEL_TYPE *pDestPixel = (DEST_PIXEL_TYPE*)pSrcScanLine0; 14 15 for (DWORD x = 0; x < dwWidthInPixels; x +=2) 16 { 17 pDestPixel[x] = TransformPixelValue(pSrcPixel[x]); 18 } 19 pDestScanLine0 += lDestStride; 20 pSrcScanLine0 += lSrcStride; 21 } 22 }
这个函数需要 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 结构访问。
1 //------------------------------------------------------------------- 2 // Name: RGB32_To_AYUV 3 // Description: Converts an image from RGB32 to AYUV 4 //------------------------------------------------------------------- 5 void RGB32_To_AYUV( 6 BYTE* pDest, 7 LONG lDestStride, 8 const BYTE* pSrc, 9 LONG lSrcStride, 10 DWORD dwWidthInPixels, 11 DWORD dwHeightInPixels 12 ) 13 { 14 for (DWORD y = 0; y < dwHeightInPixels; y++) 15 { 16 RGBQUAD *pSrcPixel = (RGBQUAD*)pSrc; 17 DXVA2_AYUVSample8 *pDestPixel = (DXVA2_AYUVSample8*)pDest; 18 19 for (DWORD x = 0; x < dwWidthInPixels; x++) 20 { 21 pDestPixel[x].Alpha = 0x80; 22 pDestPixel[x].Y = RGBtoY(pSrcPixel[x]); 23 pDestPixel[x].Cb = RGBtoU(pSrcPixel[x]); 24 pDestPixel[x].Cr = RGBtoV(pSrcPixel[x]); 25 } 26 pDest += lDestStride; 27 pSrc += lSrcStride; 28 } 29 }
下面的例子将 32-bit RGB 图像转换为 YV12 图像。这里展示了如何处理 planar YUV 格式(YV12 是 planar 4:2:0 格式,planar后面会讲《Recommended 8-Bit YUV Formats for Video Rendering》)。本例中的函数中,分别为三个 plane 维护了三个单独的指针,但基本方法与前面的示例相同。
1 void RGB32_To_YV12( 2 BYTE* pDest, 3 LONG lDestStride, 4 const BYTE* pSrc, 5 LONG lSrcStride, 6 DWORD dwWidthInPixels, 7 DWORD dwHeightInPixels 8 ) 9 { 10 assert(dwWidthInPixels % 2 == 0); 11 assert(dwHeightInPixels % 2 == 0); 12 13 const BYTE *pSrcRow = pSrc; 14 15 BYTE *pDestY = pDest; 16 17 // Calculate the offsets for the V and U planes. 18 19 // In YV12, each chroma plane has half the stride and half the height 20 // as the Y plane. 21 BYTE *pDestV = pDest + (lDestStride * dwHeightInPixels); 22 BYTE *pDestU = pDest + 23 (lDestStride * dwHeightInPixels) + 24 ((lDestStride * dwHeightInPixels) / 4); 25 26 // Convert the Y plane. 27 for (DWORD y = 0; y < dwHeightInPixels; y++) 28 { 29 RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow; 30 31 for (DWORD x = 0; x < dwWidthInPixels; x++) 32 { 33 pDestY[x] = RGBtoY(pSrcPixel[x]); // Y0 34 } 35 pDestY += lDestStride; 36 pSrcRow += lSrcStride; 37 } 38 39 // Convert the V and U planes. 40 41 // YV12 is a 4:2:0 format, so each chroma sample is derived from four 42 // RGB pixels. 43 pSrcRow = pSrc; 44 for (DWORD y = 0; y < dwHeightInPixels; y += 2) 45 { 46 RGBQUAD *pSrcPixel = (RGBQUAD*)pSrcRow; 47 RGBQUAD *pNextSrcRow = (RGBQUAD*)(pSrcRow + lSrcStride); 48 49 BYTE *pbV = pDestV; 50 BYTE *pbU = pDestU; 51 52 for (DWORD x = 0; x < dwWidthInPixels; x += 2) 53 { 54 // Use a simple average to downsample the chroma. 55 56 *pbV++ = ( RGBtoV(pSrcPixel[x]) + 57 RGBtoV(pSrcPixel[x + 1]) + 58 RGBtoV(pNextSrcRow[x]) + 59 RGBtoV(pNextSrcRow[x + 1]) ) / 4; 60 61 *pbU++ = ( RGBtoU(pSrcPixel[x]) + 62 RGBtoU(pSrcPixel[x + 1]) + 63 RGBtoU(pNextSrcRow[x]) + 64 RGBtoU(pNextSrcRow[x + 1]) ) / 4; 65 } 66 pDestV += lDestStride / 2; 67 pDestU += lDestStride / 2; 68 69 // Skip two lines on the source image. 70 pSrcRow += (lSrcStride * 2); 71 } 72 }
在这篇的所有示例中,都假设应用程序已经确定了 stride。有时可以从 media buffer 中得到这些信息,否则必须根据视频格式进行计算。有关图像 stride 的计算和如何使用 media buffer,参考 Uncompressed Video Buffers。
