מבוא ל-Media Foundation: להבין את ה-API דרך COM
· עודכן בתאריך: · Go Komura · Media Foundation, COM, C++, Windows
היסטוריית עדכונים (גרסה ראשונה, פורסמה בתאריך 9 Mar 2026)
- פרסום ראשון
לצטט את המאמר הזה(DOI: 10.5281/zenodo.22173324)
מאמר זה מאוחסן בארכיון Zenodo. להלן גם ה-DOI שתמיד מפנה לגרסה האחרונה וגם ה-DOI המקובע לגרסה שאתם קוראים.
Go Komura (2026). מבוא ל-Media Foundation: להבין את ה-API דרך COM. KomuraSoft LLC. https://doi.org/10.5281/zenodo.22173324 https://comcomponent.com/he/blog/media-foundation-why-it-feels-like-com/
- DOI (הגרסה האחרונה)
- 10.5281/zenodo.22173324
- DOI (הגרסה הזו)
- 10.5281/zenodo.22173325
כשמתחילים לגעת ב-Media Foundation, קל להרגיש “אני אמור להשתמש ב-API של וידאו ואודיו של Windows, אבל פתאום נכנס הרבה COM לתמונה”. CoInitializeEx, MFStartup, IMFSourceReader, IMFMediaType, IMFTransform, IMFActivate, HRESULT, ו-GUID מופיעים בבת אחת, האווירה הופכת פתאום ל-Win32 / COM, וקשה לראות מה זה בעצם Media Foundation.
במאמר הזה לא נסקור את Media Foundation כולו כמו מילון, אלא נכתוב תוך התמקדות בשלוש הנקודות האלה.
- למה, כשמשתמשים ב-Media Foundation, נושא ה-COM עולה באופן טבעי
- היכן הצבע של COM מתחזק
- מאיפה כדאי להתחיל — Source Reader / Sink Writer / Media Session / MFT
דוגמאות הקוד מבוססות C++, אבל הרעיון עצמו כמעט זהה גם כשניגשים דרך wrapper מ-.NET וכדומה.
תוכן עניינים
- המסקנה בקצרה
- מונחים ו-overview
- 2.1. מונחים שתופסים קודם רק את המשמעות
- 2.2. overview של Media Foundation (תרשים)
- הנקודות שבהן Media Foundation מתחיל להיראות כמו COM
- 3.1.
CoInitializeExו-MFStartupמופיעים באתחול יחד - 3.2. העברת אובייקטים ממוקדת ממשקים
- 3.3. הגדרות ומידע טיפוס ממוקדים ב-
IMFAttributesוב-GUID - 3.4. מופיע Activation Object
- 3.5. גם הטיפול באסינכרוני / callback / threads הוא בסגנון COM
- 3.1.
- אבל Media Foundation אינו COM
- מאיפה מתחילים (בחירת נקודת הכניסה)
- 5.1. מקרה שבו נכנסים קודם מ-Source Reader
- 5.2. אם כותבים לקובץ: Sink Writer
- 5.3. אם מטפלים גם ב-playback ובסנכרון: Media Session
- 5.4. אם מכניסים רכיב עצמאי: MFT
- checklist לשימוש בפועל
- סיכום
- מקורות
ב-diagram, solid line מציינת relation שתמיד מתקיים ו-dashed line מציינת relation מותנה (התנאים מופיעים בהסבר של כל relation ב-detail page). הרשימה המלאה של ה-relations (סה”כ 21, כולל evidence ו-certainty) וההגדרות של ה-concepts המרכזיים נמצאות ב-detail page של ה-knowledge map (ביפנית). Data: 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 כשנדרשת בקרת playback, ול-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. מונחים ו-overview
לפני שניכנס לנושא COM, נעבור תחילה על המונחים שנשתמש בהם במאמר, ועל ה-overview של Media Foundation.
2.1. מונחים שתופסים קודם רק את המשמעות
| מונח | המשמעות כאן |
|---|---|
| Media Source | הכניסה שמכניסה נתוני מדיה ל-pipeline. קובץ, רשת, capture device וכדומה |
| MFT | Media Foundation Transform. מודל משותף ל-decoder, encoder, ממיר וידאו וכדומה |
| Media Sink | היעד של נתוני המדיה. תצוגה על המסך, פלט אודיו, כתיבה לקובץ וכדומה |
| Media Session | מנגנון שמנהל את זרימת ה-pipeline כולו. אחראי על playback וסנכרון |
| Topology | תרשים חיבור שמתאר איך מחברים בין source / transform / sink |
| Activation Object | אובייקט עזר ליצירת הגוף בהמשך. מיוצג על ידי IMFActivate |
| Attributes | מאגר key/value עם GUID כמפתח. נעשה בו שימוש נרחב בכל Media Foundation |
| apartment | היחידה שבה COM מרכז threads. הסכם של “מאיזה thread מותר לקרוא לאובייקט הזה”, שנקבע לפי הארגומנט של CoInitializeEx (3.1, 3.5) |
| STA / MTA | סוגי apartment. STA (Single-Threaded Apartment) מקושר ל-thread אחד, וקריאה מ-thread אחר מובאת דרך message pump. MTA (Multi-Threaded Apartment) משתף כמה threads באותו apartment, ואפשר לקרוא ישירות. לפירוט: ידע בסיסי על STA/MTA ב-COM — מודל ה-threading ואיך נמנעים מתקיעה |
| work queue | מנגנון ה-threads ש-Media Foundation מחזיק כדי להריץ עיבוד אסינכרוני. ה-callback נקרא מה-thread הזה (3.5) |
אם מחזיקים את המונחים האלה מראש, ההתקלות בקריאת התיעוד קטנה משמעותית.
apartment יהיה הנושא המרכזי בסעיף 3.5, אבל כאן מספיק לזכור ש”ה-callback של Media Foundation יכול להגיע מ-thread אחר מזה שקרא ל-ReadSample (ה-work queue של MTA)”.
2.2. overview של Media Foundation (תרשים)
Media Foundation, במבט כללי, הוא בעיקר סיפור של media pipeline. נושא ה-COM חשוב, אבל קל יותר ליישר קו אם מסתכלים קודם על ה-overview.
flowchart TB
accTitle: שני אופני השימוש ב-Media Foundation
accDescr: מודל שבו Media Session מנהל את כל ה-pipeline בין Media Source, MFT ו-Media Sink, לעומת מודל שבו האפליקציה עצמה מטפלת בנתונים ישירות בין Source Reader ל-Sink Writer.
subgraph Pipeline["מודל שמשתמש ב-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 מנהל את ה-pipeline כולו, ומודל שבו Reader / Writer מאפשרים לאפליקציה לטפל בנתונים ישירות.
יש בעיקר שני אופני שימוש ב-Media Foundation.
- מודל שמשתמש ב-pipeline כולו
- מחברים source / transform / sink, ו-Media Session מנהל את זרימת הנתונים וסנכרון A/V
- מודל שבו האפליקציה מטפלת בנתונים ישירות
- מוציאים נתונים מה-source עם Source Reader, ומזרימים ל-sink עם Sink Writer
השני קל יותר להיכנס אליו במקרים שרוצים לעבד frames או samples בעצמכם. מצד שני, אם רוצים להשאיר לפלטפורמה גם playback וגם סנכרון, הראשון הוא העיקרי.
חשוב לזכור שמהות 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. enumeration ויצירה מושהית | IMFActivate, ActivateObject |
תוצאת ה-enumeration לא תמיד הגוף עצמו |
| 3.5. אסינכרוני | IMFSourceReaderCallback, work queue |
צריך להיות מודעים ל-callback ול-apartment |
| (פרק 4) בקרת playback | topology, Media Session | זרימת ה-pipeline כולו הוא מושג ייחודי ל-Media Foundation |
רק בקרת ה-playback האחרונה שונה במהות, ולא עניין כללי של COM אלא פונקציונליות של Media Foundation עצמו. לכן נטפל בזה בנפרד בפרק 4.
נעבור לפי הסדר. הקוד לא יהיה דוגמה מלאה, אלא רק קטעים שמראים היכן זה מתחיל להיראות כמו COM.
3.1. CoInitializeEx ו-MFStartup מופיעים באתחול יחד
זו הנקודה הראשונה שרוב האנשים מרגישים בה אי-נוחות. לפני הסיפור של “רוצה לפתוח קובץ” או “רוצה לקבל מהמצלמה”, מופיעים קודם CoInitializeEx ו-MFStartup.
CoInitializeExהוא אתחול ספריית COMMFStartupהוא אתחול פלטפורמת 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 מופיעים יחד, היא הנקודה הראשונה שבה האווירה של COM מתחזקת פתאום כשנוגעים ב-Media Foundation.
flowchart TB
accTitle: שני שלבי האתחול והסיום
accDescr: מאתחלים את ספריית COM עם CoInitializeEx, ולפני השימוש מאתחלים את פלטפורמת Media Foundation עם MFStartup, ובסיום קוראים ל-MFShutdown ול-CoUninitialize בסדר הפוך.
co["CoInitializeEx (אתחול COM)"] --> mfs["MFStartup (אתחול MF)"]
mfs --> use["משתמשים ב-Media Foundation"]
use --> shut["MFShutdown"]
shut --> coun["CoUninitialize"]
איור 3: אתחול COM לבדו לא מספיק. האתחול הוא בשני שלבים, והסיום מוחזר בסדר הפוך.
בפועל, כדאי להחליט מראש בשלב הזה את הדברים הבאים, וזה מקל בהמשך.
- איזה thread ישתמש ב-Media Foundation
- האם ה-thread הזה יהיה STA או MTA
- מי אחראי על
MFStartup/MFShutdownועלCoInitializeEx/CoUninitialize
יש מקרים שבמימוש שכבה אחרת כבר אחראית על אתחול COM. גם אז, בטוח יותר לקבע מראש מי אחראי. אם ממשיכים בלי ליישר קו על זה, זה נעשה מבלבל בהמשך ב-callback ובשילוב עם ה-UI.
הקוד במאמר הזה כותב SafeRelease בעצמו ומנהל מצביעי ממשק גולמיים. הדוגמאות בתיעוד של Microsoft כתובות בצורה הזו, כי כך רואים איפה AddRef / Release פועלים. עם זאת, זה לא סיבה לא להשתמש ב-smart pointers בקוד בפועל. אם כותבים חדש ב-C++, בטוח יותר להתקרב לאחד משני אלה.
| אפשרות | הגוף בפועל | הערה |
|---|---|---|
Microsoft::WRL::ComPtr<T> |
<wrl/client.h> |
מגיע כלול ב-Windows SDK, בלי תלות נוספת. Get() נותן מצביע גולמי, GetAddressOf() / & לארגומנט out, ו-As<U>() כותב QueryInterface |
wil::com_ptr<T> |
wil/com.h של WIL (Windows Implementation Libraries) |
מתקינים בנפרד למשל דרך NuGet. אפשר להשתמש יחד עם עזר להמרת HRESULT לחריגה |
אם כותבים עם ComPtr, לא נדרש השילוב של goto done; ו-SafeRelease שמופיע בהמשך 3.1, ו-Release מתבצע ברגע שיוצאים מה-scope. המאמר הזה משאיר מצביעים גולמיים כדי להעדיף שרואים את המנהג של COM, אבל מומלץ להתחיל קוד חדש מ-ComPtr.
flowchart TB
accTitle: החלוקה בין מצביע גולמי ל-smart pointer
accDescr: הקוד במאמר כתוב עם מצביע גולמי ו-SafeRelease כדי שרואים איפה AddRef ו-Release פועלים, אך קוד חדש בפועל כדאי שיתקרב ל-ComPtr או ל-wil::com_ptr, שמבצעים Release ברגע שיוצאים מה-scope.
raw["מצביע גולמי ו-SafeRelease"] -.-> why["כתיבה שמראה איפה Release פועל"]
smart["ComPtr או wil::com_ptr"] --> auto["Release ברגע שיוצאים מה-scope"]
auto --> rec["קוד חדש כדאי שיתחיל מ-ComPtr"]
איור 4: קוד המאמר נשאר עם מצביע גולמי לצורכי לימוד. קוד חדש בפועל כדאי שיתקרב ל-smart pointer.
3.2. העברת אובייקטים ממוקדת ממשקים
כשקוראים את ה-API של Media Foundation, רוב ערכי ההחזרה והארגומנטים מסוג out הם ממשקי COM.
IMFSourceReaderIMFMediaTypeIMFTransformIMFActivateIMFSampleIMFMediaBuffer
מה שאופייני הוא שלא רק גוף הנתונים, אלא גם מידע הטיפוס ואובייקטי ההגדרה מיוצגים בממשק.
לדוגמה,
IMFTransformהוא ממשק שמייצג MFTIMFAttributesהוא מאגר key/valueIMFMediaTypeהוא “תיאור פורמט המדיה” שיורש מ-IMFAttributes
גם משהו כמו media type, שנראה “כמו נתוני הגדרה”, מוחזק בממשק COM. כאן נכנס באופן טבעי ה-context של IUnknown, QueryInterface, AddRef / Release ו-HRESULT.
flowchart TD
accTitle: שושלת הממשקים המרכזיים ב-Media Foundation
accDescr: IUnknown הוא בסיס לכל הממשקים, כשממנו נגזרים IMFAttributes ואילו ממנו IMFMediaType ו-IMFActivate, וכן נגזרים ישירות מ-IUnknown גם IMFSourceReader וגם IMFTransform.
IUnknown["IUnknown"]
IUnknown --> IMFAttributes["IMFAttributes"]
IMFAttributes --> IMFMediaType["IMFMediaType"]
IMFAttributes --> IMFActivate["IMFActivate"]
IUnknown --> IMFSourceReader["IMFSourceReader"]
IUnknown --> IMFTransform["IMFTransform"]
איור 5: שושלת הממשקים המרכזיים. גם הגדרות וגם מידע טיפוס מיוצגים בממשקי COM עם IUnknown בראש.
עד כאן רואים ש”Media Foundation הוא API למדיה, אבל אופן ייצוג הגבולות שלו די COM”.
3.3. הגדרות ומידע טיפוס ממוקדים ב-IMFAttributes וב-GUID
כשנוגעים ב-Media Foundation, יש נקודה שבה ההגדרות נראות פתאום מלאות ב-GUID. במרכזה IMFAttributes, מאגר key/value עם GUID כמפתח. זה בשימוש נרחב מאוד בכל Media Foundation.
חשוב במיוחד IMFMediaType, שיורש מ-IMFAttributes ומחזיק מידע פורמט המדיה כ-attributes.
לדוגמה, מידע כמו זה.
- major type (אודיו או וידאו)
- subtype (H.264, AAC, RGB32, PCM וכדומה)
- גודל ה-frame
- frame rate
- sample rate
- מספר ערוצים
flowchart LR
accTitle: IMFMediaType כמאגר attributes
accDescr: IMFMediaType מחזיק כמפתחות GUID גם את MF_MT_MAJOR_TYPE, גם את MF_MT_SUBTYPE, וגם פרטים נוספים כמו גודל, FPS ו-sample rate.
MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
MediaType --> Subtype["MF_MT_SUBTYPE"]
MediaType --> Detail["גודל / FPS / sample rate וכדומה"]
איור 6: IMFMediaType הוא מאגר attributes, שמחזיק מידע פורמט כמו major type ו-subtype כמפתחות GUID.
קל להרגיש את זה כ”יער של GUID”, אבל בפועל מה שעושים כאן די פשוט.
- משתמשים במאגר attributes כדי להחזיק הגדרות
- גם media type מיוצג כמאגר attributes
- בין source / transform / sink, מסתכלים על ה-attribute הזו כדי לתאם פורמט
זה פשוט שהייצוג של ההגדרות ומידע הטיפוס משתמש בממשק בסגנון COM וב-GUID.
אם רואים את זה בקוד, זה נראה כך. דוגמה של קריאת frame אחד מווידאו עם 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;
// מוציאים את ה-IMFMediaBuffer מתוך pSample ומעבדים
done:
SafeRelease(&pSample);
SafeRelease(&pType);
SafeRelease(&pReader);
return hr;
}
מה שרואים כאן:
- גם ה-reader וגם ה-media type הם ממשק COM
- ההגדרה מבוססת GUID
- ערך ההחזרה הוא
HRESULT - במצב סינכרוני,
ReadSampleחוסם
גם אם “רק רוצים לקרוא frame אחד”, בגבול של Media Foundation מתקבלות פנים די COM. סיפור המצב הסינכרוני האחרון יטופל בסעיף 3.5.
שלבי media type negotiation (פריט “שלושת הדברים לבדוק ראשונים” ב-checklist של פרק 6)
הקוד למעלה רק מצהיר “רוצים RGB32”, ולכן בפועל נדרשים שלבים סביבו. במקרה של Source Reader, הזרימה שמתעדת Microsoft היא ארבעת השלבים האלה.
- מונים את הטיפוסים הנייטיביים — קוראים ל-
IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType), תוך הגדלתtypeIndexמ-0. כשעוברים את הטווח, מוחזרMF_E_NO_MORE_TYPES, וזה סוף ה-enumeration (אםstreamIndexמחוץ לטווח, מוחזרMF_E_INVALIDSTREAMNUMBER). קובץ בדרך כלל מכיל סוג אחד לזרם, אבל webcam יכולה להכיל כמה פורמטים - בודקים את ה-major type — קוראים
MF_MT_MAJOR_TYPEמה-media type שהתקבל ב-enumeration, וקובעים אם זה אודיו או וידאו. אם מתקדמים בלי לבדוק את זה, אפשר לשלוח הגדרת וידאו לזרם אודיו - מרכיבים ומגדירים את פורמט הפלט הרצוי — יוצרים media type חדש עם
MFCreateMediaType, מגדיריםMF_MT_MAJOR_TYPEו-MF_MT_SUBTYPE, וקוראים ל-SetCurrentMediaType. אם רוצים לקבל דחוס כמו שהוא, מעבירים את הטיפוס שהתקבל בשלב 1 כמו שהוא; אם רוצים שיפוענח, מציינים פורמט לא דחוס (MFVideoFormat_RGB32,MFAudioFormat_PCMוכדומה). את ה-decoder Source Reader טוען אוטומטית - קוראים מחדש את הפורמט שהוחלט — אחרי
SetCurrentMediaTypeקוראים ל-GetCurrentMediaType, ומקבלים את פרטי הפורמט שבאמת הוחלט (גודל frame, stride, sample rate וכדומה). מה שמעבירים בשלב 3 הוא ציון חלקי, ולכן את הערך הסופי קוראים מכאן — זה הסדר הנכון
אם מדלגים על ארבעת השלבים האלה ומתקדמים עם “כנראה זה הפורמט”, מוחזר MF_E_INVALIDMEDIATYPE, או שגם אם עובר, קוראים buffer בפורמט שונה מהצפוי.
flowchart TB
accTitle: ארבעת השלבים של media type negotiation
accDescr: מונים טיפוסים נייטיביים עם GetNativeMediaType, בודקים את ה-major type, מרכיבים ומגדירים את הפורמט הרצוי עם SetCurrentMediaType, ולבסוף קוראים מחדש את הפורמט הסופי עם GetCurrentMediaType.
s1["מונים עם GetNativeMediaType"] --> s2["בודקים את ה-major type"]
s2 --> s3["מגדירים את הפורמט הרצוי עם SetCurrentMediaType"]
s3 --> s4["קוראים ערך סופי עם GetCurrentMediaType"]
s1 -.-> stop["MF_E_NO_MORE_TYPES הוא סוף ה-enumeration"]
איור 7: תיאום הפורמט הוא ארבעה שלבים. מה שמעבירים הוא ציון חלקי, ולכן קוראים את הערך הסופי בסוף.
3.4. מופיע Activation Object
הצבע של COM ב-Media Foundation בולט במיוחד ב-activation object.
IMFActivate הוא אובייקט עזר ליצירת הגוף בהמשך. קל להבין אותו כקרוב ל-class factory של COM.
במקומות שהוא מופיע, ערך ההחזרה של API ה-enumeration לא תמיד “הגוף המוכן לשימוש”, אלא לעיתים תחילה מערך של IMFActivate*.
ואז, רק את מה שנדרש, יוצרים בפועל עם ActivateObject.
sequenceDiagram
accTitle: מ-API ה-enumeration ועד אובייקט COM ממשי דרך IMFActivate
accDescr: האפליקציה קוראת ל-API ה-enumeration ומקבלת מערך IMFActivate, בודקת בו attributes, ורק אחרי קריאה ל-ActivateObject מקבלת בחזרה אובייקט COM ממשי כמו IMFTransform או Sink.
participant App as אפליקציה
participant Enum as API ה-enumeration
participant Act as IMFActivate
participant Obj as IMFTransform / Sink וכדומה
App->>Enum: קורא ל-enumeration
Enum-->>App: מערך IMFActivate*
App->>Act: בודק attributes
App->>Act: ActivateObject(...)
Act-->>App: אובייקט COM ממשי
איור 8: מה ש-API ה-enumeration מחזיר הוא IMFActivate, ורק כשקוראים ל-ActivateObject מתקבל אובייקט COM ממשי.
הצורה הזו מתאימה היטב ל-Media Foundation שמתוכנן למצוא ולהרכיב רכיבים ניתנים להחלפה בהמשך.
בנוסף, מכיוון של-activation object עצמו יכולות להיות attributes, נוצרת זרימה של “קודם רואים את ה-attributes של המועמד”, “אם צריך, מגדירים”, “בהמשך יוצרים בפועל”. גם זה די בסגנון COM.
בפועל, כשמונים MFT עם MFTEnumEx ויוצרים בפועל, זה נראה כך.
HRESULT FindH264Decoder(IMFTransform** ppTransform)
{
*ppTransform = nullptr;
IMFActivate** ppActivate = nullptr;
UINT32 count = 0;
MFT_REGISTER_TYPE_INFO inputType = {};
inputType.guidMajorType = MFMediaType_Video;
inputType.guidSubtype = MFVideoFormat_H264;
HRESULT hr = MFTEnumEx(
MFT_CATEGORY_VIDEO_DECODER,
MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_LOCALMFT,
&inputType,
nullptr,
&ppActivate,
&count);
if (FAILED(hr))
{
return hr;
}
if (count == 0)
{
CoTaskMemFree(ppActivate);
return MF_E_TOPO_CODEC_NOT_FOUND;
}
hr = ppActivate[0]->ActivateObject(
__uuidof(IMFTransform),
reinterpret_cast<void**>(ppTransform));
for (UINT32 i = 0; i < count; ++i)
{
ppActivate[i]->Release();
}
CoTaskMemFree(ppActivate);
return hr;
}
תוצאת ה-enumeration לא מתקבלת מלכתחילה כ-IMFTransform*, אלא כ-IMFActivate**, ורק אחרי קריאה ל-ActivateObject מקבלים סוף-סוף את ה-IMFTransform הממשי. הזרימה הזו מייצגת היטב את התחושה של “Media Foundation מתחיל פתאום להיראות כמו COM”.
flowchart TB
accTitle: הזרימה מ-MFTEnumEx ועד יצירת ה-decoder בפועל
accDescr: כשמונים מועמדי decoder עם MFTEnumEx מתקבל מערך IMFActivate. אם אין מועמדים מחזירים MF_E_TOPO_CODEC_NOT_FOUND, ואם יש יוצרים בפועל עם ActivateObject ומקבלים IMFTransform, ואז משחררים כל IMFActivate ומשחררים את המערך עצמו עם CoTaskMemFree.
enum["מונים מועמדים עם MFTEnumEx"] --> arr["מתקבל מערך IMFActivate"]
arr --> q{"יש מועמדים?"}
q -->|"אין"| nf["MF_E_TOPO_CODEC_NOT_FOUND"]
q -->|"יש"| act["יוצרים בפועל עם ActivateObject"]
act --> obj["מתקבל IMFTransform"]
act -.-> free["משחררים כל איבר"]
free -.-> free2["המערך משוחרר עם CoTaskMemFree"]
איור 9: הזרימה היא enumeration → בדיקת מועמדים → יצירה בפועל → שחרור. תוצאת ה-enumeration אינה הגוף המוכן לשימוש.
3.5. גם הטיפול באסינכרוני / callback / threads הוא בסגנון COM
מה שקל לפספס בשימוש בפועל ב-Media Foundation הוא העיבוד האסינכרוני ומודל ה-threads.
לדוגמה, Source Reader כברירת מחדל הוא במצב סינכרוני. במצב סינכרוני, ReadSample חוסם.
תלוי במצב הקובץ, הרשת או ה-device, ההמתנה הזו יכולה להפוך לזמן שנראה לעין.
אם רוצים מצב אסינכרוני, מעבירים callback בזמן יצירת Source Reader.
מכינים אובייקט שמממש IMFSourceReaderCallback, מגדירים אותו ב-attribute 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, ו-ה-thread של ה-work queue הוא MTA. לכן, כשגם צד האפליקציה מתקרב ל-MTA, המימוש נעשה פשוט יותר.
sequenceDiagram
accTitle: זרימת ReadSample אסינכרוני דרך ה-work queue של MTA
accDescr: thread האפליקציה קורא ל-ReadSample וה-Source Reader חוזר מיד, בעוד העיבוד הפנימי מועבר ל-work queue של MF ב-MTA שקוראת בתורה ל-OnReadSample של ה-callback.
participant App as thread האפליקציה
participant Reader as Source Reader
participant Queue as work queue של MF (MTA)
participant Cb as IMFSourceReaderCallback
App->>Reader: ReadSample(...)
Reader-->>App: חוזר מיד
Reader->>Queue: מעבד באופן פנימי
Queue->>Cb: OnReadSample(...)
איור 10: ה-ReadSample במצב אסינכרוני חוזר מיד, וה-OnReadSample נקרא מה-thread של ה-work queue.
מה שכדאי להיזהר ממנו סביב ה-callback הן הנקודות האלה.
- לא לגעת ישירות באובייקטי STA של UI thread מתוך ה-callback
- מימוש ה-callback צריך להיות thread-safe
- אם נדרש עדכון UI, מחזירים רק את התוצאה ל-UI thread
- לקבוע מראש “מאיזה thread מגיע ה-callback של Media Foundation”
Media Foundation לא סופג אוטומטית את העניינים של אובייקט STA. לכן, טבעי יותר להקדיש worker שמשתמש ב-Media Foundation ל-MTA, ולבנות גשר מפורש לצד ה-UI.
flowchart TB
accTitle: איך בונים גשר בין callback ל-UI thread
accDescr: ה-callback מגיע מ-thread ה-work queue של MTA, ולכן המימוש צריך להיות thread-safe, לא נוגעים ישירות באובייקטי UI של STA, ואם נדרש עדכון מחזירים רק את התוצאה ל-UI thread.
cb["ה-callback מגיע מה-work queue של MTA"] --> safe["המימוש thread-safe"]
cb -.-> ng["לא נוגעים ישירות באובייקטי UI של STA"]
safe --> bridge["מחזירים רק את התוצאה ל-UI thread"]
איור 11: את ה-worker שמשתמש ב-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 pipeline.
לדוגמה, ב-Media Session, כשהאפליקציה מעבירה partial topology, קיימת זרימה שבה ה-topology loader משלים את ה-transform הנדרש ופותר ל-full topology. זה לא סיפור כללי של COM, אלא פונקציונליות ש-Media Foundation מחזיק כפלטפורמת עיבוד מדיה.
flowchart LR
accTitle: השלמת Partial Topology ל-Full Topology
accDescr: כשמעבירים Partial Topology מ-Source ל-Output, ה-Topology Loader משלים אותה ל-Full Topology שכוללת גם את ה-Decoder MFT הנדרש בין ה-Source ל-Output.
Partial["Partial Topology (Source -> Output)"] --> Loader["Topology Loader"]
Loader --> Full["Full Topology (Source -> Decoder MFT -> Output)"]
איור 12: כשמעבירים partial topology, ה-topology loader משלים את ה-transform הנדרש ופותר ל-full topology.
Media Foundation הוא משהו שמייצג את החוזה בין הרכיבים באמצעות COM, ומעליו פועל כפלטפורמת עיבוד מדיה. אם מסתכלים על שני השלבים האלה, קל פחות ללכת לאיבוד.
flowchart TB
accTitle: שני שכבות — שכבת COM ושכבת הפלטפורמה
accDescr: Media Foundation מייצג את החוזה בין הרכיבים באמצעות COM, ומעל זה יש שכבה של פלטפורמת עיבוד מדיה עם Media Session, topology ו-presentation clock — מבנה של שני שלבים.
com2["שכבת COM (החוזה בין הרכיבים)"] --> mf2["שכבת פלטפורמת עיבוד המדיה"]
mf2 --> own["Media Session, topology וכדומה"]
own -.-> note2["מושגים ייחודיים ל-MF שלא מסתיימים בעניין כללי של COM"]
איור 13: זה לא שכפול של COM. מעל שכבת COM יושבת שכבה ייחודית ל-MF שמזרימה את ה-pipeline.
5. מאיפה מתחילים (בחירת נקודת הכניסה)
בבחירת נקודת הכניסה הראשונה, לרוב מספיק התרשים הזה.
flowchart TD
accTitle: בחירת נקודת הכניסה ל-Media Foundation
accDescr: לפי מה שנדרש קודם בוחרים בין Source Reader לקריאת frames או samples, Sink Writer לכתיבה לקובץ, Media Session לבקרת playback וסנכרון A/V, או MFT להכנסת ממיר עצמאי.
Start["מה רוצים לעשות"] --> Q1{"מה נדרש קודם?"}
Q1 -- "רוצים לקרוא frames / samples" --> A1["Source Reader"]
Q1 -- "רוצים לכתוב לקובץ" --> A2["Sink Writer"]
Q1 -- "נדרש בקרת playback או סנכרון A/V" --> A3["Media Session"]
Q1 -- "רוצים להכניס ממיר עצמאי" --> A4["MFT"]
איור 14: בוחרים נקודת כניסה לפי מה שנדרש קודם. אם רוצים לקרוא — Reader, אם לכתוב — Writer, אם לנגן — Session.
בטבלה, זה נראה כך.
| מה רוצים לעשות | מה נוגעים בו קודם | עוצמת COM | הערה |
|---|---|---|---|
| לקבל frames / samples מקובץ או ממצלמה | Source Reader | בינונית | אם צריך, גם ה-decoder מטופל אוטומטית |
| לכתוב אודיו / וידאו שנוצר לקובץ | Sink Writer | בינונית | אם צריך, אפשר לטפל יחד גם ב-encoder וגם ב-media sink |
| לטפל ב-playback, עצירה, seek, סנכרון A/V ובקרת איכות | Media Session | גבוהה | נדרשת הבנה של topology ושל session |
| להכניס ממיר עצמאי או רכיב בסגנון codec | MFT | גבוהה | חושבים סביב IMFTransform |
| לראות את המועמדים שנמנו ולהחליט רק על מה שצריך | IMFActivate |
גבוהה | לעיתים מה שמוחזר הוא activation object ולא הגוף עצמו |
5.1. מקרה שבו נכנסים קודם מ-Source Reader
Source Reader נוח למדי כנקודת כניסה כשרוצים להוציא נתונים מקובץ או מ-device.
מתאים למקרים כמו אלה.
- רוצים לקבל frames מקובץ וידאו
- רוצים לפענח קובץ אודיו ולקבל samples
- רוצים לקבל frames ממצלמה
- רוצים לחבר את ה-source של Media Foundation ל-pipeline העיבוד העצמי
Source Reader טוען decoder לפי הצורך, ומעביר נתונים לאפליקציה. מצד שני, הוא לא מטפל בניהול presentation clock, סנכרון A/V, או ציור המסך עצמו.
קל יותר להבין אותו כ-נקודת כניסה “להוצאת נתונים”, לא “ל-playback”.
flowchart TB
accTitle: היקף התפקיד של Source Reader
accDescr: Source Reader מוציא נתונים מקובץ או ממצלמה, טוען decoder לפי הצורך ומעביר לאפליקציה, אך לא מטפל בניהול presentation clock, סנכרון A/V, או ציור המסך.
src["source כמו קובץ או מצלמה"] --> sr["Source Reader"]
sr --> app["מעביר נתונים לאפליקציה"]
sr -.-> dec["אם צריך, טוען decoder"]
sr -.-> not["לא מטפל ב-playback או סנכרון"]
איור 15: Source Reader הוא נקודת כניסה ל”הוצאת נתונים”, לא ל-playback.
5.2. אם כותבים לקובץ: Sink Writer
Sink Writer הוא נקודת כניסה כשרוצים לכתוב אודיו או וידאו לקובץ.
מבחינת שימוש, אלה טיפוסיים.
- רוצים לשמור frames שיצרתם לקובץ וידאו
- רוצים לקודד samples של אודיו ולכתוב אותן
- רוצים להמיר נתונים שקראתם לפורמט אחר ולשמור
Sink Writer מוצא ומטעין encoder לפי הצורך, ומנהל את זרימת הנתונים ל-media sink. לעיתים קרובות משלבים אותו עם Source Reader, אבל שניהם רכיבים עצמאיים, ואין חובה להשתמש בהם יחד.
flowchart TB
accTitle: היקף התפקיד של Sink Writer
accDescr: כשהאפליקציה מעבירה ל-Sink Writer frames או samples של אודיו שיצרה, הוא טוען לפי הצורך encoder, מנהל את זרימת הנתונים ל-media sink וכותב לקובץ.
app2["frames / אודיו שיצרה האפליקציה"] --> sw["Sink Writer"]
sw -.-> enc["אם צריך, טוען encoder"]
sw --> sink["כותב אל media sink"]
sw -.-> ind["רכיב עצמאי מ-Source Reader"]
איור 16: Sink Writer הוא נקודת כניסה שמטפלת בקידוד ובכתיבה. שימוש יחד עם Reader לא חובה.
5.3. אם מטפלים גם ב-playback ובסנכרון: Media Session
אם לא “רוצה להוציא נתונים” אלא רוצה לנגן כמו שצריך, טבעי יותר לחשוב סביב Media Session.
תורו של Media Session מגיע כשיש דרישות כאלה.
- רוצים לטפל ב-playback / עצירה / seek
- רוצים להשאיר את סנכרון האודיו והווידאו לפלטפורמה
- רוצים לטפל ב-pipeline כולל בקרת איכות ושינוי פורמט
- רוצים להרכיב את זרימת source / transform / sink עם topology
בכניסה לשכבה הזו, מתקרבים יותר ל”גוף Media Foundation” מ-Source Reader / Sink Writer. בהתאם, גדלים גם מושגים ייחודיים ל-Media Foundation כמו topology ו-session event.
flowchart TB
accTitle: ההחלטה לבחור ב-Media Session
accDescr: אם רוצים להשאיר לפלטפורמה גם playback, עצירה, seek, סנכרון A/V ובקרת איכות, חושבים סביב Media Session, בונים את הזרימה עם topology, ומושגים ייחודיים ל-MF גדלים בהתאם.
need["רוצים להשאיר playback, seek וסנכרון"] --> ms["חושבים סביב Media Session"]
ms --> topo["בונים מ-source ל-sink עם topology"]
ms -.-> deep["מושגים ייחודיים ל-MF גדלים בהתאם"]
איור 17: אם לא “רוצים להוציא נתונים” אלא “רוצים לנגן כמו שצריך”, Media Session הוא העיקרי.
5.4. אם מכניסים רכיב עצמאי: MFT
MFT הוא המודל המשותף של ה-transform ב-Media Foundation.
נכנסים לכאן במקרים כאלה.
- רוצים ליצור decoder או encoder עצמאי
- רוצים להכניס ל-pipeline רכיב לעיבוד וידאו או אודיו
- רוצים למנות codec או ממיר ולבחור בעצמכם
- רוצים שליטה עמוקה יותר מהפתרון האוטומטי הרגיל
בעולם ה-MFT, IMFTransform, IMFActivate, media type negotiation, ניהול samples/buffers — החוזה בסגנון COM בולט מאוד.
לכן, קל יותר לבדוק תחילה איזה משלושת Source Reader / Sink Writer / Media Session באמת נחוץ, במקום להיכנס ישר ל-MFT כנקודת כניסה ראשונה.
flowchart TB
accTitle: הבדיקה לפני מעבר ל-MFT
accDescr: כשרוצים להכניס decoder או ממיר עצמאי ל-pipeline מתקדמים ל-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. checklist לשימוש בפועל
לבסוף, נרכז בדף אחד את הנקודות שכדאי לבדוק ראשונות בפועל.
| פריט | מה בודקים | מה נוטה לקרות אם מפספסים |
|---|---|---|
| אחריות האתחול | מחליטים איפה קוראים ל-CoInitializeEx ול-MFStartup, ומי מחזיק את הסיום |
אתחול שדולג, בלבול בסדר הסיום |
| apartment | מחליטים מראש אם ה-thread שנוגע ב-MF יהיה STA או MTA | בלבול סביב ה-callback, התנגשות עם ה-UI |
| מצב Source Reader | מחליטים בזמן היצירה אם סינכרוני או אסינכרוני | ReadSample חוסם שלא כמצופה, אי אפשר להחליף אחר כך |
| media type negotiation | מונים פורמטי פלט, ומציינים במפורש את הפורמט שבו באמת משתמשים. השלבים הם ארבעת השלבים של 3.3 (enumeration עם GetNativeMediaType → בדיקת major type → SetCurrentMediaType → קריאת ערך סופי עם GetCurrentMediaType) |
MF_E_INVALIDMEDIATYPE, מגיע פורמט שונה מהצפוי |
| אורך חיי אובייקטים | מבהירים את האחריות של Release, Unlock, ShutdownObject |
memory leak, החזקת buffer, אי-עקביות בסיום |
| activation object | מבחינים אם תוצאת ה-enumeration היא הגוף עצמו או IMFActivate |
חושבים שאפשר QueryInterface וזה נכשל |
| topology | מבינים אם מטפלים ב-partial topology או ב-full topology | נתקעים בהנחה ש”זה אמור להתחבר אוטומטית” |
| בדיקת שגיאות | בודקים כל פעם HRESULT, דגלי הזרם, ואירועים |
מפספסים כישלון חלקי |
| שילוב UI | לא נוגעים ישירות ב-UI מתוך ה-callback, מחזירים רק את התוצאה ל-UI thread | hang, race, תקלה קשה להבנה |
מה שהעדיפות הכי גבוהה עבורו הן שלוש אלה.
- לא לטעות בבחירת ה-API כנקודת כניסה ראשונה
- קודם מפרידים איזה מ-Source Reader / Sink Writer / Media Session באמת נחוץ
- להחליט מראש על ה-apartment
- אם מערבבים בין ה-UI של STA ל-work queue של Media Foundation, מחליטים מראש איך בונים את הגשר
- לא להתייחס ל-media type negotiation ברשלנות
- אם ממשיכים עם “כנראה זה הפורמט”, זה נעשה בהמשך קשה מאוד להבנה
- השלבים הקונקרטיים מרוכזים ב-“שלבי media type negotiation” בסעיף 3.3
flowchart TB
accTitle: שלושת הפריטים עם העדיפות הגבוהה ביותר ב-checklist
accDescr: שלוש הנקודות עם העדיפות הגבוהה ביותר הן לא לטעות ב-API של נקודת הכניסה, להחליט מראש על ה-apartment, ולא להתייחס ברשלנות ל-media type negotiation, וכולן מקטינות את הבלבול במימוש המאוחר.
c1["לא טועים ב-API של הכניסה"] --> ease["מקטין בלבול במימוש המאוחר"]
c2["מחליטים מראש על ה-apartment"] --> ease
c3["לא מתייחסים ברשלנות לתיאום הפורמט"] --> ease
איור 19: מתוך ה-checklist, שלושת אלה יעילים ביותר לתפוס ראשונים.
7. סיכום
זה לא במקרה שנושא ה-COM מתרבה פתאום כשנוגעים ב-Media Foundation.
- Media Foundation היא פלטפורמת עיבוד מדיה
- הגבול בין source / transform / sink / activation / callback וכדומה מיוצג בממשקי COM
- לכן, נושאי
IUnknown,HRESULT, GUID, apartment ו-callback עולים באופן טבעי - עם זאת, הגוף של Media Foundation הוא media pipeline שמחזיק Media Session ו-topology, ולא רק שכפול של COM
בפועל, קל יותר ליישר קו אם חושבים תחילה בסדר הזה.
- מפרידים תחילה איזה מ-Source Reader / Sink Writer / Media Session / MFT נחוץ
- מחליטים מראש על מדיניות ה-apartment וה-callback
- מטפלים בזהירות ב-media type negotiation ובאורך חיי האובייקטים
flowchart TB
accTitle: הסדר לחשיבה בפועל
accDescr: קודם מפרידים איזו נקודת כניסה נחוצה, אחר כך מחליטים על מדיניות ה-apartment וה-callback, ולבסוף מטפלים בזהירות בתיאום הפורמט ובאורך החיים — סדר החשיבה המומלץ בפועל.
o1["מפרידים איזו נקודת כניסה נחוצה"] --> o2["מחליטים על מדיניות apartment ו-callback"]
o2 --> o3["מטפלים בזהירות בתיאום הפורמט ובאורך החיים"]
איור 20: הסדר לחשיבה בפועל. בחירת נקודת הכניסה, מדיניות ה-threads, ואז הפורמט ואורך החיים.
אין צורך להבין הכול מיד מההתחלה. אם מסתכלים תחילה על “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
-
[ידע בסיסי על STA/MTA ב-COM — מודל ה-threading ואיך נמנעים מתקיעה KomuraSoft Blog](/he/blog/sta-mta-com-relationship/) -
[קריאה מ-C# ל-DLL נייטיב: עטיפת C++/CLI מול P/Invoke KomuraSoft Blog](/he/blog/cpp-cli-wrapper-for-native-dlls/)
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
איך לשלוף still מ-MP4 בזמן נתון עם Media Foundation
עם Source Reader שולפים מ-MP4 את ה-frame הקרוב לזמן שביקשת, מיישרים stride ו-alpha של RGB32, ושומרים כ-PNG.
Time Travel Debugging — להקליט ולהריץ אחורה באגים שלא משתחררים באפליקציות ארוכות-ריצה
באג פעם בחודש משאיר ב-crash dump רק את התוצאה. מקליטים ומריצים אחורה את הביצוע עם WinDbg Time Travel Debugging (TTD): TTD.exe, ring buffe...
למה arguments נשברים — כללי command-line arguments ב-Windows
Windows מעביר ל-CreateProcess מחרוזת אחת שהמקבל מפצל. מכסה את כללי CommandLineToArgvW, CRT ו-.NET, ArgumentList, ובניה ב-C++.
WinRT הוא COM — IInspectable, .winmd, language projections, ולמה WinUI עדיין יושב על חוזה בינארי
WinRT אינו managed runtime אלא ABI שנבנה על COM ועליו metadata של .winmd ו-language projections. מכסה IUnknown מול IInspectable, הגדרת HW...
מה נשאר אחרי שה-parent מת — מחזיקים child processes ב-Job Object
למה SDK helpers שורדים UI שנהרג ומחזיקים את המצלמה או את ה-COM port? מתכננים משך חיים של child process עם Job Objects, KillOnJobClose ו-c...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
עיבוד מדיה ב-Windows שכולל Media Foundation, COM ו-HRESULT קרוב לנושאי המימוש שמטופלים כפיתוח אפליקציות Windows.
ייעוץ טכני וסקירת תכנון
אם רוצים ליישר קו מראש סביב גבולות בסגנון COM וסדר האתחול, אפשר להיכנס לזה מצד התכנון דרך ייעוץ טכני ו-design review.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה זה 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. בפועל כדאי להחליט מראש איזה thread ישתמש ב-Media Foundation, האם הוא יהיה STA או MTA, ומי אחראי על MFStartup / MFShutdown ו-CoInitializeEx / CoUninitialize — זה מקל אחר כך על ה-callback והשילוב עם ה-UI.
- איך בוחרים בין Source Reader, Sink Writer, Media Session ו-MFT?
- אם רוצים להוציא frames או samples מקובץ או ממצלמה, נקודת הכניסה היא Source Reader; אם רוצים לכתוב אודיו/וידאו שיצרתם לקובץ, זה Sink Writer. אם רוצים להשאיר לפלטפורמה את ה-playback, העצירה, ה-seek, סנכרון A/V ובקרת איכות, חושבים סביב Media Session. אם רוצים להכניס ל-pipeline decoder או ממיר משלכם, מתקדמים ל-MFT, אבל כי החוזה בסגנון COM בולט שם מאוד, מומלץ לבדוק קודם איזה משלושת הראשונים באמת נחוץ.
- מה חשוב ב-callback האסינכרוני של Media Foundation?
- העיבוד האסינכרוני של Media Foundation משתמש ב-work queue, וה-thread שלו הוא MTA, ולכן אם גם צד האפליקציה מתקרב ל-MTA, המימוש נעשה פשוט יותר. המימוש של IMFSourceReaderCallback צריך להיות thread-safe, וחשוב לא לגעת ישירות באובייקטי STA של UI thread מתוך ה-callback. אם נדרש עדכון UI, מחזירים רק את התוצאה ל-UI thread. כמו כן, המצב הסינכרוני/אסינכרוני של Source Reader נקבע בזמן היצירה, ואי אפשר להחליף אותו אחר כך.