Microsoft Media Foundation官方文档翻译(21)《Media Buffers》

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

基于05/31/2018

本篇包含了 Media Buffer 的所有内容

Media Buffers

  • 05/31/2018
  • 2 minutes to read

一个 media buffer 是一个 管理着一块内存的 COM 组件,通常包含了媒体数据。Media buffers 用来把数据从一个 pipeline 组件传递给下一个组件。大多数应用程序不会直接使用 media buffers,因为 Media Session 管理着 pipeline 组件之间的数据流。如果你需要写自己的 pipeline 组件,或者不通过 Media Session,而是直接操作 pipeline 组件,那么你一定会用到 media buffers。

Media buffers 暴露了IMFMediaBuffer 接口。这个接口可以用来读写任何类型的数据。未压缩的视频帧需要特殊处理,因为它们可能存储在 显存 中的 Direct3D surface 中。

This section contains the following topics.

Topic Description
Working with Media Buffers Describes the general behavior of media buffers for all media types.
Uncompressed Video Buffers How work with media buffers that contain uncompressed video frames.
DirectX Surface Buffer Describes how to store a Direct3D surface in a media buffer.

Working with Media Buffers

  • 05/31/2018
  • 2 minutes to read

所有的 media buffers 都公开了 IMFMediaBuffer 接口,未压缩的视频帧是一种特例,将在 Uncompressed Video Buffers 里详细介绍。

Buffer Size

media buffer 有两种相关的 “size”:

  • maximum length 是这个 buffer 申请的物理内存大小。这个数值在 buffer 被创建时就确定了,而且在 buffer 的生命周期中无法更改。这个表示这个 buffer 最多可以存储多少数据。调用 IMFMediaBuffer::GetMaxLength 可以获取这个值。

  • current length 表示当前 buffer 的有效数据有多少。buffer 刚刚创建时,这个值为 0,因为此时还没有存储任何有效数据。如果向 buffer 里写入了任何数据,都必须调用 IMFMediaBuffer::SetCurrentLength 来更新 current length。例如向 buffer 里写入 100 字节数据时,要调用 SetCurrentLength(100)。当从 media buffer 里面读取数据时,先调用 IMFMediaBuffer::GetCurrentLength 来确定当前 buffer 里有多少有效数据。不要读多了。current length 永远不会超过 maximum length。

Accessing the Buffer Memory

要访问 buffer 管理的内存,调用 IMFMediaBuffer::Lock。这个方法会返回一个指向一块内存的地址。同时也会返回这个 buffer 的 maximum length 和 current length。当不再使用这个指针时,调用 IMFMediaBuffer::Unlock

向 media buffer 中写入数据:

  1. 调用 IMFMediaBuffer::Lock 获得指向内存的指针。此函数同时返回 maximum length。
  2. 向内存中写入数据,不要大于 buffer 的 maximum length。
  3. 调用 IMFMediaBuffer::SetCurrentLength 更新 current length。数值要与步骤 2 中写入的数据量相等。
  4. 调用 IMFMediaBuffer::Unlock 来结束使用 buffer。

从 media buffer 中读取数据:

  1. 调用 IMFMediaBuffer::Lock 来获取指针。此函数同时返回 current length。
  2. 读取内存数据,不要超过 current length。
  3. 调用 IMFMediaBuffer::Unlock 来结束使用 buffer。

Creating System Memory Buffers

要创建一个实例,调用 MFCreateMemoryBufferMFCreateAlignedMemoryBuffer,并且指明需要的大小即可。两个函数都会申请一块内存,然后返回一个 IMFMediaBuffer 指针。内存会在 media buffer 的引用计数为 0 时自动释放。

下面的示例演示如何创建一个 buffer 并向其写入数据。

HRESULT CreateSystemMemoryBuffer(
    BYTE *pSrc, 
    DWORD cbData, 
    IMFMediaBuffer **ppBuffer
    )
{
    HRESULT hr = S_OK;
    BYTE *pData = NULL;

    IMFMediaBuffer *pBuffer = NULL;

    // Create the media buffer.
    hr = MFCreateMemoryBuffer(
        cbData,   // Amount of memory to allocate, in bytes.
        &pBuffer        
        );

    // Lock the buffer to get a pointer to the memory.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->Lock(&pData, NULL, NULL);
    }

    if (SUCCEEDED(hr))
    {
        memcpy_s(pData, cbData, pSrc, cbData);
    }

    // Update the current length.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->SetCurrentLength(cbData);
    }

    // Unlock the buffer.
    if (pData)
    {
        hr = pBuffer->Unlock();
    }

    if (SUCCEEDED(hr))
    {
        *ppBuffer = pBuffer;
        (*ppBuffer)->AddRef();
    }

    return hr;
}

Uncompressed Video Buffers

  • 05/31/2018
  • 5 minutes to read

In this article

  1. Use the Underlying Direct3D Surface
  2. Use the IMF2DBuffer Interface
  3. Use the IMFMediaBuffer Interface
  4. Related topics

此部分介绍如何使用包含未压缩视频帧的 media buffer。按照优先级,可以使用以下选项。并不是每一个 media buffer 都支持所有选项。

  1. 使用底层 Direct3D surface. (仅适用于存储在 Direct3D surface 中的视频帧。)
  2. 使用 IMF2DBuffer 接口。
  3. 使用 IMFMediaBuffer 接口.

Use the Underlying Direct3D Surface

视频帧有可能存储在 Direct3D surface 中。如果是这样,你可以通过在 media buffer 上调用 IMFGetService::GetService 或 MFGetService 来获取指向 surface 的指针。使用服务标识符 MR_BUFFER_SERVICE(Use the service identifier MR_BUFFER_SERVICE.)。当组件使用 Direct3D 访问视频帧时建议使用此方法。例如,支持 DirectX 硬件加速的解码器应使用此方法。

下面的代码展示了如何从 media buffer 获得 IDirect3DSurface9 指针。

IDirect3DSurface9 *pSurface = NULL;

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

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

The following objects support the MR_BUFFER_SERVICE service:

Use the IMF2DBuffer Interface

如果视频帧没有存储在 Direct3D surface 中,或者组件不使用 Direct3D,那么接下来建议使用 IMF2DBuffer 接口。这个接口专门为图像数据而设计。要获取此接口的指针,在 media buffer 上调用 QueryInterface。并非所有的 media buffer 都支持此接口。但是如果一个 media buffer 支持此接口,那么你就应该使用这个接口来访问数据,而不是使用 IMFMediaBuffer。你仍然可以使用 IMFMediaBuffer 接口,但效率会比较低。

  1. 在 media buffer 上调用 QueryInterface 来获取 IMF2DBuffer 接口。
  2. 调用 IMF2DBuffer::Lock2D 来访问内存。这个方法返回指向第一行像素的第一字节的指针,同时返回图像的 stride(从一行像素开始到下一行像素开始的字节偏移量)。注意每行像素之后可能存在间隔,所以 stride 可能比一行像素的字节数要大。当图像在内存中以 bottom-up 存储时,stride 为负。参考 Image Stride
  3. 访问内存时保持 buffer 为 locked。调用 IMF2DBuffer::Unlock2D 来 Unlock buffer。

并非所有的 media buffer 实现了 IMF2DBuffer 接口。如果没有实现这个接口(也就是步骤 1 返回了 E_NOINTERFACE),那就必须使用 IMFMediaBuffer 接口。下面将会介绍。

Use the IMFMediaBuffer Interface

如果 media buffer 没有 IMF2DBuffer 接口,那么就使用 IMFMediaBuffer 接口。Working with Media Buffers 中描述过此接口。

  1. 在 media buffer 上调用 QueryInterface 来获取 IMFMediaBuffer 接口。
  2. 调用 IMFMediaBuffer::Lock 来访问内存。此方法返回一个指向内存的指针。调用 Lock 之后,stride 总是等于视频格式的stride,也就是一行像素后面没有填充字节。
  3. 访问内存时保持 buffer 为 locked。调用 IMFMediaBuffer::Unlock 来 Unlock buffer。

如果 buffer 可以使用 IMF2DBuffer 接口,那就应该避免使用 IMFMediaBuffer::Lock,因为 Lock 方法会将视频帧转移(force?)到一块连续的内存中然后返回(为了消除行与行之间的额外填充)。另一方面,buffer 如果不支持 IMF2DBuffer,那么 IMFMediaBuffer::Lock 也就不会产生额外的拷贝。

从 media type 计算 stride 如下所示:

下面的代码展示了对于大多数视频格式来说如何获得 stride。

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;
}

根据您的应用程序,你可能会知道给定的 media buffer 是否支持 IMF2DBuffer(例如这个 buffer 是你自己创建的),否则,你必须准备好使用这两种接口中的任意一个。

下面的代码是一个帮助类,可以帮助你包装隐藏这些细节。这个类有一个 LockBuffer 方法来调用 Lock2D 或者 Lock,然后返回指向第一行开头像素的指针。LockBuffer 方法还返回实际的 stride。

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;
};

DirectX Surface Buffer

  • 05/31/2018
  • 2 minutes to read

DirectX surface buffer 是管理 Direct3D surface 的 media buffer。要创建这种实例,调用 MFCreateDXSurfaceBuffer 并传入指向 DirectX surface 的指针。DirectX surface buffer 公开了以下接口:

有几种方法可以访问 surface 的内存:

  • 建议此种:在 buffer 上调用 IMFGetService::GetService。使用标识 MR_BUFFER_SERVICE。此方法返回一个指向底层 Direct3D surface 的指针。
  • 调用 IMF2DBuffer::Lock2D。这个方法会在 surface 上直接调用 IDirect3DSurface9::LockRectIMF2DBuffer::Unlock2D 方法则会在 surface 上调用 UnlockRect
  • 调用 IMFMediaBuffer::Lock。通常不建议这样做,因为这时它会将数据从 Direct3D surface 复制到内存然后再返回。使用 Lock2D 都会更好一些。

如果底层 surface 不能 lock,那么 Lock 和 Lock2D 都可能失败。DirectX surface buffer 实现了这两种方法,主要是考虑到一些应用程序可能不使用 Direct3D。

当解码器未配置 DirectX Video Acceleration (DXVA) 时,enhanced video renderer (EVR) 会创建 DirectX surface buffer。详细信息参考 IMFVideoSampleAllocator

Obtaining the Direct3D Surface

要从一个 video sample 获取 Direct3D surface,步骤如下:

  1. 使用序号 0 调用 IMFSample::GetBufferByIndex
  2. 使用 MR_BUFFER_SERVICE 标识调用 MFGetService

下面的代码演示了这两步:

HRESULT GetD3DSurfaceFromSample(IMFSample *pSample, IDirect3DSurface9 **ppSurface)
{
    *ppSurface = NULL;

    IMFMediaBuffer *pBuffer = NULL;

    HRESULT hr = pSample->GetBufferByIndex(0, &pBuffer);
    if (SUCCEEDED(hr))
    {
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(ppSurface));
        pBuffer->Release();
    }

    return hr;
}

 

 

 

 

原创文章 59 获赞 41 访问量 10万+

猜你喜欢

转载自blog.csdn.net/rzdyzx/article/details/89110260