Media Foundation이란 무엇인가 - COM과 Windows 미디어 API의 얼굴이 보이는 이유
· 업데이트: · 小村 豪 · Media Foundation, COM, C++, Windows 개발
수정 이력(2건, 최종 수정 2026년 09월 01일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- 일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다.
- 참고 링크 등에서 세로줄(파이프) 기호가 들어간 줄이 표로 표시되어 링크를 누를 수 없었던 표시 오류를 수정했습니다. 본문 내용은 그대로입니다.
- 최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635098)
이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.
小村 豪 (2026). 「Media Foundation이란 무엇인가 - COM과 Windows 미디어 API의 얼굴이 보이는 이유」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635098 https://comcomponent.com/ko/blog/2026/03/09/002-media-foundation-why-it-feels-like-com/
- DOI(최신 버전)
- 10.5281/zenodo.21635098
- DOI(이 버전)
- 10.5281/zenodo.22217414
Media Foundation을 만지기 시작하면 「Windows의 동영상이나 음성 API를 쓰고 있을 텐데 갑자기 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을 토대로 한 미디어 처리 플랫폼이라는 것을, 초기화·오브젝트 생성·인도·설정·열거·비동기 처리라는 5가지 지점에서 설명합니다. COM 라이브러리의 초기화(CoInitializeEx)는 Media Foundation 자신의 초기화(MFStartup)의 전제가 되며, IMFSourceReader나 IMFAttributes, IMFTransform 같은 부품은 모두 IUnknown을 기반으로 하는 COM 인터페이스로서 HRESULT를 반환합니다. IMFActivate는 실체를 나중에 만들기 위한 입구로, MFTEnumEx의 열거 결과로부터 ActivateObject를 불러야 비로소 IMFTransform을 얻을 수 있습니다. 비동기 처리는 MTA에서 동작하는 work queue의 스레드에서 호출되기 때문에, STA 쪽의 UI 객체를 직접 다루지 않고 결과만 다리 놓는 설계가 필요해집니다.
flowchart LR
accTitle: Media Foundation과 COM의 관계 지식 맵
accDescr: Media Foundation이 COM을 기반으로 한 미디어 처리 플랫폼이라는 것, 초기화·오브젝트 생성·인도·설정·열거·비동기 처리의 각 지점에서 COM의 성질이 나타나는 것, MTA의 work queue와 UI의 apartment 모델을 다리 놓아야 하는 것을 보여주는 그림
media_foundation["Media Foundation"]
com["COM(컴포넌트 오브젝트 모델)"]
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(토폴로지)"]
imfsourcereadercallback["IMFSourceReaderCallback"]
mf_work_queue["work queue"]
com_apartment_model["COM 아파트먼트 모델(STA/MTA)"]
com_smart_pointer["COM 스마트 포인터(ComPtr/wil::com_ptr)"]
media_type_negotiation["미디어 타입 협상(media type negotiation)"]
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 | GUID를 키로 한 key/value 스토어. Media Foundation 전체에서 널리 쓰인다 |
| apartment | COM이 스레드를 묶는 단위. 「이 오브젝트는 어느 스레드에서 호출해도 되는가」의 약속으로, CoInitializeEx의 인수로 정해진다(3.1, 3.5) |
| STA / MTA | apartment의 종류. STA(Single-Threaded Apartment)는 스레드 하나에 묶이며, 다른 스레드에서의 호출은 메시지 펌프를 거쳐 들어온다. 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
그림 2: 두 가지 사용법. Media Session에 파이프라인을 맡기는 모델과, Reader / Writer로 앱이 데이터를 직접 다루는 모델.
Media Foundation에는 크게 다음 두 가지 사용법이 있습니다.
- 파이프라인 전체를 사용하는 모델
- 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의 공기가 짙어지는 첫 지점입니다.
flowchart TB
accTitle: 초기화와 종료의 2단 구성
accDescr: CoInitializeEx로 COM 라이브러리를 초기화하고 MFStartup으로 Media Foundation 플랫폼을 초기화한 뒤 사용하며, 종료 시에는 MFShutdown과 CoUninitialize를 역순으로 호출하는 2단 구성을 보여준다.
co["CoInitializeEx(COM의 초기화)"] --> mfs["MFStartup(MF의 초기화)"]
mfs --> use["Media Foundation을 사용"]
use --> shut["MFShutdown"]
shut --> coun["CoUninitialize"]
그림 3: COM 초기화만으로는 부족하다. 초기화는 2단 구성이며, 종료는 역순으로 되돌린다.
실무에서는 이 시점에서 다음을 정해 두면 나중이 편합니다.
- 어느 스레드가 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부터 시작하는 것을 권합니다.
flowchart TB
accTitle: 생 포인터와 스마트 포인터의 구분
accDescr: 이 글의 코드는 AddRef와 Release가 작동하는 지점이 보이도록 생 포인터와 SafeRelease로 작성했지만, 실무의 신규 코드는 ComPtr이나 wil::com_ptr에 맞추면 스코프를 벗어난 시점에 Release되어 안전하다는 것을 보여준다.
raw["생 포인터와 SafeRelease"] -.-> why["Release가 작동하는 지점이 보이는 작성법"]
smart["ComPtr이나 wil::com_ptr"] --> auto["스코프를 벗어나면 Release"]
auto --> rec["신규 코드는 ComPtr부터 시작"]
그림 4: 이 글의 코드는 학습용으로 생 포인터를 그대로 쓴다. 실무의 신규 코드는 스마트 포인터에 맞춘다.
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"]
그림 5: 주요 인터페이스의 계보. 설정이나 형 정보까지 IUnknown을 정점으로 하는 COM 인터페이스로 표현된다.
여기까지 오면 「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 / 샘플 레이트 등"]
그림 6: IMFMediaType은 속성 스토어이며, major type이나 subtype 같은 형식 정보를 GUID 키로 가진다.
이 부분을 「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). 파일이라면 스트림 하나당 한 종류인 경우가 많지만, 웹캠은 여러 형식을 가집니다 - 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이 반환되거나, 통과하더라도 예상과 다른 형식의 버퍼를 읽게 됩니다.
flowchart TB
accTitle: media type negotiation의 4단계
accDescr: GetNativeMediaType으로 네이티브 타입을 열거하고, major type을 확인하고, 원하는 출력 형식을 구성해 SetCurrentMediaType으로 설정하고, 마지막으로 GetCurrentMediaType으로 확정된 형식을 다시 읽는 4단계를 보여준다.
s1["GetNativeMediaType으로 열거"] --> s2["major type을 확인한다"]
s2 --> s3["원하는 형식을 SetCurrentMediaType"]
s3 --> s4["GetCurrentMediaType으로 확정값을 읽는다"]
s1 -.-> stop["MF_E_NO_MORE_TYPES가 열거의 끝"]
그림 7: 형식 맞추기는 4단계. 넘기는 것은 부분적인 지정이므로 확정값은 마지막에 다시 읽는다.
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 오브젝트
그림 8: 열거 API가 반환하는 것은 IMFActivate이며, ActivateObject를 호출해야 비로소 실체의 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의 얼굴이 된다」는 느낌을 꽤 잘 나타냅니다.
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 속성에 설정한 뒤 작성하는 흐름입니다.
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(...)
그림 10: 비동기 모드의 ReadSample은 바로 돌아오며, work queue의 스레드에서 OnReadSample이 호출된다.
callback 주변에서 유의하고 싶은 것은 다음과 같은 점입니다.
- UI 스레드의 STA 오브젝트를 그대로 callback 쪽에서 만지지 않는다
- callback 구현은 스레드 세이프로 한다
- UI 업데이트가 필요하면 결과만 UI 스레드로 되돌린다
- 「Media Foundation의 callback은 어느 스레드에서 오는가」를 처음에 고정해서 생각한다
Media Foundation은 STA 오브젝트의 사정을 알아서 흡수해 주는 것이 아닙니다. 그래서 Media Foundation을 사용하는 워커는 MTA로 맞추고, UI와는 명시적으로 다리를 놓는 편이 정리하기 쉽습니다.
flowchart TB
accTitle: callback과 UI 스레드의 다리 놓는 법
accDescr: callback은 MTA의 work queue 스레드에서 오므로 구현을 스레드 세이프로 하고, STA의 UI 오브젝트를 직접 만지지 않으며, UI 업데이트가 필요하면 결과만 UI 스레드로 되돌린다는 정리를 보여준다.
cb["callback은 MTA의 work queue에서 온다"] --> 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
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을 사용해 부품의 계약을 표현하면서, 그 위에서 미디어 처리 플랫폼으로 동작하는 것입니다. 이 2단 구조로 보면 헤매기 쉽지 않습니다.
flowchart TB
accTitle: COM 계층과 플랫폼 계층의 2단 구조
accDescr: Media Foundation은 COM으로 부품끼리의 계약을 표현하고, 그 위에 Media Session이나 topology나 presentation clock 같은 미디어 파이프라인 고유의 구조를 가지는 2단 구조임을 보여준다.
com2["COM 계층(부품의 계약을 표현)"] --> mf2["미디어 처리 플랫폼 계층"]
mf2 --> own["Media Session이나 topology 등"]
own -.-> note2["COM의 일반론만으로는 끝나지 않는 MF 고유의 개념"]
그림 13: COM을 다시 구운 것이 아니다. COM 계층 위에, 파이프라인을 흘려보내는 MF 고유의 계층이 얹혀 있다.
5. 어디서부터 만질까(입구 선택)
처음 입구를 정할 때는 다음 그림으로 충분한 경우가 많습니다.
flowchart TD
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는 파일이나 디바이스에서 데이터를 꺼내고 싶을 때의 입구로서 꽤 사용하기 쉽습니다.
어울리는 것은 예를 들어 다음과 같은 경우입니다.
- 동영상 파일에서 프레임을 얻고 싶다
- 음성 파일을 디코드해서 샘플을 얻고 싶다
- 카메라에서 프레임을 얻고 싶다
- Media Foundation의 source를 자체 처리 파이프라인에 연결하고 싶다
Source Reader는 필요에 따라 decoder를 읽어 들여 애플리케이션에 데이터를 건네줍니다. 반면 프레젠테이션 클록의 관리나 A/V 동기, 화면 묘화 자체까지는 돌보지 않습니다.
「재생한다」가 아니라 「데이터를 얻는다」를 위한 입구로 생각하면 이해하기 쉽습니다.
flowchart TB
accTitle: Source Reader의 역할 범위
accDescr: Source Reader는 파일이나 카메라에서 데이터를 꺼내 필요에 따라 decoder를 읽어 들여 앱에 건네주지만, 프레젠테이션 클록의 관리나 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이 나설 차례가 되는 것은 다음과 같은 요건이 있을 때입니다.
- 재생 / 정지 / 시크를 다루고 싶다
- 음성과 영상의 동기를 플랫폼 측에 맡기고 싶다
- 품질 제어나 포맷 변경까지 포함해 파이프라인을 다루고 싶다
- topology를 사용해 source / transform / sink의 흐름을 짜고 싶다
이 레이어에 들어가면 Source Reader / Sink Writer보다 「Media Foundation 본체」에 가까워집니다. 그만큼 topology나 session event 등, Media Foundation 고유의 개념도 늘어납니다.
flowchart TB
accTitle: Media Session을 선택하는 판단
accDescr: 재생·정지·시크나 A/V 동기, 품질 제어까지 플랫폼에 맡기고 싶다면 Media Session을 중심으로 생각하고, topology로 흐름을 짜게 되며, 그만큼 Media Foundation 고유의 개념이 늘어난다는 것을 보여준다.
need["재생·시크·동기까지 맡기고 싶다"] --> ms["Media Session을 중심으로 생각한다"]
ms --> topo["topology로 source부터 sink까지 짠다"]
ms -.-> deep["MF 고유의 개념이 그만큼 늘어난다"]
그림 17: 「데이터를 얻고 싶다」가 아니라 「제대로 재생하고 싶다」면 Media Session이 본줄기다.
5.4. 독자 부품을 끼워 넣는다면 MFT
MFT는 Media Foundation의 transform의 공통 모델입니다.
여기에 들어가는 것은 다음과 같은 상황입니다.
- 독자적인 디코더나 인코더를 만들고 싶다
- 영상 처리나 음성 처리의 부품을 파이프라인에 끼워 넣고 싶다
- codec이나 변환기를 열거해서 직접 고르고 싶다
- 기본값의 자동 해결보다 깊이 제어하고 싶다
MFT의 세계에서는 IMFTransform, IMFActivate, media type negotiation, 샘플 / 버퍼 관리 등 COM적인 계약이 꽤 전면에 나옵니다.
그래서 처음 입구로 갑자기 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 중 어느 쪽으로 할지 먼저 정한다 | 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의 절차」에 정리되어 있습니다
flowchart TB
accTitle: 체크리스트에서 우선순위가 높은 3항목
accDescr: 처음의 입구 API를 틀리지 않을 것, apartment를 먼저 정할 것, media type negotiation을 대충 하지 않을 것의 3가지가 특히 우선순위가 높으며, 이것으로 이후 구현의 혼란을 피할 수 있다는 것을 보여준다.
c1["입구 API를 틀리지 않는다"] --> ease["이후 구현의 혼란을 피할 수 있다"]
c2["apartment를 먼저 정한다"] --> ease
c3["형식 맞추기를 대충 하지 않는다"] --> ease
그림 19: 체크리스트 중에서도 이 3가지를 먼저 챙기는 것이 효과적이다.
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과 오브젝트 수명을 정성껏 다룬다
flowchart TB
accTitle: 실무에서 생각하는 순서
accDescr: 먼저 어느 입구가 필요한지 나누고, 다음으로 apartment와 callback의 방침을 정하고, 마지막으로 media type negotiation과 오브젝트 수명을 정성껏 다룬다는 실무 정리 순서를 보여준다.
o1["어느 입구가 필요한지 나눈다"] --> o2["apartment와 callback의 방침을 정한다"]
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
-
[COM의 STA/MTA에서 행을 피하기 위한 기초 지식 KomuraSoft Blog](/ko/blog/2026/01/31/000-sta-mta-com-relationship/) -
[C++의 네이티브 DLL을 C#에서 사용할 때 C++/CLI로 래퍼를 만드는 편이 좋은 이유 KomuraSoft Blog](/ko/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 Studio C++에 그대로...
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파일 완결 코...
Win32 스레드 풀 API ── CreateThreadpoolWork로 「스레드를 만들지 않는」병렬 처리
네이티브 코드에서 CreateThread를 여기저기 만들고 있지는 않은가요. Vista에서 개편된 Win32 스레드 풀 API의 work·timer·wait·io 네 객체, 클린업 그룹, 콜백에서 해서는 안 되는 일까지 1차 정보를 바탕으로 해설...
네임드 파이프의 실무 ── Windows 프로세스 간 통신의 정석을 설계부터 보안까지
Windows 프로세스 간 통신의 정석인 네임드 파이프를 실무 관점에서 정리합니다. 바이트/메시지 모드 선택, 여러 클라이언트를 받는 서버 설계, ACL과 위장 보안, .NET NamedPipeStream까지 1차 정보를 바탕으로 설명합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
ActiveX 이관
COM / ActiveX / OCX 자산을 유지할지, 감쌀지, 교체할지의 단계적 판단을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기존 자산 활용 & 이관 지원
COM / ActiveX / OCX 자산, 네이티브 코드, 32비트 의존성을 유지하면서 단계적인 이관 계획을 지원합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 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적인 계약이 전면에 나오므로 먼저 앞의 세 가지 중 무엇이 정말로 필요한지를 먼저 보는 것을 권합니다.
- Media Foundation의 비동기 콜백에서 주의할 점은 무엇인가요?
- Media Foundation의 비동기 처리는 work queue를 사용하며 그 스레드는 MTA이므로, 애플리케이션 측도 MTA로 맞추면 구현이 단순해집니다. IMFSourceReaderCallback 구현은 스레드 세이프로 하고, UI 스레드의 STA 오브젝트를 callback 측에서 직접 만지지 않는 것이 중요합니다. UI 업데이트가 필요하면 결과만 UI 스레드로 되돌립니다. 또한 Source Reader의 동기·비동기 모드는 작성 시에 정해지며, 나중에 전환할 수 없다는 점에도 주의가 필요합니다.