Введение в Media Foundation: как понять API через COM
· Обновлено: · Го Комура · Media Foundation, COM, C++, Разработка Windows
История изменений (9 обновлений, последнее 30 Aug 2026)
Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.
- Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
- По замечаниям ревью схемы, добавленные в тот же день и слишком широкие, переложены в вертикальную компоновку; формулировки части схем и подписей приведены в точное соответствие с текстом. Сам текст статьи не менялся.
- Чтобы порядок инициализации и выбор API можно было проследить и по схеме, добавлено 13 диаграмм Mermaid (по норме «не меньше одной схемы на 500–750 знаков основного текста»). К уже существовавшим схемам добавлены подписи со сквозной нумерацией. Текст статьи не менялся.
- В начало статьи добавлен раздел «Карта знаний этой статьи». Понятия из текста и связи между ними собраны в краткое изложение, схему и ссылку на страницу сведений. Утверждения статьи не менялись.
- Текст обновлён по результатам внешнего ревью (1283 замечания). Содержание отдельных правок — в записях ниже.
- На схеме общей картины не хватало стрелки обратно к приложению; поток Source → Reader → приложение → Writer → Sink теперь читается как схема. В словарь терминов добавлены apartment и work queue, таблица сравнения `ComPtr` и `wil::com_ptr`, а также четыре шага согласования media type.
- Заголовок в результатах поиска расходился с темой статьи (взгляд через COM); формулировку выровняли с заголовком. Текст, заголовки и заголовок для цитирования не менялись.
- Исправлена ошибка отображения: строки с вертикальной чертой выводились как таблица, из-за чего справочные ссылки нельзя было нажать. Текст статьи не изменился.
- Содержание не менялось, перестроена композиция. Термины и общая картина вынесены вперёд, примеры кода перенесены в соответствующие пояснения, дублирующие вывод и краткую таблицу собраны в одно место. Открыть версию до этого обновления (DOI: 10.5281/zenodo.21589600)
- Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619645)
Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.
Го Комура (2026). Введение в Media Foundation: как понять API через COM. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619645 https://comcomponent.com/ru/blog/2026/03/09/002-media-foundation-why-it-feels-like-com/
- DOI (последняя версия)
- 10.5281/zenodo.21619645
- DOI (эта версия)
- 10.5281/zenodo.21619646
Когда начинаешь работать с Media Foundation, легко возникает ощущение: «вроде бы я использую видео- и аудио-API Windows, но вдруг стало слишком много разговоров о COM». CoInitializeEx, MFStartup, IMFSourceReader, IMFMediaType, IMFTransform, IMFActivate, HRESULT, GUID — всё это появляется разом, атмосфера резко становится похожа на Win32 / COM, и понять, что же такое Media Foundation, становится труднее.
Эта статья не пытается охватить весь Media Foundation, как словарь, — она сосредоточена на трёх вопросах.
- почему при использовании Media Foundation разговор о COM возникает естественным образом
- в каких местах COM-характер проявляется сильнее всего
- с чего начать в первую очередь — с Source Reader / Sink Writer / Media Session / MFT
Примеры кода даны на C++, но сам подход в целом одинаков и при работе через обёртки, например из .NET.
Содержание
- Сначала вывод (в двух словах)
- Слова и общая картина
- 2.1. Сначала усвоить смысл слов
- 2.2. Общая картина Media Foundation (схема)
- Где Media Foundation начинает выглядеть как COM
- 3.1. При инициализации рядом стоят
CoInitializeExиMFStartup - 3.2. Объекты передают через интерфейсы
- 3.3. Настройки и сведения о типе строятся вокруг
IMFAttributesи GUID - 3.4. Появляется Activation Object
- 3.5. Асинхронность, обратные вызовы и потоки тоже устроены по-COM
- 3.1. При инициализации рядом стоят
- И всё же Media Foundation ≠ COM
- С чего начинать (выбор точки входа)
- 5.1. Когда начинают с Source Reader
- 5.2. Если писать в файл — Sink Writer
- 5.3. Если нужны воспроизведение и синхронизация — Media Session
- 5.4. Если встраивать собственные компоненты — MFT
- Практический чек-лист
- Итог
- Источники
Карта знаний этой статьи
Статья объясняет, что Media Foundation — платформа обработки медиа на базе COM, через пять точек: инициализация, передача объектов, настройка, перечисление и асинхронная обработка. Инициализация библиотеки COM (CoInitializeEx) — предпосылка инициализации самой Media Foundation (MFStartup). Компоненты вроде IMFSourceReader, IMFAttributes и IMFTransform — COM-интерфейсы с IUnknown в основании, и они возвращают HRESULT. IMFActivate — точка входа, чтобы создать сам объект позже: IMFTransform появляется только после вызова ActivateObject по результату перечисления MFTEnumEx. Асинхронная обработка вызывается из потока work queue, который работает в MTA, поэтому проектирование не трогает UI-объекты стороны STA напрямую, а мостит только результат.
flowchart LR
accTitle: Карта знаний связи Media Foundation и COM
accDescr: Рисунок, показывающий, что Media Foundation — платформа обработки медиа на базе COM, что свойства COM проявляются в инициализации, представлении объектов, настройке, перечислении и асинхронной обработке, и что нужно мостить work queue в MTA и apartment-модель UI
media_foundation["Media Foundation"]
com["COM (Component Object Model)"]
mfstartup["MFStartup"]
coinitializeex["CoInitializeEx"]
iunknown["IUnknown"]
hresult["HRESULT"]
imfsourcereader["IMFSourceReader (Source Reader)"]
imftransform["IMFTransform (MFT)"]
imfattributes["IMFAttributes"]
imfactivate["IMFActivate (Activation Object)"]
imfsinkwriter["IMFSinkWriter (Sink Writer)"]
media_session["Media Session"]
mf_topology["Topology (Media Foundation)"]
imfsourcereadercallback["IMFSourceReaderCallback"]
mf_work_queue["work queue (Media Foundation)"]
com_apartment_model["модель апартаментов COM (STA/MTA)"]
com_smart_pointer["умный указатель COM (ComPtr / wil::com_ptr)"]
media_type_negotiation["согласование media type"]
media_foundation -->|"использует"| com
mfstartup -->|"требует"| coinitializeex
media_foundation -->|"использует"| mfstartup
com -->|"требует"| iunknown
com -->|"использует"| hresult
imfsourcereader -->|"использует"| com
imftransform -->|"использует"| com
imfattributes -->|"использует"| com
imfactivate -->|"использует"| imfattributes
media_foundation -.->|"использует"| imfsourcereader
media_foundation -.->|"использует"| imfsinkwriter
media_foundation -.->|"использует"| media_session
media_session -->|"использует"| mf_topology
imfsourcereader -->|"использует"| imftransform
imfsourcereader -.->|"использует"| imfsourcereadercallback
imfsourcereadercallback -->|"использует"| mf_work_queue
mf_work_queue -->|"использует"| com_apartment_model
com_smart_pointer -->|"рекомендуется для"| com
imfsourcereader -->|"требует"| media_type_negotiation
media_type_negotiation -->|"использует"| imfattributes
imftransform -.->|"требует"| imfactivate
На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 21, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle
1. Сначала вывод (в двух словах)
- Media Foundation — это платформа для работы с видео и аудио, и API целиком не является просто чистым COM
- Однако границы между source, transform, sink, activation, attributes и callback выражены через COM-интерфейсы, поэтому при использовании естественным образом возникают темы
IUnknown,HRESULT, GUID и apartment - Проще всего структурировать освоение так: начать с Source Reader / Sink Writer, перейти к Media Session, когда понадобится контроль воспроизведения, и к MFT, когда понадобится собственный преобразователь
Иными словами, Media Foundation — это платформа обработки медиаданных, в границы которой глубоко встроен COM.
Если усвоить это заранее, становится намного понятнее, «почему она вдруг начинает выглядеть как COM».
flowchart TB
accTitle: Связь Media Foundation и COM
accDescr: Суть Media Foundation — платформа обработки медиаданных; API целиком не является чистым COM, но границы между компонентами выражены COM-интерфейсами, поэтому естественным образом появляются IUnknown, HRESULT и GUID.
mf["Media Foundation"] --> plat["Платформа обработки медиаданных"]
mf --> border["Границы компонентов — COM-интерфейсы"]
border --> com["Появляются IUnknown, HRESULT, GUID"]
plat -.-> note["API целиком — не чистый COM"]
Рис. 1: Суть — платформа обработки медиаданных, а COM глубоко встроен в её границы.
2. Слова и общая картина
До разговора о COM сначала пройдём слова этой статьи и крупный контур Media Foundation.
2.1. Сначала усвоить смысл слов
| Термин | Что имеется в виду здесь |
|---|---|
| Media Source | Точка входа, подающая медиаданные в конвейер: файл, сеть, устройство захвата и т. п. |
| MFT | Media Foundation Transform. Общая модель для декодеров, энкодеров, преобразователей видео и т. п. |
| Media Sink | Место назначения медиаданных: показ на экране, аудиовывод, запись в файл и т. п. |
| Media Session | Механизм, который управляет потоком всего конвейера; отвечает за воспроизведение и синхронизацию |
| Topology | Схема соединений: как связаны source / transform / sink |
| Activation Object | Вспомогательный объект, чтобы создать реальный объект позже; представлен через IMFActivate |
| Attributes | Хранилище key/value с ключами-GUID; активно используется во всём Media Foundation |
| apartment | Единица, в которую COM собирает потоки. Договорённость «с какого потока можно вызывать этот объект»; задаётся аргументом CoInitializeEx (3.1, 3.5) |
| STA / MTA | Виды apartment. STA (Single-Threaded Apartment) привязан к одному потоку, вызовы с других потоков приходят через цикл сообщений. MTA (Multi-Threaded Apartment) делят несколько потоков, вызывать можно напрямую. Подробности: STA и MTA в COM: модель потоков и как не получить зависание |
| work queue | Механизм потоков, которым Media Foundation крутит асинхронную работу. Обратный вызов приходит с этих потоков (3.5) |
Если заранее иметь эти слова в словарном запасе, чтение документации станет заметно менее спотыкающимся.
Apartment станет основной темой в 3.5; здесь достаточно запомнить: «обратный вызов Media Foundation может прийти не с того потока, с которого вы вызвали ReadSample, а с другого (work queue в MTA)».
2.2. Общая картина Media Foundation (схема)
В целом Media Foundation — это рассказ о медиаконвейере. Тема COM важна, но упорядочить понимание проще, если сначала посмотреть на общую картину.
flowchart TB
subgraph Pipeline["Модель использования всего конвейера"]
Source1["Media Source"] --> Transform1["MFT"]
Transform1 --> Sink1["Media Sink"]
Session["Media Session"] --- Source1
Session --- Transform1
Session --- Sink1
end
subgraph Direct["Модель, где приложение обрабатывает данные напрямую"]
Source2["Media Source"] --> Reader["Source Reader (+ decoder)"]
Reader --> App["Приложение"]
App --> Writer["Sink Writer (+ encoder)"]
Writer --> Sink2["Media Sink"]
end
Рис. 2: Два способа использования. Модель, где конвейер отдают Media Session, и модель, где приложение само обрабатывает данные через Reader / Writer.
В общих чертах у Media Foundation есть два способа использования.
- Модель использования всего конвейера
- вы соединяете source / transform / sink, а Media Session управляет потоком данных и синхронизацией A/V
- Модель, где приложение обрабатывает данные напрямую
- вы извлекаете данные из source через Source Reader и подаёте их в sink через Sink Writer
Второй вариант проще, когда хочется самостоятельно обрабатывать кадры и сэмплы. С другой стороны, если хочется отдать платформе воспроизведение и синхронизацию, правильный путь — первый.
Стоит зафиксировать: суть Media Foundation — это платформа обработки медиаданных, и ощущение от неё немного отличается от прямой работы с набором COM-объектов.
Однако как только начинаешь рассматривать границы между её компонентами, COM-характер резко проявляется сильнее. В следующей главе эти точки рассматриваются по порядку.
3. Где Media Foundation начинает выглядеть как COM
Места, где COM-характер проявляется сильнее, можно свести примерно к пяти.
| Точка | Что появляется | Что важно понять в первую очередь |
|---|---|---|
| 3.1. Инициализация | CoInitializeEx, MFStartup |
Инициализация COM и инициализация Media Foundation — это разные вещи |
| 3.2. Создание и передача объектов | IMFSourceReader, IMFMediaType, IMFTransform |
В основном это указатели на интерфейсы + HRESULT |
| 3.3. Настройки | IMFAttributes, GUID |
Значения настроек и сведения о типе выражены как key/value + GUID |
| 3.4. Перечисление / отложенное создание | IMFActivate, ActivateObject |
Результат перечисления не всегда сам целевой объект |
| 3.5. Асинхронность | IMFSourceReaderCallback, work queue |
Нужно учитывать обратные вызовы и apartment |
| (глава 4) Контроль воспроизведения | topology, Media Session | Общий поток конвейера — понятие, специфичное для Media Foundation |
Последний пункт — контроль воспроизведения — другого рода: это не общая теория COM, а собственная функциональность Media Foundation. Поэтому его разбираем отдельно в главе 4.
Дальше — по порядку. Код здесь не полный пример, а фрагмент, достаточный, чтобы увидеть, где проявляется COM-характер.
3.1. При инициализации рядом стоят CoInitializeEx и MFStartup
Именно здесь у многих впервые возникает ощущение странности. Раньше разговора об открытии файла или захвате с камеры сначала появляются CoInitializeEx и MFStartup.
CoInitializeExинициализирует библиотеку COMMFStartupинициализирует платформу Media Foundation
То есть одной лишь инициализации COM недостаточно, нужна ещё инициализация со стороны Media Foundation. Здесь становится понятно: «это не просто видео-API — внутри заложен весьма основательный контракт на базе 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();
}
Именно эта пара — CoInitializeEx рядом с MFStartup — первая точка, где при работе с Media Foundation атмосфера COM резко сгущается.
flowchart TB
accTitle: Два уровня инициализации и завершения
accDescr: Сначала CoInitializeEx инициализирует библиотеку COM, затем MFStartup — платформу Media Foundation; при завершении вызывают MFShutdown и CoUninitialize в обратном порядке.
co["CoInitializeEx (инициализация COM)"] --> mfs["MFStartup (инициализация MF)"]
mfs --> use["Использовать Media Foundation"]
use --> shut["MFShutdown"]
shut --> coun["CoUninitialize"]
Рис. 3: Одной инициализации COM недостаточно. Инициализация — в два уровня, завершение — в обратном порядке.
На практике полезно решить в этот момент следующее.
- какой поток использует Media Foundation
- будет ли этот поток STA или MTA
- кто отвечает за
MFStartup/MFShutdownиCoInitializeEx/CoUninitialize
В реализации инициализацией COM иногда уже занимается другой слой. Даже тогда безопаснее заранее зафиксировать, кто несёт эту ответственность. Если продвигаться дальше, оставив это проектирование расплывчатым, позже станет труднее разобраться с обратными вызовами и связкой с UI.
В коде этой статьи SafeRelease написан вручную, и сырые указатели на интерфейсы управляются явно. Так написаны примеры в документации Microsoft, и так видно, где срабатывают AddRef / Release. Но это не причина не использовать smart-указатели в рабочем коде. Если писать новый код на C++, безопаснее держаться одного из двух вариантов ниже.
| Вариант | Где лежит | Замечание |
|---|---|---|
Microsoft::WRL::ComPtr<T> |
<wrl/client.h> |
Входит в Windows SDK, дополнительных зависимостей нет. Сырой указатель — Get(), out-аргумент — GetAddressOf() / &, QueryInterface — As<U>() |
wil::com_ptr<T> |
wil/com.h из WIL (Windows Implementation Libraries) |
Ставят отдельно, например через NuGet. Обычно используют вместе с помощниками, которые превращают HRESULT в исключение |
Если писать на ComPtr, сочетание goto done; и SafeRelease из второй половины 3.1 больше не нужно: Release вызывается при выходе из области видимости. В этой статье сырые указатели оставлены, чтобы была видна COM-дисциплина, но новый код лучше начинать с ComPtr.
flowchart TB
accTitle: Когда сырой указатель, когда smart-указатель
accDescr: Код статьи написан на сырых указателях и SafeRelease, чтобы было видно, где срабатывают AddRef и Release; в новом рабочем коде безопаснее держаться ComPtr или wil::com_ptr — тогда Release вызывается при выходе из области видимости.
raw["Сырой указатель и SafeRelease"] -.-> why["Запись, в которой видно, где срабатывает Release"]
smart["ComPtr или wil::com_ptr"] --> auto["Release при выходе из области видимости"]
auto --> rec["Новый код начинать с ComPtr"]
Рис. 4: Код статьи учебный и поэтому на сырых указателях. Новый рабочий код держат ближе к smart-указателям.
3.2. Объекты передают через интерфейсы
Если читать API Media Foundation дальше, окажется, что большинство возвращаемых значений и out-параметров — это COM-интерфейсы.
IMFSourceReaderIMFMediaTypeIMFTransformIMFActivateIMFSampleIMFMediaBuffer
Характерная особенность в том, что интерфейсами представлены не только сами данные, но и сведения о типе, и объекты настроек.
Например:
IMFTransform— интерфейс, представляющий MFTIMFAttributes— хранилище key/valueIMFMediaTypeнаследуетIMFAttributesи представляет собой «описание формата медиаданных»
То есть даже нечто вроде «данных настроек», как media type, хранится через COM-интерфейс. Здесь естественным образом появляется контекст IUnknown, QueryInterface, AddRef / Release и HRESULT.
flowchart TD
accTitle: Родословная основных интерфейсов
accDescr: Настройки и сведения о типе тоже выражены COM-интерфейсами с вершиной IUnknown: IMFAttributes наследует IUnknown, IMFMediaType и IMFActivate наследуют IMFAttributes, IMFSourceReader и IMFTransform — напрямую IUnknown.
IUnknown["IUnknown"]
IUnknown --> IMFAttributes["IMFAttributes"]
IMFAttributes --> IMFMediaType["IMFMediaType"]
IMFAttributes --> IMFActivate["IMFActivate"]
IUnknown --> IMFSourceReader["IMFSourceReader"]
IUnknown --> IMFTransform["IMFTransform"]
Рис. 5: Родословная основных интерфейсов. Даже настройки и сведения о типе выражены COM-интерфейсами с вершиной IUnknown.
Дойдя до этого места, начинаешь видеть: «Media Foundation — это медиа-API, но способ выражения границ весьма COM-подобен».
3.3. Настройки и сведения о типе строятся вокруг IMFAttributes и GUID
При работе с Media Foundation есть момент, когда настройки внезапно начинают казаться сплошными GUID. В центре этого — IMFAttributes, хранилище key/value с ключами-GUID. Оно очень активно используется во всём Media Foundation.
Особенно важен IMFMediaType, который наследует IMFAttributes и хранит сведения о формате медиаданных как атрибуты.
Например, такую информацию:
- major type (аудио или видео)
- subtype (H.264, AAC, RGB32, PCM и т. п.)
- размер кадра
- частоту кадров
- частоту дискретизации
- число каналов
flowchart LR
accTitle: IMFMediaType как хранилище атрибутов
accDescr: IMFMediaType — хранилище атрибутов: сведения о формате вроде major type и subtype держатся с ключами GUID.
MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
MediaType --> Subtype["MF_MT_SUBTYPE"]
MediaType --> Detail["Размер / FPS / частота дискретизации и т. п."]
Рис. 6: IMFMediaType — хранилище атрибутов; сведения о формате вроде major type и subtype держатся с ключами GUID.
Это легко воспринять как «лес из GUID», но на деле происходящее довольно простое.
- настройки хранятся через хранилище атрибутов
- media type тоже представлен как хранилище атрибутов
- между source, transform и sink формат согласовывается путём просмотра этих атрибутов
Речь просто о том, что для выражения настроек и сведений о типе используются COM-подобные интерфейсы и GUID.
В коде это выглядит так. Пример — прочитать из видео один кадр через 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;
// Извлекаем IMFMediaBuffer из pSample и обрабатываем
done:
SafeRelease(&pSample);
SafeRelease(&pType);
SafeRelease(&pReader);
return hr;
}
Здесь видны такие моменты.
- и reader, и media type — это COM-интерфейсы
- настройки строятся на основе GUID
- возвращаемое значение —
HRESULT - в синхронном режиме
ReadSampleблокирует поток
Даже когда хочется «просто прочитать один кадр», на границе Media Foundation это оборачивается весьма COM-подобным лицом. Про синхронный режим в конце — в 3.5.
Порядок согласования media type (это один из «трёх пунктов, которые стоит смотреть сначала» в чек-листе главы 6)
Код выше только заявляет «хочу RGB32», поэтому в настоящей работе до и после него нужны шаги. Для Source Reader документация Microsoft показывает такой поток из четырёх шагов.
- Перечислить собственные типы — вызывать
IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType), увеличиваяtypeIndexс 0. Когда диапазон исчерпан, возвращаетсяMF_E_NO_MORE_TYPES— это конец перечисления (еслиstreamIndexвне диапазона —MF_E_INVALIDSTREAMNUMBER). У файла на один поток часто один вид, у веб-камеры — несколько форматов - Проверить major type — из полученного media type читают
MF_MT_MAJOR_TYPEи решают, аудио это или видео. Если идти дальше с фиксированным предположением, не глядя сюда, настройки видео можно отправить в аудиопоток - Собрать нужный выходной формат и задать его —
MFCreateMediaTypeсоздаёт новый media type, задаютMF_MT_MAJOR_TYPEиMF_MT_SUBTYPE, вызываютSetCurrentMediaType. Если нужно получить сжатое как есть, передают тип из шага 1 без изменений; если нужно декодировать — указывают несжатый формат (MFVideoFormat_RGB32,MFAudioFormat_PCMи т. п.). Декодер Source Reader загружает сам - Перечитать зафиксированный формат — после
SetCurrentMediaTypeвызываютGetCurrentMediaTypeи получают детали реально зафиксированного формата (размер кадра, stride, частота дискретизации и т. п.). На шаге 3 передают частичное указание, поэтому зафиксированные значения читают здесь — это правильный порядок
Если пропустить эти четыре шага и идти дальше из «наверное, это тот формат», вернётся MF_E_INVALIDMEDIATYPE либо пройдёт, но вы будете читать буфер не того формата, который ожидали.
flowchart TB
accTitle: Четыре шага согласования media type
accDescr: Собственные типы перечисляют GetNativeMediaType, проверяют major type, собирают нужный выходной формат и задают его SetCurrentMediaType, затем перечитывают зафиксированные значения через GetCurrentMediaType.
s1["Перечислить GetNativeMediaType"] --> s2["Проверить major type"]
s2 --> s3["Задать нужный формат SetCurrentMediaType"]
s3 --> s4["Прочитать зафиксированные значения GetCurrentMediaType"]
s1 -.-> stop["MF_E_NO_MORE_TYPES — конец перечисления"]
Рис. 7: Согласование формата — четыре шага. Передают частичное указание, поэтому зафиксированные значения перечитывают в конце.
3.4. Появляется Activation Object
COM-подобность Media Foundation особенно проявляется в activation object.
IMFActivate — это вспомогательный объект для создания реального объекта позже. Интуитивно проще всего воспринимать его как нечто близкое к class factory в COM.
В сценариях, где он появляется, возвращаемое значение API перечисления может быть не «сразу готовым к использованию объектом», а сначала массивом IMFActivate*.
Затем через ActivateObject создаётся только то, что действительно нужно.
sequenceDiagram
accTitle: Перечисление даёт IMFActivate, ActivateObject даёт реальный объект
accDescr: API перечисления возвращает массив IMFActivate; атрибуты проверяют, вызывают ActivateObject и только тогда получают реальный COM-объект.
participant App as Приложение
participant Enum as API перечисления
participant Act as IMFActivate
participant Obj as IMFTransform / sink и т. п.
App->>Enum: Вызывает перечисление
Enum-->>App: Массив IMFActivate*
App->>Act: Проверяет атрибуты
App->>Act: ActivateObject(...)
Act-->>App: Реальный COM-объект
Рис. 8: API перечисления возвращает IMFActivate; реальный COM-объект получают, только вызвав ActivateObject.
Такая форма хорошо согласуется с тем, что Media Foundation спроектирован так, чтобы находить заменяемые компоненты позже и комбинировать их.
Кроме того, поскольку сам activation object может нести атрибуты, часто складывается поток: «сначала посмотреть атрибуты кандидата», «при необходимости настроить», «создать реальный объект позже». Это тоже весьма COM-подобно.
Если перечислить MFT через MFTEnumEx и создать реальный объект, получается так.
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;
}
Здесь результат перечисления возвращается не сразу как IMFTransform*, а как IMFActivate**.
И только вызвав ActivateObject, вы наконец получаете реальный IMFTransform.
Этот поток весьма хорошо передаёт то ощущение, что Media Foundation «вдруг начинает выглядеть как COM».
flowchart TB
accTitle: От MFTEnumEx до реального декодера
accDescr: MFTEnumEx перечисляет кандидатов-декодеров и возвращает массив IMFActivate; если кандидатов нет — MF_E_TOPO_CODEC_NOT_FOUND, если есть — ActivateObject создаёт реальный IMFTransform, затем каждый IMFActivate освобождают Release, а сам массив — CoTaskMemFree.
enum["Перечислить кандидатов MFTEnumEx"] --> arr["Возвращается массив IMFActivate"]
arr --> q{"Есть кандидаты?"}
q -->|"Нет"| nf["MF_E_TOPO_CODEC_NOT_FOUND"]
q -->|"Да"| act["Создать реальный объект ActivateObject"]
act --> obj["Получить IMFTransform"]
act -.-> free["Release каждого элемента"]
free -.-> free2["Массив — CoTaskMemFree"]
Рис. 9: Перечисление → проверка кандидатов → создание реального объекта → освобождение. Результат перечисления — ещё не готовый к использованию объект.
3.5. Асинхронность, обратные вызовы и потоки тоже устроены по-COM
В реальной работе с Media Foundation легко упустить из виду асинхронную обработку и потоковую модель.
Например, Source Reader по умолчанию работает в синхронном режиме. В синхронном режиме ReadSample блокирует поток.
В зависимости от состояния файла, сети или устройства это ожидание может стать заметным по времени.
Чтобы перейти в асинхронный режим, при создании Source Reader передают callback.
Порядок такой: подготовить объект, реализующий IMFSourceReaderCallback, установить его в атрибут MF_SOURCE_READER_ASYNC_CALLBACK, а затем создать 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;
}
То есть:
- сам callback — это COM-интерфейс
- настройка асинхронности идёт через
IMFAttributes - режим определяется в момент создания
Ещё несколько более важен вопрос apartment. Асинхронная обработка Media Foundation использует work queue, а потоки work queue относятся к MTA. Поэтому если и сторону приложения держать ближе к MTA, реализация упрощается.
sequenceDiagram
accTitle: Асинхронный ReadSample сразу возвращается, OnReadSample приходит с work queue
accDescr: В асинхронном режиме ReadSample сразу возвращается вызывающей стороне, внутри обрабатывается на work queue, и OnReadSample вызывается с потока work queue (MTA).
participant App as Поток приложения
participant Reader as Source Reader
participant Queue as MF work queue (MTA)
participant Cb as IMFSourceReaderCallback
App->>Reader: ReadSample(...)
Reader-->>App: Возвращается немедленно
Reader->>Queue: Обрабатывает внутри
Queue->>Cb: OnReadSample(...)
Рис. 10: В асинхронном режиме ReadSample сразу возвращается, а OnReadSample вызывается с потока work queue.
Вокруг обратных вызовов стоит обратить внимание на такие моменты.
- не трогать STA-объекты потока UI напрямую со стороны callback
- делать реализацию callback потокобезопасной
- если нужно обновление UI, передавать в поток UI только результат
- заранее зафиксировать в голове, «с какого потока приходят обратные вызовы Media Foundation»
Media Foundation не берёт на себя автоматическое улаживание особенностей STA-объектов. Поэтому проще держать порядок, если воркер, использующий Media Foundation, держать ближе к MTA и явно построить мост к UI.
flowchart TB
accTitle: Как строить мост между callback и потоком UI
accDescr: Callback приходит с потока work queue в MTA, поэтому реализацию делают потокобезопасной, STA-объекты UI напрямую не трогают, а если нужно обновить UI — в поток UI передают только результат.
cb["Callback приходит с work queue в MTA"] --> safe["Реализацию делают потокобезопасной"]
cb -.-> ng["STA-объекты UI напрямую не трогают"]
safe --> bridge["В поток UI передают только результат"]
Рис. 11: Воркер, который использует Media Foundation, держат ближе к MTA и явно строят мост к UI.
4. И всё же Media Foundation ≠ COM
Дочитав до этого места, легко подумать: «в итоге Media Foundation — это просто COM». Но это не совсем так.
В Media Foundation есть понятия, специфичные для платформы, которые общими рассуждениями о COM не объяснить.
MFStartup/MFShutdown- Media Session
- topology
- topology loader
- presentation clock
- Source Reader / Sink Writer
Всё это — собственная роль Media Foundation: как прогонять медиаконвейер.
Например, в Media Session, когда приложение передаёт partial topology, topology loader дополняет её нужными transform и разрешает в full topology. Это не общий разговор о COM, а функциональность, которой Media Foundation обладает именно как платформа обработки медиаданных.
flowchart LR
accTitle: Topology loader дополняет partial topology до full topology
accDescr: Приложение передаёт partial topology, topology loader дополняет нужные transform и разрешает в full topology: Source → Decoder MFT → Output.
Partial["Partial Topology<br/>Source -> Output"] --> Loader["Topology Loader"]
Loader --> Full["Full Topology<br/>Source -> Decoder MFT -> Output"]
Рис. 12: Передали partial topology — topology loader дополняет нужные transform и разрешает в full topology.
Media Foundation использует COM для выражения контрактов между компонентами и поверх этого работает как платформа обработки медиаданных. Если рассматривать это в две ступени, заблудиться становится труднее.
flowchart TB
accTitle: Два уровня: слой COM и слой платформы
accDescr: Media Foundation выражает контракты между компонентами через COM, а поверх него держит механизмы медиаконвейера — Media Session, topology, presentation clock, — которые общей теорией COM не исчерпываются.
com2["Слой COM (контракты компонентов)"] --> mf2["Слой платформы обработки медиаданных"]
mf2 --> own["Media Session, topology и т. п."]
own -.-> note2["Понятия, специфичные для MF, которые общей теорией COM не закрыть"]
Рис. 13: Это не перелицовка COM. Над слоем COM лежит слой, специфичный для MF, который прогоняет конвейер.
5. С чего начинать (выбор точки входа)
При выборе первой точки входа часто достаточно этой схемы.
flowchart TD
accTitle: Точку входа выбирают по тому, что нужно сначала
accDescr: Если нужно читать кадры или сэмплы — Source Reader, писать в файл — Sink Writer, нужен контроль воспроизведения или синхронизация A/V — Media Session, встроить собственный преобразователь — MFT.
Start["Что нужно сделать"] --> Q1{"Что нужно в первую очередь?"}
Q1 -- "Прочитать кадры / сэмплы" --> A1["Source Reader"]
Q1 -- "Записать в файл" --> A2["Sink Writer"]
Q1 -- "Нужен контроль воспроизведения или синхронизация A/V" --> A3["Media Session"]
Q1 -- "Встроить собственный преобразователь" --> A4["MFT"]
Рис. 14: Точку входа выбирают по тому, что нужно сначала. Читать — Reader, писать — Writer, воспроизводить — Session.
Таблицей это выглядит так.
| Что нужно сделать | Что использовать в первую очередь | Насыщенность COM | Примечание |
|---|---|---|---|
| Получить кадры / сэмплы из файла или камеры | Source Reader | Средняя | При необходимости сам позаботится и о decoder |
| Записать сгенерированное аудио / видео в файл | Sink Writer | Средняя | При необходимости объединяет работу encoder и media sink |
| Обрабатывать воспроизведение, остановку, перемотку, синхронизацию A/V, контроль качества | Media Session | Высокая | Требует понимания topology и session |
| Встроить собственный преобразователь или codec-подобный компонент | MFT | Высокая | Рассуждать вокруг IMFTransform |
| Сначала просмотреть перечисленные кандидаты, а затем создать только нужные | IMFActivate |
Высокая | Возвращается не сам объект, а иногда activation object |
5.1. Когда начинают с Source Reader
Source Reader — весьма удобная точка входа, когда нужно извлекать данные из файлов или устройств.
Он подходит, например, для таких случаев.
- получить кадры из видеофайла
- декодировать аудиофайл и получить сэмплы
- получить кадры с камеры
- подключить source Media Foundation к собственному конвейеру обработки
Source Reader при необходимости загружает decoder и передаёт данные приложению. При этом он не берёт на себя управление presentation clock, синхронизацию A/V и саму отрисовку на экране.
Проще всего воспринимать его как точку входа не для «воспроизведения», а для «получения данных».
flowchart TB
accTitle: Границы роли Source Reader
accDescr: Source Reader берёт данные из файла или камеры, при необходимости загружает decoder и отдаёт их приложению, но не берёт на себя управление presentation clock, синхронизацию A/V и отрисовку.
src["Source: файл, камера и т. п."] --> sr["Source Reader"]
sr --> app["Данные приложению"]
sr -.-> dec["При необходимости загружает decoder"]
sr -.-> not["Воспроизведение и синхронизацию на себя не берёт"]
Рис. 15: Source Reader — точка входа не для «воспроизведения», а для «получения данных».
5.2. Если писать в файл — Sink Writer
Sink Writer — точка входа, когда нужно записать аудио или видео в файл.
Типичные варианты применения такие.
- сохранить сгенерированные кадры в видеофайл
- закодировать и записать аудиосэмплы
- преобразовать прочитанные данные в другой формат и сохранить
Sink Writer при необходимости находит и загружает encoder, а также управляет потоком данных к media sink. Его часто сочетают с Source Reader, но оба компонента независимы, поэтому использовать их обязательно вместе не нужно.
flowchart TB
accTitle: Границы роли Sink Writer
accDescr: Приложение отдаёт сгенерированные кадры или аудиосэмплы в Sink Writer; тот при необходимости находит и загружает encoder, управляет потоком к media sink и пишет в файл.
app2["Кадры и аудио, которые сгенерировало приложение"] --> sw["Sink Writer"]
sw -.-> enc["При необходимости загружает encoder"]
sw --> sink["Пишет в media sink"]
sw -.-> ind["Независимый от Source Reader компонент"]
Рис. 16: Sink Writer — точка входа, которая берёт на себя кодирование и запись. Использовать вместе с Reader не обязательно.
5.3. Если нужны воспроизведение и синхронизация — Media Session
Если цель не «получить данные из файла», а полноценно воспроизвести, естественнее выстраивать логику вокруг Media Session.
Черёд Media Session наступает при таких требованиях.
- нужно обрабатывать воспроизведение / остановку / перемотку
- нужно отдать синхронизацию аудио и видео платформе
- нужно управлять конвейером, включая контроль качества и смену формата
- нужно строить поток source / transform / sink через topology
Войдя в этот слой, вы приближаетесь к «самому Media Foundation» сильнее, чем при работе с Source Reader / Sink Writer. Соответственно, растёт и число специфичных для Media Foundation понятий — topology, session event и т. д.
flowchart TB
accTitle: Когда выбирают Media Session
accDescr: Если воспроизведение, остановку, перемотку, синхронизацию A/V и контроль качества хотят отдать платформе, логику выстраивают вокруг Media Session, поток собирают через topology, и специфичных для Media Foundation понятий становится больше.
need["Отдать воспроизведение, перемотку и синхронизацию"] --> ms["Логику выстраивают вокруг Media Session"]
ms --> topo["Собирают поток от source к sink через topology"]
ms -.-> deep["Специфичных для MF понятий становится больше"]
Рис. 17: Если нужно не «взять данные», а «полноценно воспроизвести», правильный путь — Media Session.
5.4. Если встраивать собственные компоненты — MFT
MFT — общая модель transform в Media Foundation.
Сюда переходят в таких ситуациях.
- нужно создать собственный декодер или энкодер
- нужно встроить в конвейер компонент обработки видео или аудио
- нужно перечислить codec’и или преобразователи и выбрать самостоятельно
- нужен более глубокий контроль, чем стандартное автоматическое разрешение
В мире MFT COM-подобные контракты выходят на первый план весьма сильно: IMFTransform, IMFActivate, media type negotiation, управление сэмплами и буферами.
Поэтому понятнее не заходить сразу в MFT как в первую точку входа, а сначала определить, что из Source Reader / Sink Writer / Media Session действительно необходимо.
flowchart TB
accTitle: Что проверить, прежде чем идти в MFT
accDescr: Если нужно встроить в конвейер собственный декодер или преобразователь, переходят к MFT, но COM-подобные контракты выходят на первый план, поэтому сначала смотрят, не хватает ли Source Reader, Sink Writer или Media Session.
first["Сначала смотрят, хватает ли трёх других точек входа"] --> q{"Нужен собственный преобразователь?"}
q -->|"Нет"| use3["Идут через Reader, Writer или Session"]
q -->|"Да"| mft["Идут к MFT (IMFTransform)"]
mft -.-> heavy["COM-подобные контракты выходят на первый план"]
Рис. 18: MFT — последняя точка входа. Не входят сразу: сначала убеждаются, что трёх других недостаточно.
6. Практический чек-лист
Напоследок соберём в одной таблице то, что стоит проверить в первую очередь на практике.
| Пункт | Что проверять | Что часто случается, если упустить |
|---|---|---|
| Ответственность за инициализацию | Решить, где вызываются CoInitializeEx и MFStartup и кто отвечает за завершение |
Пропуски инициализации, путаница в порядке завершения |
| apartment | Заранее решить, будет ли поток, работающий с MF, STA или MTA | Путаница вокруг обратных вызовов, конфликты с UI |
| Режим Source Reader | Решить синхронный или асинхронный режим при создании | ReadSample блокирует неожиданно, переключить позже нельзя |
| Согласование media type | Перечислить выходные форматы и явно указать используемый. Порядок — четыре шага из 3.3 (перечисление GetNativeMediaType → проверка major type → SetCurrentMediaType → чтение зафиксированных значений через GetCurrentMediaType) |
MF_E_INVALIDMEDIATYPE, приходит не тот формат, что ожидался |
| Время жизни объектов | Чётко определить ответственность за Release, Unlock, ShutdownObject |
Утечки памяти, удержание буферов, несогласованность при завершении |
| activation object | Различать, является ли результат перечисления самим объектом или IMFActivate |
Ошибка из-за предположения, что QueryInterface сработает |
| topology | Понимать, с частичной или полной topology вы работаете | Затор из-за предположения «должно соединиться автоматически» |
| Проверка ошибок | Каждый раз проверять HRESULT, stream flags, события |
Пропуск частичного сбоя |
| Связка с UI | Не трогать UI напрямую из callback, передавать в поток UI только результат | Зависания, гонки, труднообъяснимые дефекты |
Особенно высокий приоритет имеют следующие три пункта.
- Не ошибиться с первой точкой входа API
- сначала определить, что из Source Reader / Sink Writer / Media Session действительно необходимо
- Заранее решить вопрос apartment
- если STA UI и work queue Media Foundation будут смешиваться, сначала определить способ наведения моста
- Не относиться небрежно к согласованию media type
- если продвигаться исходя из «наверное, это тот формат», позже это станет весьма запутанным
- конкретный порядок собран в 3.3, в подразделе «Порядок согласования media type»
flowchart TB
accTitle: Три пункта чек-листа с самым высоким приоритетом
accDescr: Не ошибиться с первой точкой входа API, заранее решить apartment и не относиться небрежно к согласованию media type — три пункта с самым высоким приоритетом; ими избегают путаницы в дальнейшей реализации.
c1["Не ошибиться с точкой входа API"] --> ease["Дальше реализация меньше путается"]
c2["Сначала решить apartment"] --> ease
c3["Не относиться небрежно к согласованию формата"] --> ease
Рис. 19: Даже внутри чек-листа эти три пункта стоит зафиксировать первыми.
7. Итог
То, что при работе с Media Foundation резко увеличивается число разговоров о COM, — не случайность.
- Media Foundation — это платформа обработки медиаданных
- её границы — source / transform / sink / activation / callback и т. п. — выражены через COM-интерфейсы
- поэтому естественным образом возникают темы
IUnknown,HRESULT, GUID, apartment, callback - однако суть Media Foundation — это медиаконвейер с Media Session и topology, а не просто перелицовка COM
На практике структурировать понимание проще, если рассуждать в таком порядке.
- сначала определить, что вообще нужно — Source Reader / Sink Writer / Media Session / MFT
- заранее решить политику apartment и обратных вызовов
- аккуратно обращаться с согласованием media type и временем жизни объектов
flowchart TB
accTitle: Порядок рассуждения на практике
accDescr: Сначала отделяют, какая точка входа нужна, затем решают политику apartment и обратных вызовов, в конце аккуратно работают с согласованием media type и временем жизни объектов.
o1["Отделить, какая точка входа нужна"] --> o2["Решить политику apartment и обратных вызовов"]
o2 --> o3["Аккуратно согласовать формат и время жизни"]
Рис. 20: Порядок рассуждения на практике. Сначала точка входа, затем политика потоков, затем формат и время жизни.
Не обязательно пытаться понять всё сразу с самого начала. Если для начала держать в голове, что «Media Foundation — платформа обработки медиаданных, а COM глубоко встроен в её границы», и документацию, и код станет заметно легче отслеживать.
8. Источники
- 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
- STA и MTA в COM: модель потоков и как не получить зависание | KomuraSoft Blog
- Вызов native DLL из C#: обёртка на C++/CLI или P/Invoke | KomuraSoft Blog
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Как наложить изображение и текст на кадры MP4 в Media Foundation
Разбираем, как в Media Foundation прожечь изображение и текст в каждый кадр MP4 и собрать новый файл: роли Source Reader, отрисовки, прео...
Как преобразовать YUV в RGB с помощью Media Foundation
Как в Media Foundation преобразовать кадр YUV в RGB: автоматическое преобразование Source Reader, самостоятельное преобразование NV12/YUY...
Как извлечь кадр из MP4 по заданному времени с помощью Media Foundation
Как Source Reader берёт из MP4 кадр, ближайший к заданному времени, выравнивает stride и alpha в RGB32 и сохраняет результат в PNG.
Обратная совместимость интерфейсов DLL и COM — таблица: какие изменения ломают вызывающий код
Какие изменения DLL или COM-компонента ломают вызывающий код. Разбираем три слоя совместимости — бинарную, исходного кода и поведенческую...
Ловушки регистрации и bitness при разработке COM/OCX/ActiveX
Практический разбор типичных ловушек COM, OCX и ActiveX: разрядность 32/64-бит, Visual Studio 2022, regsvr32 и Regasm, права администрато...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Миграция ActiveX
Решения о сохранении, обёртке или замене компонентов COM / ActiveX / OCX.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Обработка медиаданных в Windows с Media Foundation, COM и HRESULT близка к тем темам реализации, которые мы ведём как разработку Windows-приложений.
Технические консультации и ревью дизайна
Если сначала нужно разобраться с COM-подобными границами и порядком инициализации, к этому можно подойти со стороны проектирования — как к технической консультации с ревью архитектуры.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Что такое Media Foundation? Чем это отличается от COM?
- Media Foundation — это платформа обработки медиаданных для работы с видео и аудио в Windows. API целиком не является просто чистым COM. Однако границы между такими компонентами, как source, transform, sink, activation, attributes и callback, выражены через COM-интерфейсы, поэтому при использовании естественным образом возникают темы IUnknown, HRESULT, GUID и apartment. Точнее всего воспринимать это так: «платформа обработки медиаданных, в границы которой глубоко встроен COM».
- Почему нужны и MFStartup, и CoInitializeEx?
- Потому что у них разные роли. CoInitializeEx инициализирует библиотеку COM, а MFStartup — платформу Media Foundation. Одной лишь инициализации COM недостаточно, нужна ещё инициализация со стороны Media Foundation. На практике, если заранее решить, какой поток использует Media Foundation, будет ли он STA или MTA, и кто отвечает за MFStartup / MFShutdown и CoInitializeEx / CoUninitialize, дальше проще работать с обратными вызовами и связкой с UI.
- Как выбирать между Source Reader, Sink Writer, Media Session и MFT?
- Если нужно извлекать кадры или сэмплы из файла или камеры, точка входа — Source Reader; если нужно записывать сгенерированное аудио или видео в файл — Sink Writer. Если хочется отдать платформе воспроизведение, остановку, перемотку, синхронизацию A/V и контроль качества, логику выстраивают вокруг Media Session. Если нужно встроить в конвейер собственный декодер или преобразователь, переходят к MFT, но там на первый план выходят COM-подобные контракты, поэтому сначала стоит понять, что из первых трёх действительно необходимо.
- На что обращать внимание в асинхронных обратных вызовах Media Foundation?
- Асинхронная обработка Media Foundation использует work queue, а её потоки — MTA, поэтому если и сторону приложения держать ближе к MTA, реализация упрощается. Важно сделать реализацию IMFSourceReaderCallback потокобезопасной и не трогать STA-объекты потока UI напрямую из обратного вызова. Если нужно обновление UI, в поток UI передают только результат. Также стоит учитывать, что режим (синхронный или асинхронный) Source Reader определяется при создании и позже переключить его нельзя.
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.