未压缩的视频缓冲区

本文介绍如何使用包含未压缩视频帧的媒体缓冲区。 根据首选项顺序,可以使用以下选项。 并非每个媒体缓冲区都支持每个选项。

  1. 使用基础 Direct3D 图面。 (仅适用于 Direct3D surfaces.) 中存储的视频帧
  2. 使用 IMF2DBuffer 接口。
  3. 使用 IMFMediaBuffer 接口。

使用基础 Direct3D Surface

视频帧可能存储在 Direct3D 图面中。 如果是这样,可以通过在媒体缓冲区对象上调用 IMFGetService::GetServiceMFGetService 来获取指向图面的指针。 使用服务标识符MR_BUFFER_SERVICE。 当访问视频帧的组件设计为使用 Direct3D 时,建议使用此方法。 例如,支持 DirectX 视频加速的视频解码器应使用此方法。

以下代码演示如何从媒体缓冲区获取 IDirect3DSurface9 指针。

IDirect3DSurface9 *pSurface = NULL;

hr = MFGetService(
    pBuffer, 
    MR_BUFFER_SERVICE,
    __uuidof(IDirect3DSurface9), 
    (void**)&pSurface
    );

if (SUCCEEDED(hr))
{
    // Call IDirect3DSurface9 methods.
}

以下对象支持 MR_BUFFER_SERVICE 服务:

使用 IMF2DBuffer 接口

如果视频帧未存储在 Direct3D 图面中,或者组件未设计为使用 Direct3D,则访问视频帧的下一种方法是查询 IMF2DBuffer 接口的缓冲区。 此接口专为图像数据设计。 若要获取指向此接口的指针,请在媒体缓冲区上调用 QueryInterface 。 并非所有媒体缓冲区对象都公开此接口。 但是,如果媒体缓冲区确实公开 IMF2DBuffer 接口,则应使用该接口访问数据(如果可能)而不是使用 IMFMediaBuffer。 你仍然可以使用 IMFMediaBuffer 接口,但效率可能更低。

  1. 在媒体缓冲区上调用 QueryInterface 以获取 IMF2DBuffer 接口。
  2. 调用 IMF2DBuffer::Lock2D 以访问缓冲区的内存。 此方法返回指向上行像素的第一个字节的指针。 它还返回图像步幅,即从一行像素开始到下一行开始的字节数。 缓冲区可能包含每行像素之后的填充字节,因此步幅可能比图像宽度宽(以字节为单位)。 如果图像在内存中面向自下而上,则步幅也可能为负值。 有关详细信息,请参阅 图像步幅
  3. 仅当需要访问内存时,才将缓冲区锁定。 通过调用 IMF2DBuffer::Unlock2D 解锁缓冲区。

并非每个媒体缓冲区都实现 IMF2DBuffer 接口。 如果媒体缓冲区未 (实现此接口,则缓冲区对象在步骤 1) 中返回E_NOINTERFACE,则必须使用下一步所述的 IMFMediaBuffer 接口接口。

使用 IMFMediaBuffer 接口

如果媒体缓冲区不公开 IMF2DBuffer 接口,请使用 IMFMediaBuffer 接口。 此接口的一般语义在主题 “使用媒体缓冲区”中介绍。

  1. 在媒体缓冲区上调用 QueryInterface 以获取 IMFMediaBuffer 接口。
  2. 调用 IMFMediaBuffer::Lock 以访问缓冲区内存。 此方法返回指向缓冲区内存的指针。 使用 Lock 方法时,步幅始终是有问题的视频格式的最低步幅,没有额外的填充字节。
  3. 仅当需要访问内存时,才将缓冲区锁定。 通过调用 IMFMediaBuffer::Unlock 解锁缓冲区。

如果缓冲区公开 IMF2DBuffer,应始终避免调用 IMFMediaBuffer::Lock,因为 Lock 方法可以将缓冲区对象强制进入连续内存块,然后重新返回。 另一方面,如果缓冲区不支持 IMF2DBuffer则 IMFMediaBuffer::Lock 可能不会生成副本。

按如下所示计算媒体类型的最小步幅:

以下代码演示如何获取最常见的视频格式的默认步幅。

HRESULT GetDefaultStride(IMFMediaType *pType, LONG *plStride)
{
    LONG lStride = 0;

    // Try to get the default stride from the media type.
    HRESULT hr = pType->GetUINT32(MF_MT_DEFAULT_STRIDE, (UINT32*)&lStride);
    if (FAILED(hr))
    {
        // Attribute not set. Try to calculate the default stride.

        GUID subtype = GUID_NULL;

        UINT32 width = 0;
        UINT32 height = 0;

        // Get the subtype and the image size.
        hr = pType->GetGUID(MF_MT_SUBTYPE, &subtype);
        if (FAILED(hr))
        {
            goto done;
        }

        hr = MFGetAttributeSize(pType, MF_MT_FRAME_SIZE, &width, &height);
        if (FAILED(hr))
        {
            goto done;
        }

        hr = MFGetStrideForBitmapInfoHeader(subtype.Data1, width, &lStride);
        if (FAILED(hr))
        {
            goto done;
        }

        // Set the attribute for later reference.
        (void)pType->SetUINT32(MF_MT_DEFAULT_STRIDE, UINT32(lStride));
    }

    if (SUCCEEDED(hr))
    {
        *plStride = lStride;
    }

done:
    return hr;
}

根据应用程序,你可能事先知道给定媒体缓冲区是否支持 IMF2DBuffer ((例如,如果创建了缓冲区) )。 否则,必须准备好使用这两个缓冲区接口之一。

以下示例显示了隐藏其中一些详细信息的帮助程序类。 此类具有一个 LockBuffer 方法,该方法调用 Lock2DLock ,并返回指向第一行像素开头的指针。 (此行为与 Lock2D 方法匹配。) LockBuffer 方法采用默认步幅作为输入参数,并将实际步幅作为输出参数返回。

class CBufferLock
{
public:
    CBufferLock(IMFMediaBuffer *pBuffer) : m_p2DBuffer(NULL), m_bLocked(FALSE)
    {
        m_pBuffer = pBuffer;
        m_pBuffer->AddRef();

        m_pBuffer->QueryInterface(IID_IMF2DBuffer, (void**)&m_p2DBuffer);
    }

    ~CBufferLock()
    {
        UnlockBuffer();
        SafeRelease(&m_pBuffer);
        SafeRelease(&m_p2DBuffer);
    }

    HRESULT LockBuffer(
        LONG  lDefaultStride,    // Minimum stride (with no padding).
        DWORD dwHeightInPixels,  // Height of the image, in pixels.
        BYTE  **ppbScanLine0,    // Receives a pointer to the start of scan line 0.
        LONG  *plStride          // Receives the actual stride.
        )
    {
        HRESULT hr = S_OK;

        // Use the 2-D version if available.
        if (m_p2DBuffer)
        {
            hr = m_p2DBuffer->Lock2D(ppbScanLine0, plStride);
        }
        else
        {
            // Use non-2D version.
            BYTE *pData = NULL;

            hr = m_pBuffer->Lock(&pData, NULL, NULL);
            if (SUCCEEDED(hr))
            {
                *plStride = lDefaultStride;
                if (lDefaultStride < 0)
                {
                    // Bottom-up orientation. Return a pointer to the start of the
                    // last row *in memory* which is the top row of the image.
                    *ppbScanLine0 = pData + abs(lDefaultStride) * (dwHeightInPixels - 1);
                }
                else
                {
                    // Top-down orientation. Return a pointer to the start of the
                    // buffer.
                    *ppbScanLine0 = pData;
                }
            }
        }

        m_bLocked = (SUCCEEDED(hr));

        return hr;
    }
    
    void UnlockBuffer()
    {
        if (m_bLocked)
        {
            if (m_p2DBuffer)
            {
                (void)m_p2DBuffer->Unlock2D();
            }
            else
            {
                (void)m_pBuffer->Unlock();
            }
            m_bLocked = FALSE;
        }
    }

private:
    IMFMediaBuffer  *m_pBuffer;
    IMF2DBuffer     *m_p2DBuffer;

    BOOL            m_bLocked;
};

图像步幅

媒体缓冲区