Media Foundation이란 무엇인가 - COM과 Windows 미디어 API의 얼굴이 보이는 이유
Media Foundation을 만지기 시작하면 「Windows의 동영상이나 음성 API를 사용하고 있을 터인데, 갑자기 COM의 이야기가 늘었다」고 느끼기 쉽습니다.
특히 CoInitializeEx, HRESULT, IMFSourceReader, IMFTransform 주변이 한꺼번에 나오고, Media Foundation이란 무엇인지가 보이기 어려워지기 쉽습니다.
CoInitializeEx, MFStartup, IMFSourceReader, IMFMediaType, IMFTransform, IMFActivate, HRESULT, GUID 등이 한 번에 나오고, 공기가 갑자기 Win32 / COM 같아집니다.
이 글에서는 Media Foundation 전체를 사전처럼 망라하는 것이 아니라, 다음 3가지를 먼저 정리합니다.
- 왜 Media Foundation을 사용하고 있으면 COM의 이야기가 자연스럽게 나오는가
- 어디서 COM의 색이 진해지는가
- 처음에는 Source Reader / Sink Writer / Media Session / MFT 중 어디부터 만져야 할지
코드 예는 C++ 베이스이지만, 사고방식 자체는 .NET 등에서 래퍼 너머로 만지는 경우에도 거의 같습니다.
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. 먼저 보는 정리표
2.1. 무엇을 하고 싶을 때 무엇을 만지는가
처음에 이 표를 보면 입구를 고르기 쉬워집니다.
| 하고 싶은 것 | 먼저 만지는 것 | COM의 진함 | 보충 |
|---|---|---|---|
| 파일이나 카메라에서 프레임 / 샘플을 얻고 싶다 | Source Reader | 중 | 필요하면 decoder도 돌봐준다 |
| 생성한 음성 / 영상을 파일로 써내고 싶다 | Sink Writer | 중 | 필요하면 encoder와 media sink를 함께 다룰 수 있다 |
| 재생, 정지, 시크, A/V 동기, 품질 제어까지 다루고 싶다 | Media Session | 고 | topology와 session의 이해가 필요 |
| 독자적인 변환기나 codec적인 부품을 끼워 넣고 싶다 | MFT | 고 | IMFTransform을 중심으로 생각한다 |
| 열거한 후보를 보고, 필요한 것만 실체화하고 싶다 | IMFActivate |
고 | 돌아오는 것이 본체가 아니라 activation object인 경우가 있다 |
2.2. 어디서 COM의 얼굴이 되는가
| 지점 | 무엇이 나오는가 | 먼저 이해하고 싶은 것 |
|---|---|---|
| 초기화 | CoInitializeEx, MFStartup |
COM 초기화와 Media Foundation 초기화는 별개 |
| 오브젝트 생성・인도 | IMFSourceReader, IMFMediaType, IMFTransform |
대부분이 인터페이스 포인터 + HRESULT |
| 설정 | IMFAttributes, GUID |
설정값이나 형 정보가 key/value + GUID로 표현된다 |
| 열거・지연 생성 | IMFActivate, ActivateObject |
열거 결과가 그대로 본체가 아닌 경우가 있다 |
| 비동기 | IMFSourceReaderCallback, work queue |
callback과 apartment를 의식할 필요가 있다 |
| 재생 제어 | topology, Media Session | 파이프라인 전체의 흐름은 Media Foundation 고유의 개념 |
2.3. 먼저 의미만 짚을 용어
| 용어 | 여기서의 의미 |
|---|---|
| Media Source | 미디어 데이터를 파이프라인으로 넣는 입구. 파일, 네트워크, 캡처 디바이스 등 |
| MFT | Media Foundation Transform. 디코더, 인코더, 영상 변환기 등의 공통 모델 |
| Media Sink | 미디어 데이터의 갈 곳. 화면 표시, 음성 출력, 파일 써내기 등 |
| Media Session | 파이프라인 전체의 흐름을 관리하는 구조. 재생이나 동기를 담당한다 |
| Topology | source / transform / sink를 어떻게 연결할지를 나타내는 접속도 |
| Activation Object | 본체를 나중에 만들기 위한 헬퍼 오브젝트. IMFActivate로 표현된다 |
| Attributes | GUID를 키로 한 key/value 스토어. Media Foundation 전체에서 다용된다 |
이 부근을 먼저 용어로서 가지고 있으면 문서를 읽었을 때의 걸림돌이 꽤 줄어듭니다.
3. 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)"]
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의 얼굴이 진해집니다. 다음에서 거기를 정리합니다.
4. Media Foundation이 COM의 얼굴이 되는 지점
4.1. 초기화에서 CoInitializeEx와 MFStartup이 나란히 나온다
처음에 많은 사람이 위화감을 가지는 것이 여기입니다.
파일을 열고 싶다, 카메라에서 얻고 싶다, 라는 이야기 전에 먼저 CoInitializeEx와 MFStartup이 나옵니다.
CoInitializeEx는 COM 라이브러리의 초기화입니다MFStartup은 Media Foundation 플랫폼의 초기화입니다
즉, COM 초기화만으로는 부족하고 Media Foundation 측의 초기화도 필요합니다. 여기서 「이건 단순한 동영상 API가 아니라 아래에 COM 베이스의 계약이 꽤 들어가 있구나」 하고 알게 됩니다.
실무에서는 이 시점에서 다음을 정해두면 후가 편합니다.
- 어느 스레드가 Media Foundation을 사용하는가
- 그 스레드를 STA로 할지 MTA로 할지
MFStartup/MFShutdown과CoInitializeEx/CoUninitialize의 책무를 어디가 가질 것인가
이 설계를 애매하게 한 채로 진행하면, 나중에 callback이나 UI 연계의 곳에서 알기 어려워집니다.
4.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이구나」 하고 보입니다.
4.3. 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적입니다.
4.4. 설정이나 형 정보가 IMFAttributes와 GUID 중심
Media Foundation을 만지고 있으면 설정이 갑자기 GUID투성이로 보이는 지점이 있습니다.
그 중심이 IMFAttributes입니다.
IMFAttributes는 GUID를 키로 한 key/value 스토어입니다. 이것이 Media Foundation 전체에서 매우 자주 사용됩니다.
특히 중요한 것이 IMFMediaType입니다.
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가 사용되고 있다는 것입니다.
4.5. 비동기・콜백・스레드의 취급도 COM적
Media Foundation의 실무에서 간과하기 쉬운 것이 비동기 처리와 스레드 모델입니다.
예를 들어 Source Reader는 기본적으로 동기 모드입니다. 동기 모드에서는 ReadSample이 블로킹합니다.
파일이나 네트워크, 디바이스의 상태에 따라서는 그 대기가 눈에 보이는 시간이 되는 경우도 있습니다.
비동기 모드로 하고 싶은 경우는 Source Reader 작성 시에 callback을 넘깁니다.
IMFSourceReaderCallback을 구현한 오브젝트를 준비하고, MF_SOURCE_READER_ASYNC_CALLBACK 속성으로 설정한 후에 작성하는 흐름입니다.
더 약간 중요한 것이 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(...)
여기서 중요한 것은 다음 점입니다.
- UI 스레드의 STA 오브젝트를 그대로 callback 측에서 만지지 않는다
- callback 구현은 스레드 세이프로 한다
- UI 업데이트가 필요하면 결과만 UI 스레드로 되돌린다
- 「Media Foundation의 callback은 어느 스레드에서 오는가」를 처음에 고정해서 생각한다
Media Foundation은 STA 오브젝트의 사정을 마음대로 흡수해 주는 것이 아닙니다. 그래서 Media Foundation을 사용하는 워커는 MTA로 기울이고, UI와는 명시적으로 다리를 놓는 편이 정리하기 쉽습니다.
4.6. 다만 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"]
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 | 출력 형식을 열거하고, 실제로 사용하는 형식을 명시한다 | 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을 거칠게 하지 말 것
- 「아마도 이 형식일 것」으로 진행하면 나중에 꽤 알기 어려워집니다
7. 코드 발췌
여기서는 완전한 샘플이 아니라, 어디서 COM의 얼굴이 되는지가 알 수 있는 정도의 발췌만 싣습니다.
7.1. 초기화
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의 공기가 진해지는 최초의 지점입니다.
구현에서는 다른 층이 이미 COM 초기화를 담당하고 있는 경우도 있습니다. 그 경우도 어디가 책무를 가질지를 먼저 고정하는 편이 안전합니다.
7.2. 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;
// pSample에서 IMFMediaBuffer를 꺼내 처리한다
done:
SafeRelease(&pSample);
SafeRelease(&pType);
SafeRelease(&pReader);
return hr;
}
여기서 보이는 것은 다음 점입니다.
- reader도 media type도 COM 인터페이스
- 설정은 GUID 베이스
- 반환값은
HRESULT - 동기 모드에서는
ReadSample이 블로킹한다
「단지 1 프레임 읽고 싶다」만으로도, Media Foundation의 경계에서는 꽤 COM적인 얼굴이 됩니다.
7.3. Source Reader를 비동기 모드로 만든다
HRESULT CreateSourceReaderAsync(
PCWSTR path,
IMFSourceReaderCallback* pCallback,
IMFSourceReader** ppReader)
{
IMFAttributes* pAttributes = nullptr;
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
return hr;
}
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
if (SUCCEEDED(hr))
{
hr = MFCreateSourceReaderFromURL(path, pAttributes, ppReader);
}
SafeRelease(&pAttributes);
return hr;
}
여기서는 비동기 모드로 하기 위해 callback을 속성에 넣은 뒤 reader를 만들고 있습니다.
즉,
- callback 자체가 COM 인터페이스
- 비동기 설정이
IMFAttributes경유 - 모드는 작성 시에 정해진다
는 형태입니다.
실무에서는 IMFSourceReaderCallback 구현을 스레드 세이프로 하고, UI 오브젝트를 직접 가지고 들어오지 않도록 하는 것이 중요합니다.
7.4. 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의 얼굴이 된다」 느낌을 꽤 잘 나타내고 있습니다.
8. 정리
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은 그 경계면에 깊게 들어 있다」로 봐두면, 문서도 코드도 꽤 쫓기 쉬워집니다.
9. 참고 자료
- 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
-
[COM의 STA/MTA에서 행을 피하기 위한 기초 지식 합동회사 고무라소프트 Blog](https://comcomponent.com/blog/2026/01/31/000-sta-mta-com-relationship/) -
[C++의 네이티브 DLL을 C#에서 사용할 때 C++/CLI로 래퍼를 만드는 편이 좋은 이유 합동회사 고무라소프트 Blog](https://comcomponent.com/blog/2026/03/07/000-cpp-cli-wrapper-for-native-dlls/)
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Media Foundation으로 MP4 동영상의 각 프레임에 이미지와 문자를 구워 넣는 방법 - Source Reader / 드로잉 / 색 변환 / Sink Writer 정리와 .cpp에 그대로 붙일 수 있는 1파일 완결판
MP4 동영상의 각 프레임에 로고나 타임스탬프, 작업자 이름을 구워 넣어 새 MP4를 만드는 방법을, Source Reader 디코드 -> GDI+ 합성 -> NV12 색 변환 -> Sink Writer 재인코딩의 흐름과, Visual Studi...
Media Foundation에서 YUV 프레임을 RGB로 변환하는 방법 - Source Reader의 자동 변환과 직접 변환을 원리부터 정리
Media Foundation에서 NV12·YUY2 같은 YUV 프레임을 RGB로 옮기는 두 가지 길을 정리합니다. Source Reader의 자동 RGB32 변환과 직접 변환을 색공간·서브샘플링·stride 관점에서 비교하고 BT.601/709...
Media Foundation으로 MP4 동영상의 지정 시각에서 정지 이미지를 뽑는 방법 - .cpp에 그대로 붙일 수 있는 1파일 완결판
Media Foundation의 Source Reader로 MP4의 지정 시각에 가까운 프레임을 PNG로 꺼내는 흐름과, seek 어긋남·sample NULL·stride·RGB32 4바이트째의 함정을 정리하고, C++ 콘솔 앱용 1파일 완결 코...
COM 컴포넌트나 OCX / ActiveX 개발에서 빠지기 쉬운 것 - Visual Studio의 32bit / 64bit, 등록, 관리자 권한의 덫을 정리
COM 컴포넌트와 ActiveX, OCX 개발에서 자주 만나는 0x80040154나 0x80070005를 비트 수, 등록 방식, HKCU와 HKLM 스코프, 관리자 권한이라는 네 축으로 풀어 Visual Studio 2022의 64bit화 시대에...
공유 메모리를 사용할 때의 함정과 베스트 프랙티스 - 동기, 가시성, 수명, ABI, 보안을 먼저 정리
공유 메모리는 단순히 빠른 IPC가 아니라 동기, 가시성, 수명, ABI, 권한의 책임을 앱 측이 떠맡는 구조입니다. 본 글은 함정과 베스트 프랙티스를 정리하여 SPSC 링 버퍼나 더블 버퍼, 고정 헤더, 오프셋 참조 등 사고율을 내리는 설계 첫...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기존 자산 활용 & 이관 지원
COM / ActiveX / OCX 자산, 네이티브 코드, 32비트 의존성을 유지하면서 단계적인 이관 계획을 지원합니다.