Введение в 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.

Содержание

  1. Сначала вывод (в двух словах)
  2. Слова и общая картина
    • 2.1. Сначала усвоить смысл слов
    • 2.2. Общая картина Media Foundation (схема)
  3. Где Media Foundation начинает выглядеть как COM
    • 3.1. При инициализации рядом стоят CoInitializeEx и MFStartup
    • 3.2. Объекты передают через интерфейсы
    • 3.3. Настройки и сведения о типе строятся вокруг IMFAttributes и GUID
    • 3.4. Появляется Activation Object
    • 3.5. Асинхронность, обратные вызовы и потоки тоже устроены по-COM
  4. И всё же Media Foundation ≠ COM
  5. С чего начинать (выбор точки входа)
    • 5.1. Когда начинают с Source Reader
    • 5.2. Если писать в файл — Sink Writer
    • 5.3. Если нужны воспроизведение и синхронизация — Media Session
    • 5.4. Если встраивать собственные компоненты — MFT
  6. Практический чек-лист
  7. Итог
  8. Источники

Карта знаний этой статьи

Статья объясняет, что Media Foundation — платформа обработки медиа на базе COM, через пять точек: инициализация, передача объектов, настройка, перечисление и асинхронная обработка. Инициализация библиотеки COM (CoInitializeEx) — предпосылка инициализации самой Media Foundation (MFStartup). Компоненты вроде IMFSourceReader, IMFAttributes и IMFTransform — COM-интерфейсы с IUnknown в основании, и они возвращают HRESULT. IMFActivate — точка входа, чтобы создать сам объект позже: IMFTransform появляется только после вызова ActivateObject по результату перечисления MFTEnumEx. Асинхронная обработка вызывается из потока work queue, который работает в MTA, поэтому проектирование не трогает UI-объекты стороны STA напрямую, а мостит только результат.

Карта знаний связи Media Foundation и COMРисунок, показывающий, что Media Foundation — платформа обработки медиа на базе COM, что свойства COM проявляются в инициализации, представлении объектов, настройке, перечислении и асинхронной обработке, и что нужно мостить work queue в MTA и apartment-модель UIиспользуеттребуетиспользуеттребуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетиспользуетрекомендуется длятребуетиспользуеттребуетMedia FoundationCOM (Component Object Model)MFStartupCoInitializeExIUnknownHRESULTIMFSourceReader (Source Reader)IMFTransform (MFT)IMFAttributesIMFActivate (Activation Object)IMFSinkWriter (Sink Writer)Media SessionTopology (Media Foundation)IMFSourceReaderCallbackwork queue (Media Foundation)модель апартаментов COM (STA/MTA)умный указатель COM (ComPtr / wil::com_ptr)согласование media type

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 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».

Связь Media Foundation и COMСуть Media Foundation — платформа обработки медиаданных; API целиком не является чистым COM, но границы между компонентами выражены COM-интерфейсами, поэтому естественным образом появляются IUnknown, HRESULT и GUID.Media FoundationПлатформа обработки медиаданныхГраницы компонентов — COM-интерфейсыПоявляются IUnknown, HRESULT, GUIDAPI целиком — не чистый 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 важна, но упорядочить понимание проще, если сначала посмотреть на общую картину.

Модель, где приложение обрабатывает данные напрямуюSource Reader (+ decoder)Media SourceПриложениеSink Writer (+ encoder)Media SinkМодель использования всего конвейераMFTMedia SourceMedia SinkMedia Session

Рис. 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 инициализирует библиотеку COM
  • MFStartup инициализирует платформу 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 резко сгущается.

Два уровня инициализации и завершенияСначала CoInitializeEx инициализирует библиотеку COM, затем MFStartup — платформу Media Foundation; при завершении вызывают MFShutdown и CoUninitialize в обратном порядке.CoInitializeEx (инициализация COM)MFStartup (инициализация MF)Использовать Media FoundationMFShutdownCoUninitialize

Рис. 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() / &, QueryInterfaceAs<U>()
wil::com_ptr<T> wil/com.h из WIL (Windows Implementation Libraries) Ставят отдельно, например через NuGet. Обычно используют вместе с помощниками, которые превращают HRESULT в исключение

Если писать на ComPtr, сочетание goto done; и SafeRelease из второй половины 3.1 больше не нужно: Release вызывается при выходе из области видимости. В этой статье сырые указатели оставлены, чтобы была видна COM-дисциплина, но новый код лучше начинать с ComPtr.

Когда сырой указатель, когда smart-указательКод статьи написан на сырых указателях и SafeRelease, чтобы было видно, где срабатывают AddRef и Release; в новом рабочем коде безопаснее держаться ComPtr или wil::com_ptr — тогда Release вызывается при выходе из области видимости.Сырой указатель и SafeReleaseЗапись, в которой видно, где срабатывает ReleaseComPtr или wil::com_ptrRelease при выходе из области видимостиНовый код начинать с ComPtr

Рис. 4: Код статьи учебный и поэтому на сырых указателях. Новый рабочий код держат ближе к smart-указателям.

3.2. Объекты передают через интерфейсы

Если читать API Media Foundation дальше, окажется, что большинство возвращаемых значений и out-параметров — это COM-интерфейсы.

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

Характерная особенность в том, что интерфейсами представлены не только сами данные, но и сведения о типе, и объекты настроек.

Например:

  • IMFTransform — интерфейс, представляющий MFT
  • IMFAttributes — хранилище key/value
  • IMFMediaType наследует IMFAttributes и представляет собой «описание формата медиаданных»

То есть даже нечто вроде «данных настроек», как media type, хранится через COM-интерфейс. Здесь естественным образом появляется контекст IUnknown, QueryInterface, AddRef / Release и HRESULT.

Родословная основных интерфейсовНастройки и сведения о типе тоже выражены COM-интерфейсами с вершиной IUnknown: IMFAttributes наследует IUnknown, IMFMediaType и IMFActivate наследуют IMFAttributes, IMFSourceReader и IMFTransform — напрямую IUnknown.IUnknownIMFAttributesIMFMediaTypeIMFActivateIMFSourceReaderIMFTransform

Рис. 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 и т. п.)
  • размер кадра
  • частоту кадров
  • частоту дискретизации
  • число каналов
IMFMediaType как хранилище атрибутовIMFMediaType — хранилище атрибутов: сведения о формате вроде major type и subtype держатся с ключами GUID.IMFMediaTypeMF_MT_MAJOR_TYPEMF_MT_SUBTYPEРазмер / 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,
        &timestamp,
        &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 показывает такой поток из четырёх шагов.

  1. Перечислить собственные типы — вызывать IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType), увеличивая typeIndex с 0. Когда диапазон исчерпан, возвращается MF_E_NO_MORE_TYPES — это конец перечисления (если streamIndex вне диапазона — MF_E_INVALIDSTREAMNUMBER). У файла на один поток часто один вид, у веб-камеры — несколько форматов
  2. Проверить major type — из полученного media type читают MF_MT_MAJOR_TYPE и решают, аудио это или видео. Если идти дальше с фиксированным предположением, не глядя сюда, настройки видео можно отправить в аудиопоток
  3. Собрать нужный выходной формат и задать егоMFCreateMediaType создаёт новый media type, задают MF_MT_MAJOR_TYPE и MF_MT_SUBTYPE, вызывают SetCurrentMediaType. Если нужно получить сжатое как есть, передают тип из шага 1 без изменений; если нужно декодировать — указывают несжатый формат (MFVideoFormat_RGB32, MFAudioFormat_PCM и т. п.). Декодер Source Reader загружает сам
  4. Перечитать зафиксированный формат — после SetCurrentMediaType вызывают GetCurrentMediaType и получают детали реально зафиксированного формата (размер кадра, stride, частота дискретизации и т. п.). На шаге 3 передают частичное указание, поэтому зафиксированные значения читают здесь — это правильный порядок

Если пропустить эти четыре шага и идти дальше из «наверное, это тот формат», вернётся MF_E_INVALIDMEDIATYPE либо пройдёт, но вы будете читать буфер не того формата, который ожидали.

Четыре шага согласования media typeСобственные типы перечисляют GetNativeMediaType, проверяют major type, собирают нужный выходной формат и задают его SetCurrentMediaType, затем перечитывают зафиксированные значения через GetCurrentMediaType.Перечислить GetNativeMediaTypeПроверить major typeЗадать нужный формат SetCurrentMediaTypeПрочитать зафиксированные значения GetCurrentMediaTypeMF_E_NO_MORE_TYPES — конец перечисления

Рис. 7: Согласование формата — четыре шага. Передают частичное указание, поэтому зафиксированные значения перечитывают в конце.

3.4. Появляется Activation Object

COM-подобность Media Foundation особенно проявляется в activation object.

IMFActivate — это вспомогательный объект для создания реального объекта позже. Интуитивно проще всего воспринимать его как нечто близкое к class factory в COM.

В сценариях, где он появляется, возвращаемое значение API перечисления может быть не «сразу готовым к использованию объектом», а сначала массивом IMFActivate*. Затем через ActivateObject создаётся только то, что действительно нужно.

Перечисление даёт IMFActivate, ActivateObject даёт реальный объектAPI перечисления возвращает массив IMFActivate; атрибуты проверяют, вызывают ActivateObject и только тогда получают реальный COM-объект.IMFTransform / sink и т. п.IMFActivateAPI перечисленияПриложениеIMFTransform / sink и т. п.IMFActivateAPI перечисленияПриложениеВызывает перечислениеМассив IMFActivate*Проверяет атрибутыActivateObject(...)Реальный 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».

От MFTEnumEx до реального декодераMFTEnumEx перечисляет кандидатов-декодеров и возвращает массив IMFActivate; если кандидатов нет — MF_E_TOPO_CODEC_NOT_FOUND, если есть — ActivateObject создаёт реальный IMFTransform, затем каждый IMFActivate освобождают Release, а сам массив — CoTaskMemFree.НетДаПеречислить кандидатов MFTEnumExВозвращается массив IMFActivateЕсть кандидаты?MF_E_TOPO_CODEC_NOT_FOUNDСоздать реальный объект ActivateObjectПолучить IMFTransformRelease каждого элементаМассив — 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, реализация упрощается.

Асинхронный ReadSample сразу возвращается, OnReadSample приходит с work queueВ асинхронном режиме ReadSample сразу возвращается вызывающей стороне, внутри обрабатывается на work queue, и OnReadSample вызывается с потока work queue (MTA).IMFSourceReaderCallbackMF work queue (MTA)Source ReaderПоток приложенияIMFSourceReaderCallbackMF work queue (MTA)Source ReaderПоток приложенияReadSample(...)Возвращается немедленноОбрабатывает внутриOnReadSample(...)

Рис. 10: В асинхронном режиме ReadSample сразу возвращается, а OnReadSample вызывается с потока work queue.

Вокруг обратных вызовов стоит обратить внимание на такие моменты.

  • не трогать STA-объекты потока UI напрямую со стороны callback
  • делать реализацию callback потокобезопасной
  • если нужно обновление UI, передавать в поток UI только результат
  • заранее зафиксировать в голове, «с какого потока приходят обратные вызовы Media Foundation»

Media Foundation не берёт на себя автоматическое улаживание особенностей STA-объектов. Поэтому проще держать порядок, если воркер, использующий Media Foundation, держать ближе к MTA и явно построить мост к UI.

Как строить мост между callback и потоком UICallback приходит с потока work queue в MTA, поэтому реализацию делают потокобезопасной, STA-объекты UI напрямую не трогают, а если нужно обновить UI — в поток UI передают только результат.Callback приходит с work queue в MTAРеализацию делают потокобезопаснойSTA-объекты UI напрямую не трогаютВ поток 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 обладает именно как платформа обработки медиаданных.

Topology loader дополняет partial topology до full topologyПриложение передаёт partial topology, topology loader дополняет нужные transform и разрешает в full topology: Source → Decoder MFT → Output.Partial TopologySource -&gt; OutputTopology LoaderFull TopologySource -&gt; Decoder MFT -&gt; Output

Рис. 12: Передали partial topology — topology loader дополняет нужные transform и разрешает в full topology.

Media Foundation использует COM для выражения контрактов между компонентами и поверх этого работает как платформа обработки медиаданных. Если рассматривать это в две ступени, заблудиться становится труднее.

Два уровня: слой COM и слой платформыMedia Foundation выражает контракты между компонентами через COM, а поверх него держит механизмы медиаконвейера — Media Session, topology, presentation clock, — которые общей теорией COM не исчерпываются.Слой COM (контракты компонентов)Слой платформы обработки медиаданныхMedia Session, topology и т. п.Понятия, специфичные для MF, которые общей теорией COM не закрыть

Рис. 13: Это не перелицовка COM. Над слоем COM лежит слой, специфичный для MF, который прогоняет конвейер.

5. С чего начинать (выбор точки входа)

При выборе первой точки входа часто достаточно этой схемы.

Точку входа выбирают по тому, что нужно сначалаЕсли нужно читать кадры или сэмплы — Source Reader, писать в файл — Sink Writer, нужен контроль воспроизведения или синхронизация A/V — Media Session, встроить собственный преобразователь — MFT.Прочитать кадры / сэмплыЗаписать в файлНужен контроль воспроизведения или синхронизация A/VВстроить собственный преобразовательЧто нужно сделатьЧто нужно в первую очередь?Source ReaderSink WriterMedia SessionMFT

Рис. 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 и саму отрисовку на экране.

Проще всего воспринимать его как точку входа не для «воспроизведения», а для «получения данных».

Границы роли Source ReaderSource Reader берёт данные из файла или камеры, при необходимости загружает decoder и отдаёт их приложению, но не берёт на себя управление presentation clock, синхронизацию A/V и отрисовку.Source: файл, камера и т. п.Source ReaderДанные приложениюПри необходимости загружает decoderВоспроизведение и синхронизацию на себя не берёт

Рис. 15: Source Reader — точка входа не для «воспроизведения», а для «получения данных».

5.2. Если писать в файл — Sink Writer

Sink Writer — точка входа, когда нужно записать аудио или видео в файл.

Типичные варианты применения такие.

  • сохранить сгенерированные кадры в видеофайл
  • закодировать и записать аудиосэмплы
  • преобразовать прочитанные данные в другой формат и сохранить

Sink Writer при необходимости находит и загружает encoder, а также управляет потоком данных к media sink. Его часто сочетают с Source Reader, но оба компонента независимы, поэтому использовать их обязательно вместе не нужно.

Границы роли Sink WriterПриложение отдаёт сгенерированные кадры или аудиосэмплы в Sink Writer; тот при необходимости находит и загружает encoder, управляет потоком к media sink и пишет в файл.Кадры и аудио, которые сгенерировало приложениеSink WriterПри необходимости загружает encoderПишет в media sinkНезависимый от 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 и т. д.

Когда выбирают Media SessionЕсли воспроизведение, остановку, перемотку, синхронизацию A/V и контроль качества хотят отдать платформе, логику выстраивают вокруг Media Session, поток собирают через topology, и специфичных для Media Foundation понятий становится больше.Отдать воспроизведение, перемотку и синхронизациюЛогику выстраивают вокруг Media SessionСобирают поток от source к sink через topologyСпецифичных для 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 действительно необходимо.

Что проверить, прежде чем идти в MFTЕсли нужно встроить в конвейер собственный декодер или преобразователь, переходят к MFT, но COM-подобные контракты выходят на первый план, поэтому сначала смотрят, не хватает ли Source Reader, Sink Writer или Media Session.НетДаСначала смотрят, хватает ли трёх других точек входаНужен собственный преобразователь?Идут через Reader, Writer или SessionИдут к MFT (IMFTransform)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 только результат Зависания, гонки, труднообъяснимые дефекты

Особенно высокий приоритет имеют следующие три пункта.

  1. Не ошибиться с первой точкой входа API
    • сначала определить, что из Source Reader / Sink Writer / Media Session действительно необходимо
  2. Заранее решить вопрос apartment
    • если STA UI и work queue Media Foundation будут смешиваться, сначала определить способ наведения моста
  3. Не относиться небрежно к согласованию media type
    • если продвигаться исходя из «наверное, это тот формат», позже это станет весьма запутанным
    • конкретный порядок собран в 3.3, в подразделе «Порядок согласования media type»
Три пункта чек-листа с самым высоким приоритетомНе ошибиться с первой точкой входа API, заранее решить apartment и не относиться небрежно к согласованию media type — три пункта с самым высоким приоритетом; ими избегают путаницы в дальнейшей реализации.Не ошибиться с точкой входа APIДальше реализация меньше путаетсяСначала решить apartmentНе относиться небрежно к согласованию формата

Рис. 19: Даже внутри чек-листа эти три пункта стоит зафиксировать первыми.

7. Итог

То, что при работе с Media Foundation резко увеличивается число разговоров о COM, — не случайность.

  • Media Foundation — это платформа обработки медиаданных
  • её границы — source / transform / sink / activation / callback и т. п. — выражены через COM-интерфейсы
  • поэтому естественным образом возникают темы IUnknown, HRESULT, GUID, apartment, callback
  • однако суть Media Foundation — это медиаконвейер с Media Session и topology, а не просто перелицовка COM

На практике структурировать понимание проще, если рассуждать в таком порядке.

  1. сначала определить, что вообще нужно — Source Reader / Sink Writer / Media Session / MFT
  2. заранее решить политику apartment и обратных вызовов
  3. аккуратно обращаться с согласованием media type и временем жизни объектов
Порядок рассуждения на практикеСначала отделяют, какая точка входа нужна, затем решают политику apartment и обратных вызовов, в конце аккуратно работают с согласованием media type и временем жизни объектов.Отделить, какая точка входа нужнаРешить политику apartment и обратных вызововАккуратно согласовать формат и время жизни

Рис. 20: Порядок рассуждения на практике. Сначала точка входа, затем политика потоков, затем формат и время жизни.

Не обязательно пытаться понять всё сразу с самого начала. Если для начала держать в голове, что «Media Foundation — платформа обработки медиаданных, а COM глубоко встроен в её границы», и документацию, и код станет заметно легче отслеживать.

8. Источники

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Что такое 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, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог