Media Foundation入門 - COM視点でAPIを理解する
· 更新日: · 小村 豪 · Media Foundation, COM, C++, Windows開発
更新履歴(5件・最終更新 2026年08月01日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 全体像の図で読み手からアプリへ戻る矢印が抜けていたのを直し、Source→Reader→アプリ→Writer→Sinkの流れが図として成立するようにしました。用語表にアパートメントとワークキューを追加し、`ComPtr`と`wil::com_ptr`の比較表、メディアタイプの取り決めの4ステップを追加しました。
- 検索結果に表示されるタイトルが記事の主題(COMの視点)と食い違っていたのを、見出しと揃う表現に直しました。本文と見出し、引用に使うタイトルは変えていません。
- 参考リンクなどで縦棒(パイプ)記号を含む行が表として表示され、リンクが押せなくなっていた表示崩れを修正しました。本文の内容は変えていません。
- 内容は変えずに構成を整理した。用語と全体像を先に示し、コード例を対応する解説の中へ移し、重複していた結論と早見表を1か所へまとめた。 更新前のバージョンを見る (DOI: 10.5281/zenodo.21589600)
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589599)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「Media Foundation入門 - COM視点でAPIを理解する」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589599 https://comcomponent.com/blog/2026/03/09/002-media-foundation-why-it-feels-like-com/
- DOI(最新版)
- 10.5281/zenodo.21589599
- DOI(この版)
- 10.5281/zenodo.21732625
Media Foundation を触り始めると、「Windows の動画や音声 API を使っているはずなのに、急に COM の話が増えた」と感じやすいです。CoInitializeEx、MFStartup、IMFSourceReader、IMFMediaType、IMFTransform、IMFActivate、HRESULT、GUID などが一気に出てきて、空気が急に Win32 / COM っぽくなり、Media Foundation とは何かが見えにくくなりがちです。
この記事では、Media Foundation 全体を辞書のように網羅するのではなく、次の 3 点に絞って書きます。
- なぜ 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
- 実務でのチェックリスト
- まとめ
- 参考資料
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 の顔になるのか」の見通しがぐっとよくなります。
2. 言葉と全体像
COM の話に入る前に、この記事で使う言葉と、Media Foundation の大枠だけ先に通しておきます。
2.1. 先に意味だけ押さえる言葉
| 用語 | ここでの意味 |
|---|---|
| Media Source | メディアデータをパイプラインへ入れる入口。ファイル、ネットワーク、キャプチャデバイスなど |
| MFT | Media Foundation Transform。デコーダー、エンコーダー、映像変換器などの共通モデル |
| Media Sink | メディアデータの行き先。画面表示、音声出力、ファイル書き出しなど |
| Media Session | パイプライン全体の流れを管理する仕組み。再生や同期を担当する |
| Topology | source / transform / sink をどうつなぐかを表す接続図 |
| Activation Object | 本体を後で作るためのヘルパーオブジェクト。IMFActivate で表される |
| Attributes | GUID をキーにした key/value ストア。Media Foundation 全体で多用される |
| apartment | COM がスレッドをまとめる単位。「このオブジェクトはどのスレッドから呼んでよいか」の取り決めで、CoInitializeEx の引数で決まる(3.1、3.5) |
| STA / MTA | apartment の種類。STA (Single-Threaded Apartment) は 1 本のスレッドに紐づき、他のスレッドからの呼び出しはメッセージポンプ経由で持ち込まれる。MTA (Multi-Threaded Apartment) は複数スレッドが同じ apartment を共有し、直接呼び出せる。詳しくは COMのSTA/MTAでハングを避けるための基礎知識 |
| work queue | Media Foundation が非同期処理を回すために持っているスレッドの仕組み。callback はここのスレッドから呼ばれる(3.5) |
このあたりを先に言葉として持っておくと、ドキュメントを読んだときの引っかかりがかなり減ります。
apartment は 3.5 で本題になりますが、ここでは「Media Foundation の callback は、自分が ReadSample を呼んだスレッドとは別のスレッド(MTA の work queue)から来ることがある」とだけ覚えておけば十分です。
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
Media Foundation には、大まかに次の 2 つの使い方があります。
- パイプライン全体を使うモデル
- source / transform / sink をつなぎ、Media Session がデータフローや A/V 同期を管理します
- アプリがデータを直接扱うモデル
- Source Reader で source からデータを取り出し、Sink Writer で sink へ流し込みます
後者のほうが、フレームやサンプルを自前で処理したい場面では入りやすいです。 一方で、再生や同期まで含めてプラットフォームに任せたいなら前者が本筋です。
押さえておきたいのは、Media Foundation の正体はメディア処理のプラットフォームであって、COM オブジェクトの寄せ集めを直接触る感覚とは少し違う ということです。
ただし、その部品同士の境界を見始めると、急に COM の顔が濃くなります。次の章で、その地点を順に見ていきます。
3. Media Foundation が COM の顔になる地点
COM の色が濃くなる地点は、だいたい次の 5 つに整理できます。
| 地点 | 何が出てくるか | まず理解したいこと |
|---|---|---|
| 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 |
callback と 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 の空気が濃くなる最初の地点です。
実務では、この時点で次を決めておくと後が楽です。
- どのスレッドが Media Foundation を使うのか
- そのスレッドを STA にするのか MTA にするのか
MFStartup/MFShutdownとCoInitializeEx/CoUninitializeの責務をどこが持つのか
実装では、別の層がすでに COM 初期化を担当していることもあります。その場合も、どこが責務を持つかを先に固定する ほうが安全です。この設計を曖昧にしたまま進むと、あとで callback や UI 連携のところで分かりにくくなります。
なお、この記事のコードでは SafeRelease を自前で書いて生のインターフェースポインタを管理します。Microsoft のドキュメントのサンプルがこの形で書かれていて、AddRef / Release がどこで効いているかが見えるためです。ただし、実務のコードでスマートポインタを使わない理由にはなりません。C++ で新しく書くなら、次のどちらかに寄せた方が安全です。
| 選択肢 | 実体 | 備考 |
|---|---|---|
Microsoft::WRL::ComPtr<T> |
<wrl/client.h> |
Windows SDK に同梱されていて追加の依存が要りません。Get() で生ポインタ、GetAddressOf() / & で out 引数、As<U>() で QueryInterface を書きます |
wil::com_ptr<T> |
WIL (Windows Implementation Libraries) の wil/com.h |
NuGet などで別途導入します。HRESULT を例外へ変換するヘルパーとセットで使えます |
ComPtr で書くと、3.1 の後半に出てくるような goto done; と SafeRelease の組み合わせは要らなくなり、スコープを抜けた時点で Release されます。この記事では COM の作法が見えることを優先して生ポインタのままにしていますが、新規コードは ComPtr から始める のをおすすめします。
3.2. オブジェクトの受け渡しがインターフェース中心
Media Foundation の API を読んでいくと、戻り値や out 引数の多くが COM インターフェースです。
IMFSourceReaderIMFMediaTypeIMFTransformIMFActivateIMFSampleIMFMediaBuffer
特徴的なのは、データの本体だけでなく、型情報や設定オブジェクトまでインターフェースで表される ことです。
たとえば、
IMFTransformは MFT を表すインターフェースですIMFAttributesは key/value ストアですIMFMediaTypeはIMFAttributesを継承した「メディア形式の説明」です
media type のような「設定データっぽいもの」まで、COM インターフェースで持っているわけです。ここで IUnknown、QueryInterface、AddRef / Release、HRESULT の文脈が自然に入ってきます。
flowchart TD
IUnknown["IUnknown"]
IUnknown --> IMFAttributes["IMFAttributes"]
IMFAttributes --> IMFMediaType["IMFMediaType"]
IMFAttributes --> IMFActivate["IMFActivate"]
IUnknown --> IMFSourceReader["IMFSourceReader"]
IUnknown --> IMFTransform["IMFTransform"]
ここまでくると、「Media Foundation はメディア API だけれど、境界の表し方はかなり COM だな」と見えてきます。
3.3. 設定や型情報が IMFAttributes と GUID 中心
Media Foundation を触っていると、設定が急に GUID だらけに見える地点があります。その中心が IMFAttributes で、GUID をキーにした key/value ストアです。これが Media Foundation 全体で非常によく使われます。
特に大事なのが IMFMediaType で、IMFAttributes を継承していて、メディア形式の情報を属性として持ちます。
たとえば次のような情報です。
- major type(音声か映像か)
- subtype(H.264、AAC、RGB32、PCM など)
- フレームサイズ
- フレームレート
- サンプルレート
- チャンネル数
flowchart LR
MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
MediaType --> Subtype["MF_MT_SUBTYPE"]
MediaType --> Detail["サイズ / FPS / サンプルレート など"]
ここを「GUID の森」と感じやすいのですが、実際にはやっていることはかなり素直です。
- 属性ストアを使って設定を持つ
- media type も属性ストアとして表す
- source / transform / sink の間で、その属性を見ながら形式をすり合わせる
設定と型情報の表現に COM 的なインターフェースと GUID が使われている、というだけの話です。
ここまでをコードで見ると、次のようになります。Source Reader で動画から 1 フレーム読むだけの例です。
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;
// pSample から IMFMediaBuffer を取り出して処理する
done:
SafeRelease(&pSample);
SafeRelease(&pType);
SafeRelease(&pReader);
return hr;
}
ここで見えてくるのは次の点です。
- reader も media type も COM インターフェース
- 設定は GUID ベース
- 戻り値は
HRESULT - 同期モードでは
ReadSampleがブロックする
「ただ 1 フレーム読みたい」だけでも、Media Foundation の境界ではかなり COM 的な顔になります。最後の同期モードの話は 3.5 で扱います。
media type negotiation の手順(6 章のチェックリストで「最初に見ておきたい 3 つ」に挙げる項目です)
上のコードは「RGB32 で欲しい」と宣言しているだけなので、実際の実務ではその前後に手順が要ります。Source Reader の場合、Microsoft のドキュメントが示している流れは次の 4 ステップです。
- ネイティブタイプを列挙する —
IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType)を、typeIndexを 0 から増やしながら呼びます。範囲を超えるとMF_E_NO_MORE_TYPESが返るので、それが列挙の終わりです(streamIndexが範囲外ならMF_E_INVALIDSTREAMNUMBER)。ファイルなら 1 ストリームにつき 1 種類のことが多いですが、Web カメラは複数の形式を持ちます - 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 で渡すのは部分的な指定なので、確定値はこちらで読む のが正しい順序です
この 4 ステップを飛ばして「たぶんこの形式だろう」で進めると、MF_E_INVALIDMEDIATYPE が返るか、通ったとしても想定と違う形式のバッファを読むことになります。
3.4. Activation Object が出てくる
Media Foundation の COM っぽさが特に出るのが activation object です。
IMFActivate は、あとで本体を作るためのヘルパーオブジェクトです。感覚としては、COM の class factory に近いものとして見ると分かりやすいです。
これが出てくる場面では、列挙 API の返り値が「そのまま使える本体」ではなく、まず IMFActivate* の配列になっていることがあります。
そして、必要なものだけ ActivateObject で実体化します。
sequenceDiagram
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 オブジェクト
この形は、Media Foundation が 差し替え可能な部品を後から見つけて組み合わせる設計 になっていることと相性がよいです。
また、activation object 自体が attributes を持てるので、「まず候補の属性を見る」「必要なら設定する」「あとで実体化する」という流れになりやすいです。ここもかなり COM 的です。
実際に MFTEnumEx で MFT を列挙して実体化すると、次のようになります。
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 の顔になる」感じをかなりよく表しています。
3.5. 非同期・コールバック・スレッドの扱いも COM 的
Media Foundation の実務で見落としやすいのが、非同期処理とスレッドモデルです。
たとえば Source Reader は、既定では同期モードです。同期モードでは ReadSample がブロックします。
ファイルやネットワーク、デバイスの状態によっては、その待ちが目に見える時間になることもあります。
非同期モードにしたい場合は、Source Reader 作成時に callback を渡します。
IMFSourceReaderCallback を実装したオブジェクトを用意し、MF_SOURCE_READER_ASYNC_CALLBACK 属性に設定してから作成する流れです。
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
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(...)
callback まわりで気を付けたいのは、この点です。
- UI スレッドの STA オブジェクトを、そのまま callback 側で触らない
- callback 実装は スレッドセーフ にする
- UI 更新が必要なら、結果だけ UI スレッドへ戻す
- 「Media Foundation の callback はどのスレッドから来るか」を最初に固定して考える
Media Foundation は STA オブジェクトの事情を勝手に吸収してくれるわけではありません。 そのため、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
Partial["Partial Topology<br/>Source -> Output"] --> Loader["Topology Loader"]
Loader --> Full["Full Topology<br/>Source -> Decoder MFT -> Output"]
Media Foundation は COM を使って部品の契約を表しつつ、その上でメディア処理プラットフォームとして動く ものです。この 2 段構えで見ておくと、迷子になりにくくなります。
5. どこから触るか(入口の選び方)
最初の入口を決めるときは、次の図で十分なことが多いです。
flowchart TD
Start["やりたいこと"] --> Q1{"最初に必要なのは?"}
Q1 -- "フレーム / サンプルを読みたい" --> A1["Source Reader"]
Q1 -- "ファイルへ書き出したい" --> A2["Sink Writer"]
Q1 -- "再生制御や A/V 同期が必要" --> A3["Media Session"]
Q1 -- "独自の変換器を入れたい" --> A4["MFT"]
表にすると、こうなります。
| やりたいこと | まず触るもの | 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 は、ファイルやデバイスからデータを取り出したいときの入口としてかなり使いやすいです。
向いているのは、たとえばこんなケースです。
- 動画ファイルからフレームを取りたい
- 音声ファイルをデコードしてサンプルを取りたい
- カメラからフレームを取りたい
- Media Foundation の source を、自前の処理パイプラインへつなぎたい
Source Reader は、必要に応じて decoder を読み込み、アプリケーションへデータを渡してくれます。 一方で、プレゼンテーションクロックの管理や A/V 同期、画面描画そのものまでは面倒を見ません。
「再生する」ためではなく「データを取る」ための入口、と考えると分かりやすいです。
5.2. ファイルへ書き出すなら Sink Writer
Sink Writer は、音声や映像をファイルへ書き出したいときの入口です。
用途としては、このあたりが典型です。
- 生成したフレームを動画ファイルへ保存したい
- 音声サンプルをエンコードして書き出したい
- 読み出したデータを別形式へ変換して保存したい
Sink Writer は、必要に応じて encoder を見つけて読み込み、media sink へのデータフローを管理します。 Source Reader と組み合わせることも多いですが、両者は独立した部品なので、必ずセットで使う必要はありません。
5.3. 再生と同期まで扱うなら Media Session
「ファイルから取りたい」ではなく、きちんと再生したい なら、Media Session を中心に考えたほうが素直です。
Media Session の出番になるのは、こういう要件があるときです。
- 再生 / 停止 / シークを扱いたい
- 音声と映像の同期をプラットフォーム側に任せたい
- 品質制御やフォーマット変更を含めてパイプラインを扱いたい
- topology を使って source / transform / sink の流れを組みたい
このレイヤーに入ると、Source Reader / Sink Writer よりも「Media Foundation 本体」に近づきます。 そのぶん、topology や session event など、Media Foundation 固有の概念も増えます。
5.4. 独自部品を差し込むなら MFT
MFT は、Media Foundation の transform の共通モデルです。
ここへ入るのは、こういう場面です。
- 独自のデコーダーやエンコーダーを作りたい
- 映像処理や音声処理の部品を、パイプラインへ差し込みたい
- codec や変換器を列挙して、自前で選びたい
- 既定の自動解決より深く制御したい
MFT の世界では、IMFTransform、IMFActivate、media type negotiation、サンプル / バッファ管理など、COM 的な契約がかなり前面に出ます。
そのため、最初の入口としていきなり MFT へ入るより、まず Source Reader / Sink Writer / Media Session のどれが本当に必要かを先に見たほうが分かりやすい です。
6. 実務でのチェックリスト
最後に、実務で最初に見ておきたい点を 1 枚にまとめます。
| 項目 | 見ておくこと | 見落とすと起きやすいこと |
|---|---|---|
| 初期化責務 | CoInitializeEx と MFStartup をどこで呼ぶか、終了処理をどこで持つか決める |
初期化漏れ、終了順序の混乱 |
| apartment | MF を触るスレッドを STA / MTA のどちらにするか先に決める | callback まわりの混乱、UI との衝突 |
| Source Reader のモード | 同期か非同期かを作成時に決める | ReadSample が想定外にブロックする、後から切り替えられない |
| media type negotiation | 出力形式を列挙し、実際に使う形式を明示する。手順は 3.3 の 4 ステップ(GetNativeMediaType で列挙 → major type 確認 → SetCurrentMediaType → GetCurrentMediaType で確定値を読む) |
MF_E_INVALIDMEDIATYPE、期待と違う形式が来る |
| オブジェクト寿命 | Release、Unlock、ShutdownObject の責務を明確にする |
メモリリーク、バッファ保持、終了時の不整合 |
| activation object | 列挙結果が本体なのか IMFActivate なのかを区別する |
QueryInterface できると思って失敗する |
| topology | partial topology と full topology のどちらを扱っているかを把握する | 「自動でつながるはず」と思って詰まる |
| エラー確認 | HRESULT、stream flags、event を毎回見る |
一部だけ失敗しているのに見逃す |
| UI 連携 | callback から直接 UI を触らず、結果だけ UI スレッドへ戻す | ハング、競合、分かりにくい不具合 |
特に優先度が高いのは次の 3 つです。
- 最初の入口 API を間違えないこと
- まずは Source Reader / Sink Writer / Media Session のどれが本当に必要かを分ける
- apartment を先に決めること
- STA の UI と Media Foundation の work queue を混ぜるなら、橋の渡し方を最初に決める
- media type negotiation を雑にしないこと
- 「たぶんこの形式だろう」で進めると、あとでかなり分かりにくくなります
- 具体的な手順は 3.3 の「media type negotiation の手順」にまとめてあります
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 と callback の方針を先に決める
- media type negotiation とオブジェクト寿命を丁寧に扱う
最初から全部を理解しようとしなくても大丈夫です。 まずは 「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
- COMのSTA/MTAでハングを避けるための基礎知識 | KomuraSoft Blog
- C++のネイティブDLLをC#から使うとき、C++/CLIでラッパーを作ったほうがよい理由 | KomuraSoft Blog
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Media FoundationでMP4に画像と文字を焼き込む方法
Media Foundation で MP4 動画の各フレームへ画像と文字を焼き込み、新しい MP4 を作る考え方を、Source Reader、描画、色変換、Sink Writer の分担と 1 ファイル完結の C++ サンプルで整理します。
Media FoundationでYUVをRGBに変換する方法
Media FoundationでYUVフレームをRGBへ変換する方法を、Source Readerの自動変換とNV12/YUY2自前変換、stride、色空間から整理します。
Media FoundationでMP4の指定時刻から静止画を切り出す方法
Source ReaderでMP4の指定時刻に近いフレームを取り出し、strideやRGB32のalphaを整えてPNG保存する実装手順をまとめます。
DLL・COMインターフェースの後方互換性 ── どの変更が呼び出し側を壊すのかの判断表
DLLやCOMコンポーネントのどの変更が呼び出し側を壊すのか。バイナリ互換・ソース互換・動作互換の3層を整理し、変更内容別の判断表、COMインターフェース不変の鉄則、semver運用までを実務ガイドとしてまとめます。
Arm版Windowsで業務アプリは動くのか ── x64エミュレーション(Prism)とネイティブDLL・COMの現実
「Arm版Windowsで業務アプリは動くのか」に開発者・情シス向けに答えます。x64エミュレーション(Prism)の仕組み、ドライバーなど動かない層、.NETのAnyCPUとP/Invokeの問題、Arm対応チェックリストまで整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
ActiveX / 移行テーマ
COM / ActiveX / OCX を残すか、包むか、置き換えるかを整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
Media Foundation、COM、HRESULT を含む Windows メディア処理は、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 の責務をどこが持つかを先に決めておくと、後の callback や UI 連携が楽になります。
- Source Reader・Sink Writer・Media Session・MFTはどう使い分けますか?
- ファイルやカメラからフレームやサンプルを取り出したいなら Source Reader、生成した音声・映像をファイルへ書き出したいなら Sink Writer が入口です。再生・停止・シークや A/V 同期、品質制御までプラットフォームに任せたいなら Media Session を中心に考えます。独自のデコーダーや変換器をパイプラインへ差し込みたい場合に MFT に進みますが、COM 的な契約が前面に出るため、まず前の3つのどれが本当に必要かを先に見るのがおすすめです。
- Media Foundationの非同期コールバックで注意することは何ですか?
- Media Foundation の非同期処理は work queue を使い、そのスレッドは MTA なので、アプリ側も MTA に寄せると実装が単純になります。IMFSourceReaderCallback の実装はスレッドセーフにし、UI スレッドの STA オブジェクトを callback 側で直接触らないことが重要です。UI 更新が必要なら結果だけを UI スレッドへ戻します。また Source Reader の同期・非同期モードは作成時に決まり、後から切り替えられない点にも注意が必要です。