Buffers de vídeo descompactados

Este artigo descreve como trabalhar com buffers de mídia que contêm quadros de vídeo descompactados. Em ordem de preferência, as opções a seguir estão disponíveis. Nem todo buffer de mídia dá suporte a cada opção.

  1. Use a superfície direct3D subjacente. (Aplica-se somente a quadros de vídeo armazenados em superfícies Direct3D.)
  2. Use a interface IMF2DBuffer .
  3. Use a interface IMFMediaBuffer .

Usar o Surface Direct3D subjacente

O quadro de vídeo pode ser armazenado dentro de uma superfície Direct3D. Nesse caso, você pode obter um ponteiro para a superfície chamando IMFGetService::GetService ou MFGetService no objeto de buffer de mídia. Use o identificador de serviço MR_BUFFER_SERVICE. Essa abordagem é recomendada quando o componente que acessa o quadro de vídeo é projetado para usar o Direct3D. Por exemplo, um decodificador de vídeo que dá suporte à Aceleração de Vídeo DirectX deve usar essa abordagem.

O código a seguir mostra como obter o ponteiro IDirect3DSurface9 de um buffer de mídia.

IDirect3DSurface9 *pSurface = NULL;

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

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

Os seguintes objetos dão suporte ao serviço MR_BUFFER_SERVICE :

Usar a interface IMF2DBuffer

Se o quadro de vídeo não estiver armazenado dentro de uma superfície Direct3D ou o componente não for projetado para usar o Direct3D, a próxima maneira recomendada de acessar o quadro de vídeo é consultar o buffer para a interface IMF2DBuffer . Essa interface foi projetada especificamente para dados de imagem. Para obter um ponteiro para essa interface, chame QueryInterface no buffer de mídia. Nem todos os objetos de buffer de mídia expõem essa interface. Mas se um buffer de mídia expõe a interface IMF2DBuffer , você deve usar essa interface para acessar os dados, se possível, em vez de usar IMFMediaBuffer. Você ainda pode usar a interface IMFMediaBuffer , mas pode ser menos eficiente.

  1. Chame QueryInterface no buffer de mídia para obter a interface IMF2DBuffer .
  2. Chame IMF2DBuffer::Lock2D para acessar a memória do buffer. Esse método retorna um ponteiro para o primeiro byte da linha superior de pixels. Ele também retorna o passo da imagem, que é o número de bytes desde o início de uma linha de pixels até o início da próxima linha. O buffer pode conter bytes de preenchimento após cada linha de pixels, portanto, o passo pode ser maior do que a largura da imagem em bytes. Stride também poderá ser negativo se a imagem for orientada de baixo para cima na memória. Para obter mais informações, consulte Image Stride.
  3. Mantenha o buffer bloqueado somente enquanto você precisa acessar a memória. Desbloqueie o buffer chamando IMF2DBuffer::Unlock2D.

Nem todo buffer de mídia implementa a interface IMF2DBuffer . Se o buffer de mídia não implementar essa interface (ou seja, o objeto buffer retornará E_NOINTERFACE na etapa 1), você deverá usar a interface IMFMediaBuffer , descrita a seguir.

Usar a interface IMFMediaBuffer

Se um buffer de mídia não expor a interface IMF2DBuffer , use a interface IMFMediaBuffer . A semântica geral dessa interface é descrita no tópico Trabalhando com buffers de mídia.

  1. Chame QueryInterface no buffer de mídia para obter a interface IMFMediaBuffer .
  2. Chame IMFMediaBuffer::Lock para acessar a memória do buffer. Esse método retorna um ponteiro para a memória do buffer. Quando o método Lock é usado, o passo a passo é sempre o passo mínimo para o formato de vídeo em questão, sem bytes de preenchimento extras.
  3. Mantenha o buffer bloqueado somente enquanto você precisa acessar a memória. Desbloqueie o buffer chamando IMFMediaBuffer::Unlock.

Você sempre deve evitar chamar IMFMediaBuffer::Lock se o buffer expor IMF2DBuffer, pois o método Lock pode forçar o objeto buffer para o quadro de vídeo em um bloco de memória contíguo e voltar novamente. Por outro lado, se o buffer não der suporte a IMF2DBuffer, IMFMediaBuffer::Lock provavelmente não resultará em uma cópia.

Calcule o passo mínimo do tipo de mídia da seguinte maneira:

  • O passo mínimo pode ser armazenado no atributo MF_MT_DEFAULT_STRIDE .
  • Se o atributo MF_MT_DEFAULT_STRIDE não estiver definido, chame a função MFGetStrideForBitmapInfoHeader para calcular o passo para os formatos de vídeo mais comuns.
  • Se a função MFGetStrideForBitmapInfoHeader não reconhecer o formato, você deverá calcular o passo com base na definição do formato. Nesse caso, não há nenhuma regra geral; você precisa saber os detalhes da definição de formato.

O código a seguir mostra como obter o passo padrão para os formatos de vídeo mais comuns.

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

Dependendo do aplicativo, você pode saber com antecedência se um determinado buffer de mídia dá suporte a IMF2DBuffer (por exemplo, se você criou o buffer). Caso contrário, você deve estar preparado para usar uma das duas interfaces de buffer.

O exemplo a seguir mostra uma classe auxiliar que oculta alguns desses detalhes. Essa classe tem um método LockBuffer que chama Lock2D ou Lock e retorna um ponteiro para o início da primeira linha de pixels. (Esse comportamento corresponde ao método Lock2D .) O método LockBuffer usa o passo padrão como um parâmetro de entrada e retorna o passo real como um parâmetro de saída.

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

Passo da imagem

Buffers de mídia