Introducción a Media Foundation - Cómo entender la API desde la perspectiva de COM

· Actualizado el: · · Media Foundation, COM, C++, Desarrollo en Windows

Al empezar a usar Media Foundation es fácil sentir que «se supone que estoy usando una API de video o audio de Windows, pero de repente aparece mucho COM». Términos como CoInitializeEx, MFStartup, IMFSourceReader, IMFMediaType, IMFTransform, IMFActivate, HRESULT y GUID surgen todos a la vez, el ambiente se vuelve de golpe muy Win32/COM, y termina siendo difícil ver qué es realmente Media Foundation.

En este artículo no pretendemos cubrir Media Foundation como un diccionario completo, sino centrarnos en estos tres puntos:

  • Por qué al usar Media Foundation surge de forma natural el tema de COM
  • En qué puntos se intensifica el carácter COM
  • Por dónde conviene empezar: Source Reader, Sink Writer, Media Session o MFT

Los ejemplos de código están en C++, pero la forma de pensar es prácticamente la misma cuando se accede a través de un wrapper desde .NET u otros lenguajes.

Índice

  1. Primero, la conclusión (en una frase)
  2. Terminología y panorama general
    • 2.1. Términos que conviene fijar primero
    • 2.2. Panorama general de Media Foundation (diagrama)
  3. Los puntos donde Media Foundation muestra su cara COM
    • 3.1. En la inicialización aparecen juntos CoInitializeEx y MFStartup
    • 3.2. El intercambio de objetos gira en torno a interfaces
    • 3.3. La configuración y la información de tipo giran en torno a IMFAttributes y GUID
    • 3.4. Aparece el Activation Object
    • 3.5. El manejo de asincronía, callbacks y subprocesos también es de estilo COM
  4. Sin embargo, Media Foundation no es igual a COM
  5. Por dónde empezar (cómo elegir el punto de entrada)
    • 5.1. Casos en los que conviene empezar por Source Reader
    • 5.2. Si quiere escribir a un archivo, use Sink Writer
    • 5.3. Si necesita reproducción y sincronización, use Media Session
    • 5.4. Si quiere insertar componentes propios, use MFT
  6. Lista de verificación para el trabajo diario
  7. Resumen
  8. Referencias

1. Primero, la conclusión (en una frase)

  • Media Foundation es una plataforma para trabajar con video y audio; no significa que toda la API sea, tal cual, COM puro.
  • Sin embargo, los límites entre source, transform, sink, activation, attributes y callback se expresan mediante interfaces COM, por lo que al usarla surgen de forma natural IUnknown, HRESULT, GUID y el concepto de apartment.
  • Conviene empezar por Source Reader/Sink Writer y avanzar hacia Media Session cuando se necesite control de reproducción, o hacia MFT cuando haga falta un convertidor propio; así resulta más fácil organizar las ideas.

En resumen: Media Foundation es una plataforma de procesamiento multimedia, y COM está profundamente integrado en su superficie de contacto.

Si fija esta idea desde el principio, entender «por qué de repente aparece la cara de COM» resulta mucho más claro.

2. Terminología y panorama general

Antes de entrar en el tema de COM, repasemos primero la terminología que usaremos en este artículo y el panorama general de Media Foundation.

2.1. Términos que conviene fijar primero

Término Significado en este artículo
Media Source Punto de entrada que introduce los datos multimedia en el pipeline: archivos, red, dispositivos de captura, etc.
MFT Media Foundation Transform. Modelo común para decodificadores, codificadores, convertidores de video, etc.
Media Sink Destino de los datos multimedia: presentación en pantalla, salida de audio, escritura a archivo, etc.
Media Session Mecanismo que administra el flujo de todo el pipeline. Se encarga de la reproducción y la sincronización.
Topology Diagrama de conexión que representa cómo se enlazan source, transform y sink.
Activation Object Objeto auxiliar para crear la instancia real más adelante. Se representa mediante IMFActivate.
Attributes Almacén de clave/valor cuya clave es un GUID. Se usa mucho en toda la plataforma Media Foundation.
apartment Unidad con la que COM agrupa subprocesos. Es la regla que determina «desde qué subproceso se puede llamar a este objeto» y se fija mediante el argumento de CoInitializeEx (secciones 3.1 y 3.5)
STA / MTA Tipos de apartment. STA (Single-Threaded Apartment) está ligado a un único subproceso, y las llamadas de otros subprocesos se traen a través del message pump. MTA (Multi-Threaded Apartment) permite que varios subprocesos compartan el mismo apartment y llamen directamente. Más detalles en Conceptos básicos para evitar cuelgues con STA/MTA en COM
work queue Mecanismo de subprocesos que Media Foundation usa para ejecutar el procesamiento asíncrono. Los callbacks se invocan desde estos subprocesos (sección 3.5)

Si maneja estos términos de antemano, se reducen bastante los tropiezos al leer la documentación.

El tema de apartment se tratará a fondo en 3.5, pero por ahora basta con recordar que «el callback de Media Foundation puede llegar desde un subproceso distinto de aquel en el que se llamó a ReadSample (la work queue de MTA)».

2.2. Panorama general de Media Foundation (diagrama)

En términos generales, Media Foundation trata sobre el pipeline multimedia. El tema de COM es importante, pero conviene ver primero el panorama general para organizar mejor las ideas.

Dos modelos de uso de Media FoundationDiagrama que compara el modelo que usa todo el pipeline, donde Media Session conecta Media Source, MFT y Media Sink, con el modelo en el que la aplicación maneja los datos directamente mediante Source Reader y Sink WriterModelo en el que la aplicación maneja los datos directamenteSource Reader (+ decoder)Media SourceAplicaciónSink Writer (+ encoder)Media SinkModelo que usa todo el pipelineMFTMedia SourceMedia SinkMedia Session

Media Foundation admite, a grandes rasgos, estas dos formas de uso:

  • Modelo que usa todo el pipeline
    • Conecta source, transform y sink, y Media Session administra el flujo de datos y la sincronización A/V.
  • Modelo en el que la aplicación maneja los datos directamente
    • Source Reader extrae los datos del source y Sink Writer los envía al sink.

El segundo modelo resulta más fácil de adoptar cuando se quiere procesar los frames o samples por cuenta propia. Por el contrario, si se prefiere dejar en manos de la plataforma incluso la reproducción y la sincronización, el primer modelo es el camino principal.

Lo importante es recordar que la verdadera naturaleza de Media Foundation es la de una plataforma de procesamiento multimedia, algo distinto de la sensación de manejar directamente un conjunto de objetos COM sueltos.

Sin embargo, en cuanto se empieza a mirar los límites entre esas piezas, la cara de COM se intensifica de golpe. En el siguiente capítulo repasaremos esos puntos uno por uno.

3. Los puntos donde Media Foundation muestra su cara COM

Los puntos donde el carácter COM se intensifica se pueden organizar, a grandes rasgos, en estos cinco:

Punto Qué aparece Lo primero que conviene entender
3.1. Inicialización CoInitializeEx, MFStartup La inicialización de COM y la de Media Foundation son distintas
3.2. Creación y paso de objetos IMFSourceReader, IMFMediaType, IMFTransform La mayoría son punteros a interfaz + HRESULT
3.3. Configuración IMFAttributes, GUID Los valores de configuración y la información de tipo se expresan como key/value + GUID
3.4. Enumeración y creación diferida IMFActivate, ActivateObject El resultado de la enumeración a veces no es directamente la instancia real
3.5. Asincronía IMFSourceReaderCallback, work queue Hay que tener presentes el callback y el apartment
(Capítulo 4) Control de reproducción topology, Media Session El flujo de todo el pipeline es un concepto propio de Media Foundation

Solo el último punto, el control de reproducción, tiene un carácter distinto: no es una cuestión general de COM, sino una función propia de Media Foundation. Por eso se trata aparte en el capítulo 4.

A continuación los repasamos en orden. El código no son ejemplos completos, sino solo fragmentos suficientes para ver dónde aparece la cara de COM.

3.1. En la inicialización aparecen juntos CoInitializeEx y MFStartup

Este es el primer punto donde a mucha gente le resulta extraño. Antes de llegar a abrir un archivo o capturar desde una cámara, aparecen primero CoInitializeEx y MFStartup.

  • CoInitializeEx inicializa la biblioteca COM
  • MFStartup inicializa la plataforma Media Foundation

Es decir, la inicialización de COM por sí sola no basta: también se necesita la inicialización propia de Media Foundation. Aquí uno empieza a comprender que «esto no es solo una API de video, sino que por debajo hay bastante contrato basado en COM».

template <class T>
void SafeRelease(T** pp)
{
    if (pp != nullptr && *pp != nullptr)
    {
        (*pp)->Release();
        *pp = nullptr;
    }
}

HRESULT InitializeMediaFoundationForCurrentThread()
{
    HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    if (FAILED(hr))
    {
        return hr;
    }

    hr = MFStartup(MF_VERSION);
    if (FAILED(hr))
    {
        CoUninitialize();
        return hr;
    }

    return S_OK;
}

void UninitializeMediaFoundationForCurrentThread()
{
    MFShutdown();
    CoUninitialize();
}

Esta forma en la que aparecen juntos CoInitializeEx y MFStartup es el primer punto donde, al trabajar con Media Foundation, el ambiente COM se intensifica de repente.

En la práctica, conviene decidir lo siguiente en este momento para que después sea más sencillo:

  • Qué subproceso usará Media Foundation
  • Si ese subproceso será STA o MTA
  • Quién es responsable de MFStartup/MFShutdown y de CoInitializeEx/CoUninitialize

En la implementación real, a veces otra capa ya se encarga de la inicialización de COM. Incluso en ese caso, es más seguro fijar de antemano quién tiene esa responsabilidad. Si se avanza dejando este diseño ambiguo, más adelante resulta difícil de entender en la parte de los callbacks y la integración con la interfaz de usuario.

Cabe señalar que en el código de este artículo escribimos SafeRelease a mano para administrar punteros a interfaz sin envolver, porque los ejemplos de la documentación de Microsoft están escritos así y permite ver dónde actúan AddRef/Release. Sin embargo, esto no es motivo para prescindir de punteros inteligentes en código de producción. Si va a escribir código nuevo en C++, es más seguro inclinarse por una de estas dos opciones:

Opción Implementación Notas
Microsoft::WRL::ComPtr<T> <wrl/client.h> Viene incluido en el Windows SDK, sin dependencias adicionales. Se usa Get() para el puntero sin envolver, GetAddressOf()/& para argumentos de salida (out) y As<U>() para expresar QueryInterface
wil::com_ptr<T> wil/com.h de WIL (Windows Implementation Libraries) Se agrega por separado, por ejemplo mediante NuGet. Puede usarse junto con helpers que convierten HRESULT en excepciones

Si se escribe con ComPtr, ya no hace falta la combinación de goto done; y SafeRelease que aparece más adelante en esta misma sección, porque Release se invoca automáticamente al salir del ámbito (scope). En este artículo mantenemos los punteros sin envolver porque priorizamos que se vea el modo de trabajar de COM, pero recomendamos que el código nuevo empiece directamente con ComPtr.

3.2. El intercambio de objetos gira en torno a interfaces

Al leer la API de Media Foundation, se observa que muchos valores de retorno y argumentos de salida son interfaces COM.

  • IMFSourceReader
  • IMFMediaType
  • IMFTransform
  • IMFActivate
  • IMFSample
  • IMFMediaBuffer

Lo característico es que no solo los datos en sí, sino también la información de tipo y los objetos de configuración, se representan mediante interfaces.

Por ejemplo:

  • IMFTransform es la interfaz que representa un MFT
  • IMFAttributes es un almacén de clave/valor
  • IMFMediaType es una «descripción del formato multimedia» que hereda de IMFAttributes

Incluso algo como el media type, que parece más bien «datos de configuración», se mantiene mediante una interfaz COM. Aquí es donde entran de forma natural IUnknown, QueryInterface, AddRef/Release y HRESULT.

Jerarquía de interfaces COM en Media FoundationDiagrama que muestra cómo IUnknown es la base de IMFAttributes, IMFSourceReader e IMFTransform, y cómo IMFMediaType e IMFActivate heredan a su vez de IMFAttributesIUnknownIMFAttributesIMFMediaTypeIMFActivateIMFSourceReaderIMFTransform

Llegados a este punto, empieza a verse que «Media Foundation es una API multimedia, pero la forma de expresar sus límites es bastante COM».

3.3. La configuración y la información de tipo giran en torno a IMFAttributes y GUID

Al trabajar con Media Foundation hay un punto en el que la configuración de repente parece estar llena de GUID por todas partes. El centro de esto es IMFAttributes, un almacén de clave/valor cuya clave es un GUID. Se usa muchísimo en toda la plataforma Media Foundation.

Lo especialmente importante es IMFMediaType, que hereda de IMFAttributes y mantiene la información del formato multimedia como atributos.

Por ejemplo, información como esta:

  • major type (si es audio o video)
  • subtype (H.264, AAC, RGB32, PCM, etc.)
  • Tamaño de frame
  • Frame rate
  • Sample rate
  • Número de canales
Atributos del media typeDiagrama que muestra que IMFMediaType expone como atributos MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE y otros detalles como tamaño, FPS o sample rateIMFMediaTypeMF_MT_MAJOR_TYPEMF_MT_SUBTYPETamaño / FPS / sample rate, etc.

Es fácil sentir que esto es «un bosque de GUID», pero en realidad lo que se hace es bastante directo.

  • Se mantiene la configuración mediante un almacén de atributos
  • El media type también se representa como un almacén de atributos
  • Entre source, transform y sink se ajusta el formato observando esos atributos

En pocas palabras: se usan interfaces de estilo COM y GUID para representar la configuración y la información de tipo.

Visto en código, esto queda de la siguiente manera. Es un ejemplo que simplemente lee 1 frame de un video con Source Reader.

HRESULT ReadOneVideoSample(PCWSTR path)
{
    IMFSourceReader* pReader = nullptr;
    IMFMediaType* pType = nullptr;
    IMFSample* pSample = nullptr;

    HRESULT hr = MFCreateSourceReaderFromURL(path, nullptr, &pReader);
    if (FAILED(hr)) goto done;

    hr = MFCreateMediaType(&pType);
    if (FAILED(hr)) goto done;

    hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
    if (FAILED(hr)) goto done;

    hr = pType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
    if (FAILED(hr)) goto done;

    hr = pReader->SetCurrentMediaType(
        MF_SOURCE_READER_FIRST_VIDEO_STREAM,
        nullptr,
        pType);
    if (FAILED(hr)) goto done;

    DWORD streamFlags = 0;
    LONGLONG timestamp = 0;

    hr = pReader->ReadSample(
        MF_SOURCE_READER_FIRST_VIDEO_STREAM,
        0,
        nullptr,
        &streamFlags,
        &timestamp,
        &pSample);
    if (FAILED(hr)) goto done;

    // Extraer IMFMediaBuffer de pSample y procesarlo

done:
    SafeRelease(&pSample);
    SafeRelease(&pType);
    SafeRelease(&pReader);
    return hr;
}

Aquí se observan los siguientes puntos:

  • Tanto el reader como el media type son interfaces COM
  • La configuración se basa en GUID
  • El valor de retorno es HRESULT
  • En modo síncrono, ReadSample bloquea

Incluso algo tan simple como «solo quiero leer 1 frame» muestra, en los límites de Media Foundation, una cara bastante propia de COM. El último punto, sobre el modo síncrono, se trata en 3.5.

Procedimiento de media type negotiation (uno de los «tres puntos que conviene revisar primero» de la lista de verificación del capítulo 6)

El código anterior solo declara «lo quiero en RGB32», así que en la práctica hacen falta pasos antes y después de eso. En el caso de Source Reader, el flujo que indica la documentación de Microsoft consta de estos 4 pasos:

  1. Enumerar los tipos nativos — Llame a IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType) incrementando typeIndex desde 0. Cuando se supera el rango se devuelve MF_E_NO_MORE_TYPES, lo cual marca el final de la enumeración (si streamIndex está fuera de rango, se devuelve MF_E_INVALIDSTREAMNUMBER). En un archivo suele haber un solo tipo por stream, pero una cámara web puede tener varios formatos
  2. Comprobar el major type — Lea MF_MT_MAJOR_TYPE del media type obtenido en la enumeración para determinar si es audio o video. Si se avanza con un valor fijo sin comprobar esto, se corre el riesgo de aplicar configuración de video a un stream de audio
  3. Construir y establecer el formato de salida deseado — Cree un nuevo media type con MFCreateMediaType, establezca MF_MT_MAJOR_TYPE y MF_MT_SUBTYPE, y llame a SetCurrentMediaType. Si quiere recibir los datos aún comprimidos, pase directamente el tipo obtenido en el paso 1; si quiere que se decodifiquen, indique un formato sin comprimir (MFVideoFormat_RGB32, MFAudioFormat_PCM, etc.). Source Reader carga el decodificador automáticamente
  4. Volver a leer el formato ya confirmado — Después de SetCurrentMediaType, llame a GetCurrentMediaType para obtener los detalles del formato realmente confirmado (tamaño de frame, stride, sample rate, etc.). Como en el paso 3 solo se pasa una especificación parcial, el orden correcto es leer los valores confirmados aquí

Si se saltan estos 4 pasos y se avanza suponiendo «probablemente sea este formato», se obtiene MF_E_INVALIDMEDIATYPE, o incluso si la llamada tiene éxito, se termina leyendo un buffer con un formato distinto del esperado.

3.4. Aparece el Activation Object

El activation object es donde el carácter COM de Media Foundation se manifiesta especialmente.

IMFActivate es un objeto auxiliar para crear la instancia real más adelante. Resulta fácil de entender si se ve, en sensación, como algo cercano a una class factory de COM.

En las situaciones donde aparece esto, el valor devuelto por la API de enumeración a veces no es «la instancia lista para usar», sino primero un arreglo de IMFActivate*. Después, solo lo que se necesita se instancia mediante ActivateObject.

Flujo de enumeración y activación con IMFActivateDiagrama de secuencia que muestra cómo la aplicación llama a la API de enumeración, recibe un arreglo de IMFActivate, consulta sus atributos y llama a ActivateObject para obtener el objeto COM real, como IMFTransform o un sinkIMFTransform / Sink, etc.IMFActivateAPI de enumeraciónAplicaciónIMFTransform / Sink, etc.IMFActivateAPI de enumeraciónAplicaciónLlama a la enumeraciónArreglo de IMFActivate*Consulta los atributosActivateObject(...)Objeto COM real

Esta forma encaja bien con el hecho de que Media Foundation está diseñado para encontrar y combinar más tarde componentes intercambiables.

Además, el propio activation object puede tener attributes, por lo que resulta natural el flujo de «primero revisar los atributos del candidato», «configurar si hace falta» y «instanciar más adelante». Esto también es bastante propio de COM.

Si en la práctica se enumera e instancia un MFT con MFTEnumEx, queda de la siguiente manera.

HRESULT FindH264Decoder(IMFTransform** ppTransform)
{
    *ppTransform = nullptr;

    IMFActivate** ppActivate = nullptr;
    UINT32 count = 0;

    MFT_REGISTER_TYPE_INFO inputType = {};
    inputType.guidMajorType = MFMediaType_Video;
    inputType.guidSubtype = MFVideoFormat_H264;

    HRESULT hr = MFTEnumEx(
        MFT_CATEGORY_VIDEO_DECODER,
        MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_LOCALMFT,
        &inputType,
        nullptr,
        &ppActivate,
        &count);
    if (FAILED(hr))
    {
        return hr;
    }

    if (count == 0)
    {
        CoTaskMemFree(ppActivate);
        return MF_E_TOPO_CODEC_NOT_FOUND;
    }

    hr = ppActivate[0]->ActivateObject(
        __uuidof(IMFTransform),
        reinterpret_cast<void**>(ppTransform));

    for (UINT32 i = 0; i < count; ++i)
    {
        ppActivate[i]->Release();
    }
    CoTaskMemFree(ppActivate);

    return hr;
}

El resultado de la enumeración no llega desde el principio como IMFTransform*, sino como IMFActivate**, y solo al llamar a ActivateObject se obtiene finalmente la instancia real de IMFTransform. Este flujo representa bastante bien la sensación de que Media Foundation «de repente muestra su cara COM».

3.5. El manejo de asincronía, callbacks y subprocesos también es de estilo COM

En el trabajo práctico con Media Foundation, lo que se pasa por alto con facilidad es el procesamiento asíncrono y el modelo de subprocesos.

Por ejemplo, Source Reader está en modo síncrono de forma predeterminada. En modo síncrono, ReadSample bloquea. Dependiendo del estado del archivo, la red o el dispositivo, esa espera puede llegar a ser un tiempo perceptible.

Si se quiere el modo asíncrono, se pasa un callback al crear el Source Reader. El flujo consiste en preparar un objeto que implemente IMFSourceReaderCallback, asignarlo al atributo MF_SOURCE_READER_ASYNC_CALLBACK y, después, crear el Source Reader.

HRESULT CreateSourceReaderAsync(
    PCWSTR path,
    IMFSourceReaderCallback* pCallback,
    IMFSourceReader** ppReader)
{
    IMFAttributes* pAttributes = nullptr;

    HRESULT hr = MFCreateAttributes(&pAttributes, 1);
    if (FAILED(hr))
    {
        return hr;
    }

    hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
    if (SUCCEEDED(hr))
    {
        hr = MFCreateSourceReaderFromURL(path, pAttributes, ppReader);
    }

    SafeRelease(&pAttributes);
    return hr;
}

En otras palabras:

  • El propio callback es una interfaz COM
  • La configuración asíncrona se hace a través de IMFAttributes
  • El modo se decide al crear el objeto

Esa es la forma en que funciona.

Un aspecto algo más importante es el apartment. El procesamiento asíncrono de Media Foundation usa una work queue, y el subproceso de la work queue es MTA. Por eso, si la aplicación también se maneja en MTA, la implementación resulta más simple.

Llamada asíncrona a través de la work queue de Media FoundationDiagrama de secuencia que muestra cómo ReadSample retorna de inmediato al subproceso de la aplicación mientras la work queue de MTA procesa internamente y luego invoca OnReadSample en el callbackIMFSourceReaderCallbackwork queue de MF (MTA)Source ReaderSubproceso de la aplicaciónIMFSourceReaderCallbackwork queue de MF (MTA)Source ReaderSubproceso de la aplicaciónReadSample(...)Retorna de inmediatoProcesa internamenteOnReadSample(...)

En torno al callback, conviene tener en cuenta estos puntos:

  • No tocar directamente, desde el callback, los objetos STA del subproceso de la interfaz de usuario
  • Hacer que la implementación del callback sea segura para subprocesos (thread-safe)
  • Si hace falta actualizar la interfaz de usuario, devolver solo el resultado al subproceso de la interfaz de usuario
  • Fijar desde el principio de qué subproceso proviene el callback de Media Foundation

Media Foundation no absorbe por sí solo las particularidades de los objetos STA. Por eso resulta más ordenado orientar hacia MTA al worker que usa Media Foundation y tender explícitamente el puente hacia la interfaz de usuario.

4. Sin embargo, Media Foundation no es igual a COM

Después de leer hasta aquí, es fácil pensar que «al final, Media Foundation es lo mismo que COM». Pero eso no es del todo exacto.

Media Foundation tiene conceptos propios de la plataforma que no se explican solo con la teoría general de COM.

  • MFStartup / MFShutdown
  • Media Session
  • topology
  • topology loader
  • presentation clock
  • Source Reader / Sink Writer

Todo esto forma parte del propio rol de Media Foundation: cómo hacer fluir el pipeline multimedia.

Por ejemplo, en Media Session existe un flujo en el que, cuando la aplicación entrega una partial topology, el topology loader completa los transform necesarios y la resuelve en una full topology. Esto no es tanto una cuestión general de COM, sino una función que Media Foundation posee como plataforma de procesamiento multimedia.

Resolución de partial topology a full topologyDiagrama que muestra cómo el Topology Loader recibe una Partial Topology con Source conectado directamente a Output y la resuelve en una Full Topology que inserta el Decoder MFT necesario entre Source y OutputPartial TopologySource -&gt; OutputTopology LoaderFull TopologySource -&gt; Decoder MFT -&gt; Output

Media Foundation es algo que usa COM para expresar el contrato entre sus componentes y, sobre esa base, funciona como una plataforma de procesamiento multimedia. Si se lo observa con esta estructura de dos niveles, es menos probable perderse.

5. Por dónde empezar (cómo elegir el punto de entrada)

Para decidir el primer punto de entrada, en muchos casos basta con el siguiente diagrama.

Cómo elegir el punto de entrada en Media FoundationÁrbol de decisión que, según lo que se necesite hacer primero, lleva a Source Reader para leer frames o samples, a Sink Writer para escribir a un archivo, a Media Session para control de reproducción y sincronización A/V, o a MFT para insertar un convertidor propioLeer frames / samplesEscribir a un archivoControl de reproducción o sincronización A/VInsertar un convertidor propioLo que se quiere hacer¿Qué se necesita primero?Source ReaderSink WriterMedia SessionMFT

En forma de tabla queda así:

Lo que se quiere hacer Por dónde empezar Intensidad de COM Notas
Extraer frames/samples de un archivo o una cámara Source Reader Media Si hace falta, también se encarga del decoder
Escribir el audio/video generado a un archivo Sink Writer Media Si hace falta, puede manejar juntos el encoder y el media sink
Manejar reproducción, detención, búsqueda, sincronización A/V y control de calidad Media Session Alta Requiere entender topology y session
Insertar un convertidor propio o un componente de tipo codec MFT Alta Pensar en torno a IMFTransform
Revisar los candidatos enumerados y luego instanciar solo lo necesario IMFActivate Alta A veces lo que se devuelve no es la instancia real sino el activation object

5.1. Casos en los que conviene empezar por Source Reader

Source Reader resulta bastante fácil de usar como punto de entrada cuando se quiere extraer datos de un archivo o un dispositivo.

Es adecuado, por ejemplo, en casos como estos:

  • Extraer frames de un archivo de video
  • Decodificar un archivo de audio y obtener samples
  • Extraer frames desde una cámara
  • Conectar un source de Media Foundation al propio pipeline de procesamiento

Source Reader carga el decoder según haga falta y entrega los datos a la aplicación. Por otro lado, no se encarga de la administración del presentation clock, de la sincronización A/V ni del renderizado en pantalla.

Resulta fácil de entender si se piensa como un punto de entrada para «obtener datos», no para «reproducir».

5.2. Si quiere escribir a un archivo, use Sink Writer

Sink Writer es el punto de entrada cuando se quiere escribir audio o video a un archivo.

Los usos típicos son estos:

  • Guardar los frames generados en un archivo de video
  • Codificar samples de audio y escribirlos
  • Convertir los datos leídos a otro formato y guardarlos

Sink Writer busca y carga el encoder según haga falta, y administra el flujo de datos hacia el media sink. Se combina a menudo con Source Reader, pero como ambos son componentes independientes, no es obligatorio usarlos siempre juntos.

5.3. Si necesita reproducción y sincronización, use Media Session

Si lo que se busca no es «extraer datos de un archivo» sino reproducir correctamente, es más directo pensar en torno a Media Session.

Media Session entra en juego cuando existen requisitos como estos:

  • Manejar reproducción/detención/búsqueda
  • Dejar la sincronización de audio y video en manos de la plataforma
  • Manejar el pipeline incluyendo el control de calidad y los cambios de formato
  • Construir el flujo de source, transform y sink mediante topology

Al entrar en esta capa, uno se acerca más al «núcleo de Media Foundation» que con Source Reader/Sink Writer. En consecuencia, también aumentan los conceptos propios de Media Foundation, como topology y session event.

5.4. Si quiere insertar componentes propios, use MFT

MFT es el modelo común de transform de Media Foundation.

Se entra en este terreno en situaciones como estas:

  • Crear un decodificador o codificador propio
  • Insertar en el pipeline un componente de procesamiento de video o audio
  • Enumerar codecs o convertidores y elegirlos por cuenta propia
  • Controlar con más profundidad que la resolución automática predeterminada

En el mundo de MFT, el contrato de estilo COM queda muy en primer plano: IMFTransform, IMFActivate, media type negotiation, administración de samples y buffers, etc. Por eso, en lugar de entrar directamente a MFT como primer punto, resulta más claro comprobar antes cuál de Source Reader, Sink Writer o Media Session es realmente necesario.

6. Lista de verificación para el trabajo diario

Por último, resumimos en una sola tabla los puntos que conviene revisar primero en el trabajo práctico.

Elemento Qué revisar Qué suele ocurrir si se pasa por alto
Responsabilidad de inicialización Decidir dónde se llama a CoInitializeEx y a MFStartup, y dónde se ubica el procesamiento de cierre Inicialización omitida, confusión en el orden de cierre
apartment Decidir de antemano si el subproceso que usa MF será STA o MTA Confusión en torno al callback, conflictos con la interfaz de usuario
Modo de Source Reader Decidir en el momento de la creación si es síncrono o asíncrono ReadSample bloquea de forma inesperada; no se puede cambiar después
media type negotiation Enumerar los formatos de salida y especificar explícitamente el que realmente se va a usar. El procedimiento son los 4 pasos de 3.3 (enumerar con GetNativeMediaType → comprobar el major type → SetCurrentMediaType → leer el valor confirmado con GetCurrentMediaType) MF_E_INVALIDMEDIATYPE, llega un formato distinto del esperado
Ciclo de vida del objeto Aclarar la responsabilidad de Release, Unlock y ShutdownObject Fugas de memoria, buffers retenidos, inconsistencias al cerrar
activation object Distinguir si el resultado de la enumeración es la instancia real o un IMFActivate Fallar al suponer que se puede llamar a QueryInterface
topology Entender si se está trabajando con una partial topology o una full topology Quedar atascado al suponer que «se conectará automáticamente»
Verificación de errores Revisar siempre HRESULT, los stream flags y los eventos Pasar por alto que solo una parte ha fallado
Integración con la interfaz de usuario No tocar la interfaz de usuario directamente desde el callback; devolver solo el resultado al subproceso de la interfaz de usuario Cuelgues, condiciones de carrera, fallos difíciles de entender

Los siguientes tres puntos tienen especial prioridad:

  1. No equivocarse en la API de entrada inicial
    • Determinar primero cuál de Source Reader, Sink Writer o Media Session es realmente necesario
  2. Decidir el apartment desde el principio
    • Si se va a combinar la interfaz de usuario en STA con la work queue de Media Foundation, decidir de antemano cómo tender el puente
  3. No tratar el media type negotiation de forma descuidada
    • Avanzar suponiendo «probablemente sea este formato» genera después bastante confusión
    • El procedimiento concreto está resumido en «Procedimiento de media type negotiation» de la sección 3.3

7. Resumen

Que al trabajar con Media Foundation el tema de COM aumente de repente no es una casualidad.

  • Media Foundation es una plataforma de procesamiento multimedia
  • Sus límites entre source, transform, sink, activation, callback, etc., se expresan mediante interfaces COM
  • Por eso surgen de forma natural los temas de IUnknown, HRESULT, GUID, apartment y callback
  • Sin embargo, el núcleo de Media Foundation es un pipeline multimedia con Media Session y topology, no un simple reciclaje de COM

En la práctica, pensar en el siguiente orden facilita bastante organizar las ideas:

  1. Determinar antes que nada cuál de Source Reader, Sink Writer, Media Session o MFT es necesario
  2. Decidir de antemano la política de apartment y de callback
  3. Manejar con cuidado el media type negotiation y el ciclo de vida de los objetos

No hace falta entenderlo todo desde el principio. Si primero se tiene presente que «Media Foundation es una plataforma de procesamiento multimedia y COM está profundamente integrado en su superficie de contacto», resulta mucho más fácil seguir tanto la documentación como el código.

8. Referencias

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Qué es Media Foundation? ¿Es diferente de COM?
Media Foundation es la plataforma de Windows para el procesamiento de video y audio, y no significa que toda la API sea COM puro en sí mismo. Sin embargo, los límites entre las piezas que la componen —source, transform, sink, activation, attributes, callback— se expresan mediante interfaces COM, por lo que al usarla surgen de forma natural conceptos como IUnknown, HRESULT, GUID y apartment. Lo más preciso es entenderla como «una plataforma de procesamiento multimedia en cuya superficie de contacto COM está profundamente integrado».
¿Por qué son necesarios tanto MFStartup como CoInitializeEx?
Porque cumplen funciones distintas. CoInitializeEx inicializa la biblioteca COM, mientras que MFStartup inicializa la plataforma Media Foundation. La inicialización de COM por sí sola no basta: también se necesita la inicialización propia de Media Foundation. En la práctica conviene decidir de antemano qué subproceso usará Media Foundation, si se trabajará en STA o en MTA, y quién es responsable de MFStartup/MFShutdown y de CoInitializeEx/CoUninitialize; esto facilita después el trabajo con callbacks y con la integración de la interfaz de usuario.
¿Cómo se elige entre Source Reader, Sink Writer, Media Session y MFT?
Si quiere extraer frames o samples de un archivo o una cámara, el punto de entrada es Source Reader; si quiere escribir el audio o el video generado a un archivo, es Sink Writer. Si quiere que la plataforma se encargue de reproducción, detención, búsqueda, sincronización A/V y control de calidad, conviene pensar en torno a Media Session. Si necesita insertar un decodificador o convertidor propio en el pipeline, se avanza hacia MFT, aunque ahí el contrato de tipo COM queda mucho más expuesto, por lo que primero conviene comprobar cuál de las tres opciones anteriores es realmente necesaria.
¿Qué hay que tener en cuenta con los callbacks asíncronos de Media Foundation?
El procesamiento asíncrono de Media Foundation utiliza una work queue cuyo subproceso es MTA, así que si la aplicación también se orienta a MTA, la implementación resulta más simple. La implementación de IMFSourceReaderCallback debe ser segura para subprocesos (thread-safe), y es importante no tocar directamente, desde el callback, los objetos STA del subproceso de interfaz de usuario. Si hace falta actualizar la interfaz de usuario, solo el resultado debe devolverse al subproceso de la interfaz de usuario. También hay que tener en cuenta que el modo síncrono o asíncrono de Source Reader se decide en el momento de crearlo y no puede cambiarse después.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog