Media Foundation 입문 - COM 관점으로 API 이해하기
· 업데이트: · Go Komura · Media Foundation, COM, C++, Windows 개발
수정 이력(10건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 일본어 원문의 완역으로 다시 번역했습니다. 기존 한국어판은 원문의 일부만 옮긴 축약본이라 절, 표, Mermaid 그림, 그림 설명, FAQ가 빠져 있었습니다. 일본어 원문에 맞춰 이들을 모두 복원했고, 기술적인 주장은 일본어판과 같습니다.
- 리뷰 지적을 반영해, 오늘 추가한 그림 가운데 폭이 너무 컸던 것을 세로 구성으로 고치고, 일부 그림과 캡션 표현을 본문 서술과 맞게 바로잡았습니다. 본문 문장은 바꾸지 않았습니다.
- 초기화 순서와 API 선택 흐름을 그림으로도 따라갈 수 있도록 Mermaid 그림을 13개 추가했습니다(본문 500~750자당 1그림 규약에 맞춘 것입니다). 기존 그림에는 일련번호가 있는 캡션을 붙였습니다. 본문 문장은 바꾸지 않았습니다.
- 글 맨 앞에 '이 글의 지식 맵' 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건)에 대한 대응으로 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
- 전체 그림에서 독자에서 앱으로 돌아가는 화살표가 빠져 있던 것을 고치고, Source→Reader→앱→Writer→Sink 흐름이 그림으로 성립하도록 했습니다. 용어표에 apartment와 work queue를 추가하고, `ComPtr`과 `wil::com_ptr`의 비교표, 미디어 타입 협상의 4단계를 추가했습니다.
- 검색 결과에 나오는 제목이 글의 주제(COM 관점)와 어긋나 있던 것을, 제목과 맞는 표현으로 고쳤습니다. 본문과 제목, 인용에 쓰는 제목은 바꾸지 않았습니다.
- 참고 링크 등에서 세로줄(파이프) 기호가 들어간 줄이 표로 표시되어 링크를 누를 수 없었던 표시 오류를 수정했습니다. 본문 내용은 바꾸지 않았습니다.
- 내용은 바꾸지 않고 구성을 정리했습니다. 용어와 전체 그림을 먼저 보이고, 코드 예를 대응하는 해설 안으로 옮겼으며, 중복되어 있던 결론과 빠른 참조표를 한곳으로 모았습니다. 수정 전 버전 보기 (DOI: 10.5281/zenodo.21589600)
- 최초 공개
이 글을 인용하기(DOI: 10.5281/zenodo.21635098)
이 글은 Zenodo에 보관되어 있습니다. 항상 최신 버전으로 연결되는 DOI와 지금 보고 있는 버전에 고정된 DOI를 아래에 함께 제시합니다.
Go Komura (2026). 「Media Foundation 입문 - COM 관점으로 API 이해하기」. 합동회사 코무라소프트. https://doi.org/10.5281/zenodo.21635098 https://comcomponent.com/ko/blog/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에서 행(hang)을 피하는 기초 지식 |
| 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의 색이 진해지는 지점은 대략 다음 다섯으로 정리할 수 있습니다.
| 지점 | 무엇이 나오는가 | 먼저 이해하고 싶은 것 |
|---|---|---|
| 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로 동영상에서 프레임 하나만 읽는 예입니다.
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이 블록한다
“프레임 하나만 읽고 싶다”만으로도, Media Foundation의 경계에서는 꽤 COM적인 면모가 됩니다. 마지막 동기 모드 이야기는 3.5에서 다룹니다.
media type negotiation의 절차(6장 체크리스트에서 “먼저 봐 두고 싶은 세 가지”로 드는 항목입니다)
위 코드는 “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 스레드로 되돌린다 | 행(hang), 경합, 알기 어려운 문제 |
특히 우선순위가 높은 것은 다음 세 가지입니다.
- 처음의 입구 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: 체크리스트에서 우선순위가 높은 세 항목
accDescr: 처음의 입구 API를 틀리지 말 것, apartment를 먼저 정할 것, media type negotiation을 대충 하지 말 것 세 가지가 특히 우선순위가 높고, 이것으로 이후 구현의 혼란을 피할 수 있다는 것을 나타낸다.
c1["입구 API를 틀리지 않는다"] --> ease["이후 구현의 혼란을 피할 수 있다"]
c2["apartment를 먼저 정한다"] --> ease
c3["형식 맞춤을 대충 하지 않는다"] --> ease
그림 19: 체크리스트 안에서도, 이 셋을 처음에 짚는 것이 효과가 크다.
7. 정리
Media Foundation을 다루다 갑자기 COM 이야기가 늘어나는 것은 우연이 아닙니다.
- Media Foundation은 미디어 처리 플랫폼이다
- 그 source / transform / sink / activation / callback 등의 경계는 COM 인터페이스로 표현된다
- 그래서
IUnknown,HRESULT, GUID, apartment, callback 이야기가 자연스럽게 나온다 - 다만 Media Foundation의 본체는 Media Session이나 topology를 가진 미디어 파이프라인이며, 단순한 COM을 그대로 옮긴 것이 아니다
실무에서는 먼저 다음 순서로 생각하면 꽤 정리하기 쉽습니다.
- 애초에 Source Reader / Sink Writer / Media Session / MFT 중 무엇이 필요한지를 나눈다
- apartment와 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에서 행(hang)을 피하는 기초 지식 | 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로 저장하는 구현 절차를 정리합니다.
Win32 스레드 풀 API ── CreateThreadpoolWork로 「스레드를 만들지 않는」 병렬 처리
네이티브 코드에서 CreateThread를 마구 늘리고 있지는 않은가요. Vista에서 개편된 Win32 스레드 풀 API의 work·timer·wait·io 네 객체, 클린업 그룹, 콜백에서 해서는 안 되는 일까지 1차 정보를 바탕으로 설명합니다.
Named Pipe 실무 ── Windows 프로세스 간 통신의 정석을 설계부터 보안까지
Windows의 프로세스 간 통신의 정석인 Named Pipe를 실무 관점에서 해설합니다. 바이트/메시지 모드 선택, 여러 클라이언트를 처리하는 서버 설계, ACL과 impersonation 보안, .NET NamedPipeStream까지 1차 ...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
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적인 계약이 전면에 나오므로, 먼저 앞의 세 가지 중 무엇이 정말 필요한지를 보는 것이 좋습니다.
- Media Foundation의 비동기 콜백에서 주의할 점은 무엇인가요?
- Media Foundation의 비동기 처리는 work queue를 쓰며, 그 스레드는 MTA이므로 앱 쪽도 MTA로 맞추면 구현이 단순해집니다. IMFSourceReaderCallback 구현은 스레드 안전하게 하고, UI 스레드의 STA 객체를 callback 쪽에서 직접 건드리지 않는 것이 중요합니다. UI를 갱신해야 하면 결과만 UI 스레드로 되돌립니다. 또한 Source Reader의 동기·비동기 모드는 만들 때 정해지며 나중에 바꿀 수 없다는 점에도 주의가 필요합니다.