Несжатые буферы видео

В этой статье описывается, как работать с буферами мультимедиа, содержащими несжатые видеокадры. В порядке предпочтения доступны следующие параметры. Не каждый буфер мультимедиа поддерживает каждый параметр.

  1. Используйте базовую поверхность Direct3D. (Применяется только к видеокадрам, хранящимся на поверхностях Direct3D.)
  2. Используйте интерфейс IMF2DBuffer .
  3. Используйте интерфейс IMFMediaBuffer .

Использование базовой поверхности Direct3D

Видеокадр может храниться внутри поверхности Direct3D. Если это так, вы можете получить указатель на поверхность, вызвав IMFGetService::GetService или MFGetService в объекте буфера мультимедиа. Используйте 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 . Если буфер мультимедиа не реализует этот интерфейс (то есть объект буфера возвращает E_NOINTERFACE на шаге 1), необходимо использовать интерфейс IMFMediaBuffer, описанный ниже.

Использование интерфейса IMFMediaBuffer

Если буфер мультимедиа не предоставляет интерфейс IMF2DBuffer , используйте интерфейс IMFMediaBuffer . Общая семантика этого интерфейса описана в разделе Работа с буферами мультимедиа.

  1. Вызовите QueryInterface в буфере мультимедиа, чтобы получить интерфейс IMFMediaBuffer .
  2. Вызовите IMFMediaBuffer::Lock , чтобы получить доступ к буферной памяти. Этот метод возвращает указатель на буферную память. При использовании метода Lock шаг всегда является минимальным для соответствующего формата видео без дополнительных байтов заполнения.
  3. Сохраняйте блокировку буфера только в то время, когда требуется доступ к памяти. Разблокируйте буфер, вызвав IMFMediaBuffer::Unlock.

Всегда следует избегать вызова IMFMediaBuffer::Lock , если буфер предоставляет функцию IMF2DBuffer, так как метод Lock может принудительно принудить объект буфера к видеокадру в непрерывный блок памяти, а затем обратно. С другой стороны, если буфер не поддерживает IMF2DBuffer, то ФУНКЦИЯ IMFMediaBuffer::Lock , скорее всего, не приведет к копированию.

Вычислите минимальный шаг от типа носителя следующим образом:

  • Минимальный шаг может храниться в атрибуте MF_MT_DEFAULT_STRIDE .
  • Если атрибут MF_MT_DEFAULT_STRIDE не задан, вызовите функцию MFGetStrideForBitmapInfoHeader , чтобы вычислить шаг для наиболее распространенных форматов видео.
  • Если функция MFGetStrideForBitmapInfoHeader не распознает формат, необходимо вычислить шаг на основе определения формата. В этом случае не существует общего правила; необходимо знать сведения об определении формата.

В следующем коде показано, как получить шаг по умолчанию для наиболее распространенных форматов видео.

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, который вызывает lock2D или Lock и возвращает указатель на начало первой строки пикселей. (Это поведение соответствует методу 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;
};

Шаг изображения

Буферы мультимедиа