Búferes de vídeo sin comprimir

En este artículo se describe cómo trabajar con búferes multimedia que contienen fotogramas de vídeo sin comprimir. En orden de preferencia, están disponibles las siguientes opciones. No todos los búferes multimedia admiten cada opción.

  1. Usa la superficie de Direct3D subyacente. (Solo se aplica a fotogramas de vídeo almacenados en superficies de Direct3D).
  2. Use la interfaz IMF2DBuffer .
  3. Use la interfaz IMFMediaBuffer .

Usar la superficie de Direct3D subyacente

El fotograma de vídeo puede almacenarse dentro de una superficie de Direct3D. Si es así, puede obtener un puntero a la superficie llamando a IMFGetService::GetService o MFGetService en el objeto de búfer multimedia. Use el identificador de servicio MR_BUFFER_SERVICE. Este enfoque se recomienda cuando el componente que accede al fotograma de vídeo está diseñado para usar Direct3D. Por ejemplo, un descodificador de vídeo que admita directX Video Acceleration debe usar este enfoque.

En el código siguiente se muestra cómo obtener el puntero IDirect3DSurface9 de un búfer multimedia.

IDirect3DSurface9 *pSurface = NULL;

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

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

Los objetos siguientes admiten el servicio MR_BUFFER_SERVICE :

Usar la interfaz IMF2DBuffer

Si el fotograma de vídeo no se almacena dentro de una superficie direct3D o el componente no está diseñado para usar Direct3D, la siguiente manera recomendada de acceder al fotograma de vídeo es consultar el búfer de la interfaz IMF2DBuffer . Esta interfaz está diseñada específicamente para los datos de imagen. Para obtener un puntero a esta interfaz, llame a QueryInterface en el búfer multimedia. No todos los objetos de búfer multimedia exponen esta interfaz. Pero si un búfer multimedia expone la interfaz IMF2DBuffer , debe usar esa interfaz para acceder a los datos, si es posible, en lugar de usar IMFMediaBuffer. Todavía puede usar la interfaz IMFMediaBuffer , pero podría ser menos eficaz.

  1. Llame a QueryInterface en el búfer multimedia para obtener la interfaz IMF2DBuffer .
  2. Llame a IMF2DBuffer::Lock2D para acceder a la memoria del búfer. Este método devuelve un puntero al primer byte de la fila superior de píxeles. También devuelve el intervalo de imagen, que es el número de bytes desde el inicio de una fila de píxeles hasta el inicio de la fila siguiente. El búfer puede contener bytes de relleno después de cada fila de píxeles, por lo que el intervalo podría ser más ancho que el ancho de la imagen en bytes. El paso también puede ser negativo si la imagen está orientada hacia abajo en la memoria. Para obtener más información, consulte Image Stride.
  3. Mantenga el búfer bloqueado solo mientras necesita acceder a la memoria. Desbloquee el búfer llamando a IMF2DBuffer::Unlock2D.

No todos los búferes multimedia implementan la interfaz IMF2DBuffer . Si el búfer multimedia no implementa esta interfaz (es decir, el objeto de búfer devuelve E_NOINTERFACE en el paso 1), debe usar la interfaz de interfaz IMFMediaBuffer , descrita a continuación.

Usar la interfaz IMFMediaBuffer

Si un búfer multimedia no expone la interfaz IMF2DBuffer , utilice la interfaz IMFMediaBuffer . La semántica general de esta interfaz se describe en el tema Trabajar con búferes multimedia.

  1. Llame a QueryInterface en el búfer multimedia para obtener la interfaz IMFMediaBuffer .
  2. Llame a IMFMediaBuffer::Lock para acceder a la memoria del búfer. Este método devuelve un puntero a la memoria del búfer. Cuando se usa el método Lock , el intervalo siempre es el intervalo mínimo para el formato de vídeo en cuestión, sin bytes de relleno adicionales.
  3. Mantenga el búfer bloqueado solo mientras necesita acceder a la memoria. Desbloquee el búfer llamando a IMFMediaBuffer::Unlock.

Siempre debe evitar llamar a IMFMediaBuffer::Lock si el búfer expone IMF2DBuffer, ya que el método Lock puede forzar el objeto de búfer al fotograma de vídeo en un bloque de memoria contiguo y, a continuación, de nuevo. Por otro lado, si el búfer no admite IMF2DBuffer, entonces IMFMediaBuffer::Lock probablemente no dará lugar a una copia.

Calcule el intervalo mínimo del tipo de medio de la siguiente manera:

  • El intervalo mínimo se puede almacenar en el atributo MF_MT_DEFAULT_STRIDE .
  • Si no se establece el atributo MF_MT_DEFAULT_STRIDE , llame a la función MFGetStrideForBitmapInfoHeader para calcular el intervalo de los formatos de vídeo más comunes.
  • Si la función MFGetStrideForBitmapInfoHeader no reconoce el formato, debe calcular el intervalo en función de la definición del formato. En ese caso, no hay ninguna regla general; debe conocer los detalles de la definición de formato.

En el código siguiente se muestra cómo obtener el intervalo predeterminado para los formatos de vídeo más comunes.

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

En función de la aplicación, es posible que sepa de antemano si un búfer multimedia determinado admite IMF2DBuffer (por ejemplo, si ha creado el búfer). De lo contrario, debe estar preparado para usar cualquiera de las dos interfaces de búfer.

En el ejemplo siguiente se muestra una clase auxiliar que oculta algunos de estos detalles. Esta clase tiene un método LockBuffer que llama a Lock2D o Lock y devuelve un puntero al inicio de la primera fila de píxeles. (Este comportamiento coincide con el método Lock2D ). El método LockBuffer toma el intervalo predeterminado como parámetro de entrada y devuelve el intervalo real como un parámetro de salida.

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

Intervalo de imagen

Búferes multimedia