Uso del lector de origen para procesar datos multimedia

En este tema se describe cómo usar el Lector de origen para procesar datos multimedia.

Para usar el Lector de origen, siga estos pasos básicos:

  1. Cree una instancia del Lector de origen.
  2. Enumerar los posibles formatos de salida.
  3. Establezca el formato de salida real para cada secuencia.
  4. Procesar datos del origen.

En el resto de este tema se describen estos pasos en detalle.

Creación del lector de origen

Para crear una instancia del Lector de origen, llame a una de las funciones siguientes:

Función Descripción
MFCreateSourceReaderFromURL
Toma una dirección URL como entrada. Esta función usa el Solucionador de origen para crear un origen multimedia a partir de la dirección URL.
MFCreateSourceReaderFromByteStream
Toma un puntero a una secuencia de bytes. Esta función también usa el Solucionador de origen para crear el origen multimedia.
MFCreateSourceReaderFromMediaSource
Toma un puntero a un origen multimedia que ya se ha creado. Esta función es útil para los orígenes multimedia que el Solucionador de origen no puede crear, como dispositivos de captura o orígenes multimedia personalizados.

 

Normalmente, para archivos multimedia, use MFCreateSourceReaderFromURL. Para dispositivos, como cámaras web, use MFCreateSourceReaderFromMediaSource. (Para obtener más información sobre la captura de dispositivos en Microsoft Media Foundation, vea Audio/Video Capture).

Cada una de estas funciones toma un puntero opcional IMFAttributes , que se usa para establecer varias opciones en el Lector de origen, como se describe en los temas de referencia de estas funciones. Para obtener el comportamiento predeterminado, establezca este parámetro en NULL. Cada función devuelve un puntero IMFSourceReader como parámetro de salida. Debe llamar a la función CoInitialize(Ex) y MFStartup antes de llamar a cualquiera de estas funciones.

El código siguiente crea el Lector de origen a partir de una dirección URL.

int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
    if (argc < 2)
    {
        return 1;
    }

    const WCHAR *pszURL = argv[1];

    // Initialize the COM runtime.
    HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (SUCCEEDED(hr))
    {
        // Initialize the Media Foundation platform.
        hr = MFStartup(MF_VERSION);
        if (SUCCEEDED(hr))
        {
            // Create the source reader.
            IMFSourceReader *pReader;
            hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
            if (SUCCEEDED(hr))
            {
                ReadMediaFile(pReader);
                pReader->Release();
            }
            // Shut down Media Foundation.
            MFShutdown();
        }
        CoUninitialize();
    }
}

Enumerar formatos de salida

Cada origen multimedia tiene al menos una secuencia. Por ejemplo, un archivo de vídeo podría contener una secuencia de vídeo y una secuencia de audio. El formato de cada secuencia se describe mediante un tipo de medio, representado por la interfaz IMFMediaType . Para obtener más información sobre los tipos de medios, vea Tipos de medios. Debe examinar el tipo de medio para comprender el formato de los datos que obtiene del Lector de origen.

Inicialmente, cada secuencia tiene un formato predeterminado, que puede encontrar llamando al método IMFSourceReader::GetCurrentMediaType :

Para cada secuencia, el origen multimedia ofrece una lista de posibles tipos de medios para esa secuencia. El número de tipos depende del origen. Si el origen representa un archivo multimedia, normalmente solo hay un tipo por secuencia. Por otro lado, una cámara web puede transmitir vídeo en varios formatos diferentes. En ese caso, la aplicación puede seleccionar el formato que se va a usar en la lista de tipos de medios.

Para obtener uno de los tipos de medios de una secuencia, llame al método IMFSourceReader::GetNativeMediaType . Este método toma dos parámetros de índice: el índice de la secuencia y un índice en la lista de tipos multimedia de la secuencia. Para enumerar todos los tipos de una secuencia, incremente el índice de lista mientras mantiene la constante del índice de flujo. Cuando el índice de lista sale de los límites, GetNativeMediaType devuelve MF_E_NO_MORE_TYPES.

HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    HRESULT hr = S_OK;
    DWORD dwMediaTypeIndex = 0;

    while (SUCCEEDED(hr))
    {
        IMFMediaType *pType = NULL;
        hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
        if (hr == MF_E_NO_MORE_TYPES)
        {
            hr = S_OK;
            break;
        }
        else if (SUCCEEDED(hr))
        {
            // Examine the media type. (Not shown.)

            pType->Release();
        }
        ++dwMediaTypeIndex;
    }
    return hr;
}

Para enumerar los tipos de medios de cada secuencia, incremente el índice de flujo. Cuando el índice de secuencia sale de los límites, GetNativeMediaType devuelve MF_E_INVALIDSTREAMNUMBER.

HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    DWORD dwStreamIndex = 0;

    while (SUCCEEDED(hr))
    {
        hr = EnumerateTypesForStream(pReader, dwStreamIndex);
        if (hr == MF_E_INVALIDSTREAMNUMBER)
        {
            hr = S_OK;
            break;
        }
        ++dwStreamIndex;
    }
    return hr;
}

Establecer formatos de salida

Para cambiar el formato de salida, llame al método IMFSourceReader::SetCurrentMediaType . Este método toma el índice de secuencia y un tipo de medio:

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

Para el tipo de medio, depende de si desea insertar un descodificador.

  • Para obtener datos directamente desde el origen sin descodificarlos, use uno de los tipos devueltos por GetNativeMediaType.
  • Para descodificar la secuencia, cree un nuevo tipo de medio que describa el formato sin comprimir deseado.

En el caso del descodificador, cree el tipo de medio de la siguiente manera:

  1. Llame a MFCreateMediaType para crear un nuevo tipo de medio.
  2. Establezca el atributo MF_MT_MAJOR_TYPE para especificar audio o vídeo.
  3. Establezca el atributo MF_MT_SUBTYPE para especificar el subtipo del formato de descodificación. (Consulte GUID de subtipo de audio y GUID de subtipo de vídeo).
  4. Llame a IMFSourceReader::SetCurrentMediaType.

El lector de origen cargará automáticamente el descodificador. Para obtener los detalles completos del formato descodificado, llame a IMFSourceReader::GetCurrentMediaType después de la llamada a SetCurrentMediaType.

El código siguiente configura la secuencia de vídeo para RGB-32 y la secuencia de audio para el audio PCM.

HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
    IMFMediaType *pNativeType = NULL;
    IMFMediaType *pType = NULL;

    // Find the native format of the stream.
    HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
    if (FAILED(hr))
    {
        return hr;
    }

    GUID majorType, subtype;

    // Find the major type.
    hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Define the output type.
    hr = MFCreateMediaType(&pType);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Select a subtype.
    if (majorType == MFMediaType_Video)
    {
        subtype= MFVideoFormat_RGB32;
    }
    else if (majorType == MFMediaType_Audio)
    {
        subtype = MFAudioFormat_PCM;
    }
    else
    {
        // Unrecognized type. Skip.
        goto done;
    }

    hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
    if (FAILED(hr))
    {
        goto done;
    }

    // Set the uncompressed format.
    hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
    if (FAILED(hr))
    {
        goto done;
    }

done:
    SafeRelease(&pNativeType);
    SafeRelease(&pType);
    return hr;
}

Procesamiento de datos multimedia

Para obtener datos multimedia del origen, llame al método IMFSourceReader::ReadSample , como se muestra en el código siguiente.

        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

El primer parámetro es el índice de la secuencia para la que desea obtener datos. También puede especificar MF_SOURCE_READER_ANY_STREAM para obtener los datos disponibles siguientes de cualquier secuencia. El segundo parámetro contiene marcas opcionales; consulte MF_SOURCE_READER_CONTROL_FLAG para obtener una lista de estos. El tercer parámetro recibe el índice de la secuencia que realmente genera los datos. Necesitará esta información si establece el primer parámetro en MF_SOURCE_READER_ANY_STREAM. El cuarto parámetro recibe marcas de estado, lo que indica varios eventos que pueden producirse al leer los datos, como los cambios de formato en la secuencia. Para obtener una lista de las marcas de estado, consulte MF_SOURCE_READER_FLAG.

Si el origen de medios puede generar datos para la secuencia solicitada, el último parámetro de ReadSample recibe un puntero a la interfaz IMFSample de un objeto de ejemplo multimedia. Use el ejemplo multimedia para:

  • Obtener un puntero a los datos multimedia.
  • Obtenga el tiempo de presentación y la duración de la muestra.
  • Obtenga atributos que describen la entrelazamiento, la dominación de campos y otros aspectos del ejemplo.

El contenido de los datos multimedia depende del formato de la secuencia. Para una secuencia de vídeo sin comprimir, cada muestra multimedia contiene un único fotograma de vídeo. Para una secuencia de audio sin comprimir, cada muestra multimedia contiene una secuencia de fotogramas de audio.

El método ReadSample puede devolver S_OK y aún no devolver un ejemplo multimedia en el parámetro pSample . Por ejemplo, al llegar al final del archivo, ReadSample establece la marca MF_SOURCE_READERF_ENDOFSTREAM en dwFlags y establece pSample en NULL. En este caso, el método ReadSample devuelve S_OK porque no se ha producido ningún error, aunque el parámetro pSample esté establecido en NULL. Por lo tanto, compruebe siempre el valor de pSample antes de desreferenciarlo.

En el código siguiente se muestra cómo llamar a ReadSample en un bucle y comprobar la información devuelta por el método hasta el final del archivo multimedia.

HRESULT ProcessSamples(IMFSourceReader *pReader)
{
    HRESULT hr = S_OK;
    IMFSample *pSample = NULL;
    size_t  cSamples = 0;

    bool quit = false;
    while (!quit)
    {
        DWORD streamIndex, flags;
        LONGLONG llTimeStamp;

        hr = pReader->ReadSample(
            MF_SOURCE_READER_ANY_STREAM,    // Stream index.
            0,                              // Flags.
            &streamIndex,                   // Receives the actual stream index. 
            &flags,                         // Receives status flags.
            &llTimeStamp,                   // Receives the time stamp.
            &pSample                        // Receives the sample or NULL.
            );

        if (FAILED(hr))
        {
            break;
        }

        wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
        if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            wprintf(L"\tEnd of stream\n");
            quit = true;
        }
        if (flags & MF_SOURCE_READERF_NEWSTREAM)
        {
            wprintf(L"\tNew stream\n");
        }
        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            wprintf(L"\tNative type changed\n");
        }
        if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
        {
            wprintf(L"\tCurrent type changed\n");
        }
        if (flags & MF_SOURCE_READERF_STREAMTICK)
        {
            wprintf(L"\tStream tick\n");
        }

        if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
        {
            // The format changed. Reconfigure the decoder.
            hr = ConfigureDecoder(pReader, streamIndex);
            if (FAILED(hr))
            {
                break;
            }
        }

        if (pSample)
        {
            ++cSamples;
        }

        SafeRelease(&pSample);
    }

    if (FAILED(hr))
    {
        wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
    }
    else
    {
        wprintf(L"Processed %d samples\n", cSamples);
    }
    SafeRelease(&pSample);
    return hr;
}

Purga de la canalización de datos

Durante el procesamiento de datos, un descodificador u otra transformación podría almacenar en búfer ejemplos de entrada. En el diagrama siguiente, la aplicación llama a ReadSample y recibe un ejemplo con un tiempo de presentación igual a t1. El descodificador contiene muestras para t2 y t3.

Ilustración que muestra el almacenamiento en búfer en un descodificador.

En la siguiente llamada a ReadSample, el lector de origen podría proporcionar t4 al descodificador y devolver t2 a la aplicación.

Si desea descodificar todos los ejemplos que están almacenados en búfer actualmente en el descodificador, sin pasar ningún nuevo ejemplo al descodificador, establezca la marca MF_SOURCE_READER_CONTROLF_DRAIN en el parámetro dwControlFlags de ReadSample. Continúe haciendo esto en un bucle hasta que ReadSample devuelva un puntero de ejemplo NULL . Dependiendo de cómo se muestren los búferes de descodificador, que pueden producirse inmediatamente o después de varias llamadas a ReadSample.

Obtención de la duración del archivo

Para obtener la duración de un archivo multimedia, llame al método IMFSourceReader::GetPresentationAttribute y solicite el atributo MF_PD_DURATION , como se muestra en el código siguiente.

HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
    PROPVARIANT var;
    HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, 
        MF_PD_DURATION, &var);
    if (SUCCEEDED(hr))
    {
        hr = PropVariantToInt64(var, phnsDuration);
        PropVariantClear(&var);
    }
    return hr;
}

La función que se muestra aquí obtiene la duración en unidades de 100 nanosegundos. Divida entre 10 000 000 para obtener la duración en segundos.

Buscando

Un origen multimedia que obtiene datos de un archivo local normalmente puede buscar posiciones arbitrarias en el archivo. Los dispositivos de captura, como las cámaras web, por lo general, no pueden buscar, ya que los datos están activos. Un origen que transmite datos a través de una red puede buscar, en función del protocolo de streaming de red.

Para averiguar si un origen multimedia puede buscar, llame a IMFSourceReader::GetPresentationAttribute y solicite el atributo MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS , como se muestra en el código siguiente:

HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
    ULONG flags = 0;

    PROPVARIANT var;
    PropVariantInit(&var);

    HRESULT hr = pReader->GetPresentationAttribute(
        MF_SOURCE_READER_MEDIASOURCE, 
        MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, 
        &var);

    if (SUCCEEDED(hr))
    {
        hr = PropVariantToUInt32(var, &flags);
    }
    if (SUCCEEDED(hr))
    {
        *pulFlags = flags;
    }

    PropVariantClear(&var);
    return hr;
}

Esta función obtiene un conjunto de marcas de funcionalidades del origen. Estas marcas se definen en la enumeración MFMEDIASOURCE_CHARACTERISTICS . Dos marcas están relacionadas con la búsqueda:

Marca Descripción
MFMEDIASOURCE_CAN_SEEK
El origen puede buscar.
MFMEDIASOURCE_HAS_SLOW_SEEK
La búsqueda puede tardar mucho tiempo en completarse. Por ejemplo, es posible que el origen tenga que descargar todo el archivo para poder buscarlo. (No hay criterios estrictos para que un origen devuelva esta marca).

 

El código siguiente prueba la marca MFMEDIASOURCE_CAN_SEEK .

BOOL SourceCanSeek(IMFSourceReader *pReader)
{
    BOOL bCanSeek = FALSE;
    ULONG flags;
    if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
    {
        bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
    }
    return bCanSeek;
}

Para buscar, llame al método IMFSourceReader::SetCurrentPosition , como se muestra en el código siguiente.

HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
    PROPVARIANT var;
    HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
    if (SUCCEEDED(hr))
    {
        hr = pReader->SetCurrentPosition(GUID_NULL, var);
        PropVariantClear(&var);
    }
    return hr;
}

El primer parámetro proporciona el formato de hora que se usa para especificar la posición de búsqueda. Todos los orígenes multimedia de Media Foundation son necesarios para admitir unidades de 100 nanosegundos, indicados por el valor GUID_NULL. El segundo parámetro es un PROPVARIANT que contiene la posición de búsqueda. Para unidades de tiempo de 100 nanosegundos, el tipo de datos es LONGLONG.

Tenga en cuenta que no todos los orígenes multimedia proporcionan búsquedas precisas de fotogramas. La precisión de la búsqueda depende de varios factores, como el intervalo de fotogramas clave, si el archivo multimedia contiene un índice y si los datos tienen una velocidad de bits constante o variable. Por lo tanto, después de buscar una posición en un archivo, no hay ninguna garantía de que la marca de tiempo en la siguiente muestra coincida exactamente con la posición solicitada. Por lo general, la posición real no será posterior a la posición solicitada, por lo que puede descartar muestras hasta llegar al punto deseado en la secuencia.

Velocidad de reproducción

Aunque puede establecer la velocidad de reproducción mediante el Lector de origen, normalmente no es muy útil, por los siguientes motivos:

  • El Lector de origen no admite la reproducción inversa, incluso si el origen multimedia lo hace.
  • La aplicación controla los tiempos de presentación, por lo que la aplicación puede implementar una reproducción rápida o lenta sin establecer la velocidad en el origen.
  • Algunos orígenes multimedia admiten el modo de fino , donde el origen entrega menos muestras, normalmente solo los fotogramas clave. Sin embargo, si desea quitar fotogramas no clave, puede comprobar cada ejemplo para el atributo MFSampleExtension_CleanPoint .

Para establecer la velocidad de reproducción mediante el Lector de origen, llame al método IMFSourceReader::GetServiceForStream para obtener las interfaces IMFRateSupport y IMFRateControl del origen multimedia.

Aceleración de hardware

El lector de origen es compatible con microsoft DirectX Video Acceleration (DXVA) 2.0 para la descodificación de vídeo acelerada por hardware. Para usar DXVA con el Lector de origen, realice los pasos siguientes.

  1. Cree un dispositivo Microsoft Direct3D.
  2. Llame a la función DXVA2CreateDirect3DDeviceManager9 para crear el administrador de dispositivos Direct3D. Esta función obtiene un puntero a la interfaz IDirect3DDeviceManager9 .
  3. Llama al método IDirect3DDeviceManager9::ResetDevice con un puntero al dispositivo Direct3D.
  4. Cree un almacén de atributos llamando a la función MFCreateAttributes .
  5. Cree el Lector de origen. Pase el almacén de atributos en el parámetro pAttributes de la función de creación.

Al proporcionar un dispositivo Direct3D, el Lector de origen asigna muestras de vídeo que son compatibles con la API de procesador de vídeo DXVA. Puedes usar el procesamiento de vídeo DXVA para realizar la desinterlacación de hardware o la mezcla de vídeo. Para obtener más información, consulta Procesamiento de vídeo DXVA. Además, si el descodificador admite DXVA 2.0, usará el dispositivo Direct3D para realizar la descodificación acelerada por hardware.

Importante

A partir de Windows 8, IMFDXGIDeviceManager se puede usar en lugar de IDirect3DDeviceManager9. Para las aplicaciones de la Tienda Windows, debes usar IMFDXGIDeviceManager. Para obtener más información, consulta las API de vídeo de Direct3D 11.

 

Lector de origen