מבוא ל-Media Foundation: להבין את ה-API דרך COM

· עודכן בתאריך: · · 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 וכדומה.

תוכן עניינים

  1. המסקנה בקצרה
  2. מונחים ו-overview
    • 2.1. מונחים שתופסים קודם רק את המשמעות
    • 2.2. overview של Media Foundation (תרשים)
  3. הנקודות שבהן Media Foundation מתחיל להיראות כמו COM
    • 3.1. CoInitializeEx ו-MFStartup מופיעים באתחול יחד
    • 3.2. העברת אובייקטים ממוקדת ממשקים
    • 3.3. הגדרות ומידע טיפוס ממוקדים ב-IMFAttributes וב-GUID
    • 3.4. מופיע Activation Object
    • 3.5. גם הטיפול באסינכרוני / callback / threads הוא בסגנון COM
  4. אבל Media Foundation אינו COM
  5. מאיפה מתחילים (בחירת נקודת הכניסה)
    • 5.1. מקרה שבו נכנסים קודם מ-Source Reader
    • 5.2. אם כותבים לקובץ: Sink Writer
    • 5.3. אם מטפלים גם ב-playback ובסנכרון: Media Session
    • 5.4. אם מכניסים רכיב עצמאי: MFT
  6. checklist לשימוש בפועל
  7. סיכום
  8. מקורות

ב-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” נעשה הרבה יותר ברור.

הקשר בין Media Foundation ל-COMגוף Media Foundation הוא פלטפורמת עיבוד מדיה ולא כל ה-API הוא COM טהור, אך מכיוון שהגבול בין הרכיבים מיוצג בממשקי COM, נושאי IUnknown, HRESULT ו-GUID עולים באופן טבעי.Media Foundationפלטפורמת עיבוד מדיההגבול בין הרכיבים הוא ממשק COMעולים IUnknown, HRESULT, GUIDלא כל ה-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.

שני אופני השימוש ב-Media Foundationמודל שבו Media Session מנהל את כל ה-pipeline בין Media Source, MFT ו-Media Sink, לעומת מודל שבו האפליקציה עצמה מטפלת בנתונים ישירות בין Source Reader ל-Sink Writer.מודל שבו האפליקציה מטפלת בנתונים ישירותSource Reader (+ decoder)Media SourceאפליקציהSink Writer (+ encoder)Media Sinkמודל שמשתמש ב-pipeline כולוMFTMedia SourceMedia SinkMedia Session

איור 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 הוא אתחול ספריית 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 מופיעים יחד, היא הנקודה הראשונה שבה האווירה של COM מתחזקת פתאום כשנוגעים ב-Media Foundation.

שני שלבי האתחול והסיוםמאתחלים את ספריית COM עם CoInitializeEx, ולפני השימוש מאתחלים את פלטפורמת Media Foundation עם MFStartup, ובסיום קוראים ל-MFShutdown ול-CoUninitialize בסדר הפוך.CoInitializeEx (אתחול COM)MFStartup (אתחול MF)משתמשים ב-Media FoundationMFShutdownCoUninitialize

איור 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.

החלוקה בין מצביע גולמי ל-smart pointerהקוד במאמר כתוב עם מצביע גולמי ו-SafeRelease כדי שרואים איפה AddRef ו-Release פועלים, אך קוד חדש בפועל כדאי שיתקרב ל-ComPtr או ל-wil::com_ptr, שמבצעים Release ברגע שיוצאים מה-scope.מצביע גולמי ו-SafeReleaseכתיבה שמראה איפה Release פועלComPtr או wil::com_ptrRelease ברגע שיוצאים מה-scopeקוד חדש כדאי שיתחיל מ-ComPtr

איור 4: קוד המאמר נשאר עם מצביע גולמי לצורכי לימוד. קוד חדש בפועל כדאי שיתקרב ל-smart pointer.

3.2. העברת אובייקטים ממוקדת ממשקים

כשקוראים את ה-API של Media Foundation, רוב ערכי ההחזרה והארגומנטים מסוג out הם ממשקי COM.

  • IMFSourceReader
  • IMFMediaType
  • IMFTransform
  • IMFActivate
  • IMFSample
  • IMFMediaBuffer

מה שאופייני הוא שלא רק גוף הנתונים, אלא גם מידע הטיפוס ואובייקטי ההגדרה מיוצגים בממשק.

לדוגמה,

  • IMFTransform הוא ממשק שמייצג MFT
  • IMFAttributes הוא מאגר key/value
  • IMFMediaType הוא “תיאור פורמט המדיה” שיורש מ-IMFAttributes

גם משהו כמו media type, שנראה “כמו נתוני הגדרה”, מוחזק בממשק COM. כאן נכנס באופן טבעי ה-context של IUnknown, QueryInterface, AddRef / Release ו-HRESULT.

שושלת הממשקים המרכזיים ב-Media FoundationIUnknown הוא בסיס לכל הממשקים, כשממנו נגזרים IMFAttributes ואילו ממנו IMFMediaType ו-IMFActivate, וכן נגזרים ישירות מ-IUnknown גם IMFSourceReader וגם IMFTransform.IUnknownIMFAttributesIMFMediaTypeIMFActivateIMFSourceReaderIMFTransform

איור 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
  • מספר ערוצים
IMFMediaType כמאגר attributesIMFMediaType מחזיק כמפתחות GUID גם את MF_MT_MAJOR_TYPE, גם את MF_MT_SUBTYPE, וגם פרטים נוספים כמו גודל, FPS ו-sample rate.IMFMediaTypeMF_MT_MAJOR_TYPEMF_MT_SUBTYPEגודל / 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,
        &timestamp,
        &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 היא ארבעת השלבים האלה.

  1. מונים את הטיפוסים הנייטיביים — קוראים ל-IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType), תוך הגדלת typeIndex מ-0. כשעוברים את הטווח, מוחזר MF_E_NO_MORE_TYPES, וזה סוף ה-enumeration (אם streamIndex מחוץ לטווח, מוחזר MF_E_INVALIDSTREAMNUMBER). קובץ בדרך כלל מכיל סוג אחד לזרם, אבל webcam יכולה להכיל כמה פורמטים
  2. בודקים את ה-major type — קוראים MF_MT_MAJOR_TYPE מה-media type שהתקבל ב-enumeration, וקובעים אם זה אודיו או וידאו. אם מתקדמים בלי לבדוק את זה, אפשר לשלוח הגדרת וידאו לזרם אודיו
  3. מרכיבים ומגדירים את פורמט הפלט הרצוי — יוצרים media type חדש עם MFCreateMediaType, מגדירים MF_MT_MAJOR_TYPE ו-MF_MT_SUBTYPE, וקוראים ל-SetCurrentMediaType. אם רוצים לקבל דחוס כמו שהוא, מעבירים את הטיפוס שהתקבל בשלב 1 כמו שהוא; אם רוצים שיפוענח, מציינים פורמט לא דחוס (MFVideoFormat_RGB32, MFAudioFormat_PCM וכדומה). את ה-decoder Source Reader טוען אוטומטית
  4. קוראים מחדש את הפורמט שהוחלט — אחרי SetCurrentMediaType קוראים ל-GetCurrentMediaType, ומקבלים את פרטי הפורמט שבאמת הוחלט (גודל frame, stride, sample rate וכדומה). מה שמעבירים בשלב 3 הוא ציון חלקי, ולכן את הערך הסופי קוראים מכאן — זה הסדר הנכון

אם מדלגים על ארבעת השלבים האלה ומתקדמים עם “כנראה זה הפורמט”, מוחזר MF_E_INVALIDMEDIATYPE, או שגם אם עובר, קוראים buffer בפורמט שונה מהצפוי.

ארבעת השלבים של media type negotiationמונים טיפוסים נייטיביים עם GetNativeMediaType, בודקים את ה-major type, מרכיבים ומגדירים את הפורמט הרצוי עם SetCurrentMediaType, ולבסוף קוראים מחדש את הפורמט הסופי עם GetCurrentMediaType.מונים עם GetNativeMediaTypeבודקים את ה-major typeמגדירים את הפורמט הרצוי עם SetCurrentMediaTypeקוראים ערך סופי עם GetCurrentMediaTypeMF_E_NO_MORE_TYPES הוא סוף ה-enumeration

איור 7: תיאום הפורמט הוא ארבעה שלבים. מה שמעבירים הוא ציון חלקי, ולכן קוראים את הערך הסופי בסוף.

3.4. מופיע Activation Object

הצבע של COM ב-Media Foundation בולט במיוחד ב-activation object.

IMFActivate הוא אובייקט עזר ליצירת הגוף בהמשך. קל להבין אותו כקרוב ל-class factory של COM.

במקומות שהוא מופיע, ערך ההחזרה של API ה-enumeration לא תמיד “הגוף המוכן לשימוש”, אלא לעיתים תחילה מערך של IMFActivate*. ואז, רק את מה שנדרש, יוצרים בפועל עם ActivateObject.

מ-API ה-enumeration ועד אובייקט COM ממשי דרך IMFActivateהאפליקציה קוראת ל-API ה-enumeration ומקבלת מערך IMFActivate, בודקת בו attributes, ורק אחרי קריאה ל-ActivateObject מקבלת בחזרה אובייקט COM ממשי כמו IMFTransform או Sink.IMFTransform / Sink וכדומהIMFActivateAPI ה-enumerationאפליקציהIMFTransform / Sink וכדומהIMFActivateAPI ה-enumerationאפליקציהקורא ל-enumerationמערך IMFActivate*בודק attributesActivateObject(...)אובייקט 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”.

הזרימה מ-MFTEnumEx ועד יצירת ה-decoder בפועלכשמונים מועמדי decoder עם MFTEnumEx מתקבל מערך IMFActivate. אם אין מועמדים מחזירים MF_E_TOPO_CODEC_NOT_FOUND, ואם יש יוצרים בפועל עם ActivateObject ומקבלים IMFTransform, ואז משחררים כל IMFActivate ומשחררים את המערך עצמו עם CoTaskMemFree.איןישמונים מועמדים עם MFTEnumExמתקבל מערך IMFActivateיש מועמדים?MF_E_TOPO_CODEC_NOT_FOUNDיוצרים בפועל עם ActivateObjectמתקבל IMFTransformמשחררים כל איברהמערך משוחרר עם 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, המימוש נעשה פשוט יותר.

זרימת ReadSample אסינכרוני דרך ה-work queue של MTAthread האפליקציה קורא ל-ReadSample וה-Source Reader חוזר מיד, בעוד העיבוד הפנימי מועבר ל-work queue של MF ב-MTA שקוראת בתורה ל-OnReadSample של ה-callback.IMFSourceReaderCallbackwork queue של MF (MTA)Source Readerthread האפליקציהIMFSourceReaderCallbackwork queue של MF (MTA)Source Readerthread האפליקציהReadSample(...)חוזר מידמעבד באופן פנימי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.

איך בונים גשר בין callback ל-UI threadה-callback מגיע מ-thread ה-work queue של MTA, ולכן המימוש צריך להיות thread-safe, לא נוגעים ישירות באובייקטי UI של STA, ואם נדרש עדכון מחזירים רק את התוצאה ל-UI thread.ה-callback מגיע מה-work queue של MTAהמימוש thread-safeלא נוגעים ישירות באובייקטי UI של STAמחזירים רק את התוצאה ל-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 מחזיק כפלטפורמת עיבוד מדיה.

השלמת Partial Topology ל-Full Topologyכשמעבירים Partial Topology מ-Source ל-Output, ה-Topology Loader משלים אותה ל-Full Topology שכוללת גם את ה-Decoder MFT הנדרש בין ה-Source ל-Output.Partial Topology (Source -> Output)Topology LoaderFull Topology (Source -> Decoder MFT -> Output)

איור 12: כשמעבירים partial topology, ה-topology loader משלים את ה-transform הנדרש ופותר ל-full topology.

Media Foundation הוא משהו שמייצג את החוזה בין הרכיבים באמצעות COM, ומעליו פועל כפלטפורמת עיבוד מדיה. אם מסתכלים על שני השלבים האלה, קל פחות ללכת לאיבוד.

שני שכבות — שכבת COM ושכבת הפלטפורמהMedia Foundation מייצג את החוזה בין הרכיבים באמצעות COM, ומעל זה יש שכבה של פלטפורמת עיבוד מדיה עם Media Session, topology ו-presentation clock — מבנה של שני שלבים.שכבת COM (החוזה בין הרכיבים)שכבת פלטפורמת עיבוד המדיהMedia Session, topology וכדומהמושגים ייחודיים ל-MF שלא מסתיימים בעניין כללי של COM

איור 13: זה לא שכפול של COM. מעל שכבת COM יושבת שכבה ייחודית ל-MF שמזרימה את ה-pipeline.

5. מאיפה מתחילים (בחירת נקודת הכניסה)

בבחירת נקודת הכניסה הראשונה, לרוב מספיק התרשים הזה.

בחירת נקודת הכניסה ל-Media Foundationלפי מה שנדרש קודם בוחרים בין Source Reader לקריאת frames או samples, Sink Writer לכתיבה לקובץ, Media Session לבקרת playback וסנכרון A/V, או MFT להכנסת ממיר עצמאי.רוצים לקרוא frames / samplesרוצים לכתוב לקובץנדרש בקרת playback או סנכרון A/Vרוצים להכניס ממיר עצמאימה רוצים לעשותמה נדרש קודם?Source ReaderSink WriterMedia SessionMFT

איור 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”.

היקף התפקיד של Source ReaderSource Reader מוציא נתונים מקובץ או ממצלמה, טוען decoder לפי הצורך ומעביר לאפליקציה, אך לא מטפל בניהול presentation clock, סנכרון A/V, או ציור המסך.source כמו קובץ או מצלמהSource Readerמעביר נתונים לאפליקציהאם צריך, טוען decoderלא מטפל ב-playback או סנכרון

איור 15: Source Reader הוא נקודת כניסה ל”הוצאת נתונים”, לא ל-playback.

5.2. אם כותבים לקובץ: Sink Writer

Sink Writer הוא נקודת כניסה כשרוצים לכתוב אודיו או וידאו לקובץ.

מבחינת שימוש, אלה טיפוסיים.

  • רוצים לשמור frames שיצרתם לקובץ וידאו
  • רוצים לקודד samples של אודיו ולכתוב אותן
  • רוצים להמיר נתונים שקראתם לפורמט אחר ולשמור

Sink Writer מוצא ומטעין encoder לפי הצורך, ומנהל את זרימת הנתונים ל-media sink. לעיתים קרובות משלבים אותו עם Source Reader, אבל שניהם רכיבים עצמאיים, ואין חובה להשתמש בהם יחד.

היקף התפקיד של Sink Writerכשהאפליקציה מעבירה ל-Sink Writer frames או samples של אודיו שיצרה, הוא טוען לפי הצורך encoder, מנהל את זרימת הנתונים ל-media sink וכותב לקובץ.frames / אודיו שיצרה האפליקציהSink Writerאם צריך, טוען encoderכותב אל media sinkרכיב עצמאי מ-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.

ההחלטה לבחור ב-Media Sessionאם רוצים להשאיר לפלטפורמה גם playback, עצירה, seek, סנכרון A/V ובקרת איכות, חושבים סביב Media Session, בונים את הזרימה עם topology, ומושגים ייחודיים ל-MF גדלים בהתאם.רוצים להשאיר playback, seek וסנכרוןחושבים סביב Media Sessionבונים מ-source ל-sink עם topologyמושגים ייחודיים ל-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 כנקודת כניסה ראשונה.

הבדיקה לפני מעבר ל-MFTכשרוצים להכניס decoder או ממיר עצמאי ל-pipeline מתקדמים ל-MFT, אך מכיוון שהחוזה בסגנון COM בולט שם, כדאי לבדוק קודם אם Source Reader, Sink Writer או Media Session מספיקים.לאכןקודם בודקים אם שלושת נקודות הכניסה האחרות מספיקותנדרש רכיב המרה עצמאי?ממשיכים עם Reader, Writer או Sessionמתקדמים ל-MFT (IMFTransform)החוזה בסגנון 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, תקלה קשה להבנה

מה שהעדיפות הכי גבוהה עבורו הן שלוש אלה.

  1. לא לטעות בבחירת ה-API כנקודת כניסה ראשונה
    • קודם מפרידים איזה מ-Source Reader / Sink Writer / Media Session באמת נחוץ
  2. להחליט מראש על ה-apartment
    • אם מערבבים בין ה-UI של STA ל-work queue של Media Foundation, מחליטים מראש איך בונים את הגשר
  3. לא להתייחס ל-media type negotiation ברשלנות
    • אם ממשיכים עם “כנראה זה הפורמט”, זה נעשה בהמשך קשה מאוד להבנה
    • השלבים הקונקרטיים מרוכזים ב-“שלבי media type negotiation” בסעיף 3.3
שלושת הפריטים עם העדיפות הגבוהה ביותר ב-checklistשלוש הנקודות עם העדיפות הגבוהה ביותר הן לא לטעות ב-API של נקודת הכניסה, להחליט מראש על ה-apartment, ולא להתייחס ברשלנות ל-media type negotiation, וכולן מקטינות את הבלבול במימוש המאוחר.לא טועים ב-API של הכניסהמקטין בלבול במימוש המאוחרמחליטים מראש על ה-apartmentלא מתייחסים ברשלנות לתיאום הפורמט

איור 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

בפועל, קל יותר ליישר קו אם חושבים תחילה בסדר הזה.

  1. מפרידים תחילה איזה מ-Source Reader / Sink Writer / Media Session / MFT נחוץ
  2. מחליטים מראש על מדיניות ה-apartment וה-callback
  3. מטפלים בזהירות ב-media type negotiation ובאורך חיי האובייקטים
הסדר לחשיבה בפועלקודם מפרידים איזו נקודת כניסה נחוצה, אחר כך מחליטים על מדיניות ה-apartment וה-callback, ולבסוף מטפלים בזהירות בתיאום הפורמט ובאורך החיים — סדר החשיבה המומלץ בפועל.מפרידים איזו נקודת כניסה נחוצהמחליטים על מדיניות apartment ו-callbackמטפלים בזהירות בתיאום הפורמט ובאורך החיים

איור 20: הסדר לחשיבה בפועל. בחירת נקודת הכניסה, מדיניות ה-threads, ואז הפורמט ואורך החיים.

אין צורך להבין הכול מיד מההתחלה. אם מסתכלים תחילה על “Media Foundation היא פלטפורמת עיבוד מדיה, ו-COM נכנס לעומק בגבולות שלה”, גם התיעוד וגם הקוד נעשים הרבה יותר קלים לעקוב אחריהם.

8. מקורות

מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.

העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.

המאמר קשור ישירות לשירותים הבאים.

שאלות נפוצות

שאלות נפוצות בפניות בנושא המאמר.

מה זה 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 נקבע בזמן היצירה, ואי אפשר להחליף אותו אחר כך.

פרופיל הכותב

עמוד היכרות עם כותב המאמר.

Go Komura

מנהל KomuraSoft LLC

מתמחה בפיתוח תוכנה עבור Windows, ייעוץ טכני וחקירת תקלות, בעיקר בפרויקטים עם מערכות קיימות ובאגים שקשה לשחזר.

קישורים ציבוריים

חזרה לבלוג