Microsoft Media Foundation官方文档翻译(14)《Image Stride》

官方英文文档链接:https://docs.microsoft.com/en-us/windows/desktop/medfound/image-stride

基于05/31/2018

 

当视频图像存储在内存中时,内存缓冲区可能在每行像素后面有额外的填充字节,填充字节会影响图像在内存中的存储方式,但不会影响图像的显示方式。

stride 是从内存中一行像素开头到内存中的下一行像素间隔的字节数。Stride 也可以叫做 pitch。如果存在填充字节,则 stride 就会大于图像宽度,如下图所示:

diagram showing an image plus padding.

两个包含相同尺寸图像的 buffer 可能有不同 stride,所以在处理视频图像时必须考虑 stride。

另外,图像在内存中的排列方式有两种。top-down 图像的首行第一个像素先储存在内存中。bottom-up 图像的最后一行像素先存储在内存中。下图显示了两者的区别:

diagram showing top-down and bottom-up images.

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值