Introducción a Media Foundation - Cómo entender la API desde la perspectiva de COM
· Actualizado el: · Go Komura · 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
- Primero, la conclusión (en una frase)
- Terminología y panorama general
- 2.1. Términos que conviene fijar primero
- 2.2. Panorama general de Media Foundation (diagrama)
- Los puntos donde Media Foundation muestra su cara COM
- 3.1. En la inicialización aparecen juntos
CoInitializeExyMFStartup - 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
IMFAttributesy GUID - 3.4. Aparece el Activation Object
- 3.5. El manejo de asincronía, callbacks y subprocesos también es de estilo COM
- 3.1. En la inicialización aparecen juntos
- Sin embargo, Media Foundation no es igual a COM
- 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
- Lista de verificación para el trabajo diario
- Resumen
- 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.
flowchart TB
accTitle: Dos modelos de uso de Media Foundation
accDescr: Diagrama 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 Writer
subgraph Pipeline["Modelo que usa todo el pipeline"]
Source1["Media Source"] --> Transform1["MFT"]
Transform1 --> Sink1["Media Sink"]
Session["Media Session"] --- Source1
Session --- Transform1
Session --- Sink1
end
subgraph Direct["Modelo en el que la aplicación maneja los datos directamente"]
Source2["Media Source"] --> Reader["Source Reader (+ decoder)"]
Reader --> App["Aplicación"]
App --> Writer["Sink Writer (+ encoder)"]
Writer --> Sink2["Media Sink"]
end
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.
CoInitializeExinicializa la biblioteca COMMFStartupinicializa 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/MFShutdowny deCoInitializeEx/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.
IMFSourceReaderIMFMediaTypeIMFTransformIMFActivateIMFSampleIMFMediaBuffer
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:
IMFTransformes la interfaz que representa un MFTIMFAttributeses un almacén de clave/valorIMFMediaTypees una «descripción del formato multimedia» que hereda deIMFAttributes
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.
flowchart TD
accTitle: Jerarquía de interfaces COM en Media Foundation
accDescr: Diagrama que muestra cómo IUnknown es la base de IMFAttributes, IMFSourceReader e IMFTransform, y cómo IMFMediaType e IMFActivate heredan a su vez de IMFAttributes
IUnknown["IUnknown"]
IUnknown --> IMFAttributes["IMFAttributes"]
IMFAttributes --> IMFMediaType["IMFMediaType"]
IMFAttributes --> IMFActivate["IMFActivate"]
IUnknown --> IMFSourceReader["IMFSourceReader"]
IUnknown --> IMFTransform["IMFTransform"]
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
flowchart LR
accTitle: Atributos del media type
accDescr: Diagrama que muestra que IMFMediaType expone como atributos MF_MT_MAJOR_TYPE, MF_MT_SUBTYPE y otros detalles como tamaño, FPS o sample rate
MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
MediaType --> Subtype["MF_MT_SUBTYPE"]
MediaType --> Detail["Tamañ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,
×tamp,
&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,
ReadSamplebloquea
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:
- Enumerar los tipos nativos — Llame a
IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType)incrementandotypeIndexdesde 0. Cuando se supera el rango se devuelveMF_E_NO_MORE_TYPES, lo cual marca el final de la enumeración (sistreamIndexestá fuera de rango, se devuelveMF_E_INVALIDSTREAMNUMBER). En un archivo suele haber un solo tipo por stream, pero una cámara web puede tener varios formatos - Comprobar el major type — Lea
MF_MT_MAJOR_TYPEdel 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 - Construir y establecer el formato de salida deseado — Cree un nuevo media type con
MFCreateMediaType, establezcaMF_MT_MAJOR_TYPEyMF_MT_SUBTYPE, y llame aSetCurrentMediaType. 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 - Volver a leer el formato ya confirmado — Después de
SetCurrentMediaType, llame aGetCurrentMediaTypepara 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.
sequenceDiagram
accTitle: Flujo de enumeración y activación con IMFActivate
accDescr: Diagrama 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 sink
participant App as Aplicación
participant Enum as API de enumeración
participant Act as IMFActivate
participant Obj as IMFTransform / Sink, etc.
App->>Enum: Llama a la enumeración
Enum-->>App: Arreglo de IMFActivate*
App->>Act: Consulta los atributos
App->>Act: ActivateObject(...)
Act-->>App: 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.
sequenceDiagram
accTitle: Llamada asíncrona a través de la work queue de Media Foundation
accDescr: Diagrama 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 callback
participant App as Subproceso de la aplicación
participant Reader as Source Reader
participant Queue as work queue de MF (MTA)
participant Cb as IMFSourceReaderCallback
App->>Reader: ReadSample(...)
Reader-->>App: Retorna de inmediato
Reader->>Queue: Procesa internamente
Queue->>Cb: OnReadSample(...)
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.
flowchart LR
accTitle: Resolución de partial topology a full topology
accDescr: Diagrama 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 Output
Partial["Partial Topology<br/>Source -> Output"] --> Loader["Topology Loader"]
Loader --> Full["Full Topology<br/>Source -> Decoder MFT -> 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.
flowchart TD
accTitle: Cómo elegir el punto de entrada en Media Foundation
accDescr: Á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 propio
Start["Lo que se quiere hacer"] --> Q1{"¿Qué se necesita primero?"}
Q1 -- "Leer frames / samples" --> A1["Source Reader"]
Q1 -- "Escribir a un archivo" --> A2["Sink Writer"]
Q1 -- "Control de reproducción o sincronización A/V" --> A3["Media Session"]
Q1 -- "Insertar un convertidor propio" --> A4["MFT"]
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:
- No equivocarse en la API de entrada inicial
- Determinar primero cuál de Source Reader, Sink Writer o Media Session es realmente necesario
- 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
- 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:
- Determinar antes que nada cuál de Source Reader, Sink Writer, Media Session o MFT es necesario
- Decidir de antemano la política de apartment y de callback
- 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
- Media Foundation and COM - Microsoft Learn
- Overview of the Media Foundation Architecture - Microsoft Learn
- Initializing Media Foundation - Microsoft Learn
- Source Reader - Microsoft Learn
- Using the Source Reader to Process Media Data - Microsoft Learn
- Using the Source Reader in Asynchronous Mode - Microsoft Learn
- Sink Writer - Microsoft Learn
- Activation Objects - Microsoft Learn
- About Topologies - Microsoft Learn
- IMFAttributes interface - Microsoft Learn
- IMFMediaType interface - Microsoft Learn
- IMFTransform interface - Microsoft Learn
- MFTEnumEx function - Microsoft Learn
- IMFSourceReader::GetNativeMediaType - Microsoft Learn
- ComPtr Class (Microsoft::WRL) - Microsoft Learn
- Conceptos básicos para evitar cuelgues con STA/MTA en COM | KomuraSoft Blog
- Por qué conviene crear un wrapper en C++/CLI al usar una DLL nativa de C++ desde C# | KomuraSoft Blog
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Cómo grabar imágenes y texto en los fotogramas de un MP4 con Media Foundation
Con Media Foundation, cómo grabar imágenes y texto en cada fotograma de un MP4 repartiendo el trabajo entre Source Reader, el dibujado, l...
Cómo convertir YUV a RGB en Media Foundation
Cómo convertir fotogramas YUV a RGB en Media Foundation: conversión automática con Source Reader, conversión manual de NV12/YUY2, stride ...
Cómo extraer una imagen fija de un MP4 en un instante específico con Media Foundation
Extraemos con Source Reader el fotograma más cercano a un instante de un MP4, ajustamos el stride y el alpha de RGB32, y lo guardamos com...
Cómo funcionan el portapapeles y arrastrar y soltar — Gestionar correctamente la transferencia de datos OLE en aplicaciones empresariales
Una tabla de Excel se deforma al pegarla y deja de poder pegarse si cierra el origen: es el portapapeles colocando el mismo contenido en ...
Compatibilidad retroactiva de interfaces DLL y COM — Tabla de decisión sobre qué cambios rompen al lado que llama
Qué cambios de DLL o COM rompen al lado que llama: los tres niveles de compatibilidad, la tabla de decisión por cambio y la regla de inmu...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Migración de ActiveX
Decisiones para conservar, encapsular o sustituir componentes COM / ActiveX / OCX.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
El procesamiento multimedia en Windows con Media Foundation, COM y HRESULT es un tema de implementación cercano al que tratamos como Desarrollo de aplicaciones Windows.
Consultoría técnica y revisión de diseño
Si desea ordenar primero los límites de tipo COM o el orden de inicialización, podemos abordarlo desde el diseño mediante Consultoría técnica y revisión de diseño.
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.