מבוא ל-Media Foundation — מבינים את ה-API מנקודת המבט של COM

· עודכן בתאריך: · · Media Foundation, COM, C++, פיתוח Windows

כשמתחילים לגעת ב-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++, אבל הרעיון עצמו כמעט זהה גם כשניגשים דרך עטיפה מ-‎.NET וכדומה.

תוכן עניינים

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

מפת הידע של המאמר

המאמר הזה מסביר שMedia Foundation היא פלטפורמת עיבוד מדיה המבוססת על COM, דרך חמש נקודות: אתחול, העברת אובייקטים, הגדרות, מנייה, ועיבוד אסינכרוני. אתחול ספריית COM (CoInitializeEx) הוא תנאי מוקדם לאתחול של Media Foundation עצמו (MFStartup), ורכיבים כמו IMFSourceReader,‏ IMFAttributes ו-IMFTransform הם כולם ממשקי COM עם IUnknown כבסיס, שמחזירים HRESULT. ‏IMFActivate הוא כניסה ליצירת הגוף בהמשך, ורק אחרי קריאה ל-ActivateObject על תוצאת המנייה של MFTEnumEx מתקבל IMFTransform. מכיוון שהעיבוד האסינכרוני נקרא מת’רד ה-work queue שרץ ב-MTA, נדרש תכנון שלא נוגע ישירות באובייקטי UI של STA אלא מגשר רק את התוצאה.

מפת הידע של הקשר בין Media Foundation ל-COMתרשים המראה ש-Media Foundation היא פלטפורמת עיבוד מדיה מבוססת COM, שתכונות COM מופיעות בכל אחת מהנקודות - אתחול, ייצוג אובייקטים, הגדרות, מנייה, ועיבוד אסינכרוני - ושנדרש לגשר בין ה-work queue של MTA למודל ה-apartment של ה-UI.משתמש במחייבמשתמש במחייבמשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במשתמש במענה מומלץ למחייבמשתמש במחייבMedia Foundation‏COM (Component Object Model)MFStartupCoInitializeExIUnknownHRESULTIMFSourceReader(Source Reader)IMFTransform(MFT)IMFAttributesIMFActivate(Activation Object)IMFSinkWriter(Sink Writer)Media Session‏Topology (טופולוגיה)IMFSourceReaderCallbackwork queueמודל ה-apartment של COM‏ (STA/MTA)מצביע חכם ל-COM‏ (ComPtr/wil::com_ptr)תיאום סוג המדיה (media type negotiation)

בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 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” נעשה הרבה יותר ברור.

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

איור 1: הגוף הוא פלטפורמת עיבוד מדיה, ובממשקי הגבולות שלה COM חודר לעומק.

2. מילים ותמונה כללית

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

2.1. מילים שתופסים קודם רק את המשמעות

מונח המשמעות כאן
Media Source הכניסה שמכניסה נתוני מדיה לצינור העיבוד. קובץ, רשת, התקן צילום וכדומה
MFT ‏Media Foundation Transform. מודל משותף ל-decoder,‏ encoder, ממיר וידאו וכדומה
Media Sink היעד של נתוני המדיה. תצוגה על המסך, פלט אודיו, כתיבה לקובץ וכדומה
Media Session מנגנון שמנהל את זרימת צינור העיבוד כולו. אחראי על ניגון וסנכרון
Topology תרשים חיבור שמתאר איך מחברים בין source / transform / sink
Activation Object אובייקט עזר ליצירת הגוף בהמשך. מיוצג על ידי IMFActivate
Attributes מאגר key/value עם GUID כמפתח. נעשה בו שימוש נרחב בכל Media Foundation
apartment היחידה שבה COM מרכז ת’רדים. הסכם של “מאיזה ת’רד מותר לקרוא לאובייקט הזה”, שנקבע לפי הארגומנט של CoInitializeEx (3.1,‏ 3.5)
STA / MTA סוגי apartment.‏ STA (Single-Threaded Apartment) מקושר לת’רד אחד, וקריאה מת’רד אחר מובאת דרך משאבת הודעות. MTA (Multi-Threaded Apartment) משתף כמה ת’רדים באותו apartment, ואפשר לקרוא ישירות. לפירוט: ידע בסיסי על STA/MTA ב-COM — מודל השרשור ואיך נמנעים מתקיעה
work queue מנגנון הת’רדים ש-Media Foundation מחזיק כדי להריץ עיבוד אסינכרוני. ה-callback נקרא מהת’רד הזה (3.5)

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

‏apartment יהיה הנושא המרכזי בסעיף 3.5, אבל כאן מספיק לזכור ש”ה-callback של Media Foundation יכול להגיע מת’רד אחר מזה שקרא ל-ReadSample (ה-work queue של MTA)”.

2.2. התמונה הכללית של Media Foundation (תרשים)

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

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

איור 2: שני אופני שימוש. מודל שבו Media Session מנהל את הצינור כולו, ומודל שבו Reader / Writer מאפשרים לאפליקציה לטפל בנתונים ישירות.

יש בעיקר שני אופני שימוש ב-Media Foundation.

  • מודל שמשתמש בצינור העיבוד כולו
    • מחברים source / transform / sink, ו-Media Session מנהל את זרימת הנתונים וסנכרון A/V
  • מודל שבו האפליקציה מטפלת בנתונים ישירות
    • מוציאים נתונים מה-source עם Source Reader, ומזרימים ל-sink עם Sink Writer

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

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

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

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

בפועל בשטח, כדאי להחליט מראש בשלב הזה את הדברים הבאים, וזה מקל בהמשך.

  • איזה ת’רד ישתמש ב-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/com.h של WIL (Windows Implementation Libraries) מתקינים בנפרד למשל דרך NuGet. אפשר להשתמש יחד עם עזר להמרת HRESULT לחריגה

אם כותבים עם ComPtr, לא נדרש השילוב של goto done; ו-SafeRelease שמופיע בהמשך 3.1, ו-Release מתבצע ברגע שיוצאים מהתחום (scope). המאמר הזה משאיר מצביעים גולמיים כדי להעדיף שרואים את המנהג של COM, אבל מומלץ להתחיל קוד חדש מ-ComPtr.

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

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

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

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

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

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

לדוגמה,

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

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

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

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

שלבי media type negotiation (פריט “שלושת הדברים לבדוק ראשונים” ברשימת הבדיקה של פרק 6)

הקוד למעלה רק מצהיר “רוצים RGB32”, ולכן בפועל בשטח נדרשים שלבים סביבו. במקרה של Source Reader, הזרימה שמתעדת Microsoft היא ארבעת השלבים האלה.

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

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

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

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

3.4. מופיע Activation Object

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

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

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

מ-API המנייה ועד אובייקט COM ממשי דרך IMFActivateתרשים רצף המראה שהאפליקציה קוראת ל-API המנייה ומקבלת מערך IMFActivate, בודקת בו תכונות, ורק אחרי קריאה ל-ActivateObject מקבלת בחזרה אובייקט COM ממשי כמו IMFTransform או Sink.IMFTransform / Sink וכדומהIMFActivateAPI המנייהאפליקציהIMFTransform / Sink וכדומהIMFActivateAPI המנייהאפליקציהקורא למנייהמערך IMFActivate*בודק תכונותActivateObject(...)אובייקט COM ממשי

איור 8: מה ש-API המנייה מחזיר הוא IMFActivate, ורק כשקוראים ל-ActivateObject מתקבל אובייקט COM ממשי.

הצורה הזו מתאימה היטב ל-Media Foundation שמתוכנן למצוא ולהרכיב רכיבים ניתנים להחלפה בהמשך.

בנוסף, מכיוון של-activation object עצמו יכולות להיות תכונות, נוצרת זרימה של “קודם רואים את תכונות המועמד”, “אם צריך, מגדירים”, “בהמשך יוצרים בפועל”. גם זה די בסגנון 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;
}

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

3.5. גם הטיפול באסינכרוני / callback / ת’רדים הוא בסגנון COM

מה שקל לפספס בשימוש בפועל ב-Media Foundation הוא העיבוד האסינכרוני ומודל הת’רדים.

לדוגמה, Source Reader כברירת מחדל הוא במצב סינכרוני. במצב סינכרוני, ReadSample חוסם. תלוי במצב הקובץ, הרשת או ההתקן, ההמתנה הזו יכולה להפוך לזמן שנראה לעין.

אם רוצים מצב אסינכרוני, מעבירים callback בזמן יצירת Source Reader. מכינים אובייקט שמממש 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, המימוש נעשה פשוט יותר.

זרימת ReadSample אסינכרוני דרך ה-work queue של MTAתרשים רצף המראה שת'רד האפליקציה קורא ל-ReadSample וה-Source Reader חוזר מיד, בעוד העיבוד הפנימי מועבר ל-work queue של MF ב-MTA שקוראת בתורה ל-OnReadSample של ה-callback.IMFSourceReaderCallback‏work queue של MF‏ (MTA)Source Readerת'רד האפליקציהIMFSourceReaderCallback‏work queue של MF‏ (MTA)Source Readerת'רד האפליקציהReadSample(...)חוזר מידמעבד באופן פנימיOnReadSample(...)

איור 10: ה-ReadSample במצב אסינכרוני חוזר מיד, וה-OnReadSample נקרא מהת’רד של ה-work queue.

מה שכדאי להיזהר ממנו סביב ה-callback הן הנקודות האלה.

  • לא לגעת ישירות באובייקטי STA של ת’רד ה-UI מתוך ה-callback
  • מימוש ה-callback צריך להיות בטוח לריבוי ת’רדים
  • אם נדרש עדכון UI, מחזירים רק את התוצאה לת’רד ה-UI
  • לקבוע מראש “מאיזה ת’רד מגיע ה-callback של Media Foundation”

‏Media Foundation לא סופג אוטומטית את העניינים של אובייקט STA. לכן, טבעי יותר להקדיש worker שמשתמש ב-Media Foundation ל-MTA, ולבנות גשר מפורש לצד ה-UI.

איך בונים גשר בין callback לת'רד ה-UIתרשים המראה שה-callback מגיע מת'רד ה-work queue של MTA, ולכן המימוש צריך להיות בטוח לריבוי ת'רדים, לא נוגעים ישירות באובייקטי UI של STA, ואם נדרש עדכון מחזירים רק את התוצאה לת'רד ה-UI.ה-callback מגיע מה-work queue של MTAהמימוש בטוח לריבוי ת'רדיםלא נוגעים ישירות באובייקטי UI של STAמחזירים רק את התוצאה לת'רד ה-UI

איור 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 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 שמזרימה את הצינור.

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

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

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

איור 14: בוחרים נקודת כניסה לפי מה שנדרש קודם. אם רוצים לקרוא - Reader, אם לכתוב - Writer, אם לנגן - Session.

בטבלה, זה נראה כך.

מה רוצים לעשות מה נוגעים בו קודם עוצמת COM הערה
לקבל פריימים / דגימות מקובץ או ממצלמה Source Reader בינונית אם צריך, גם ה-decoder מטופל אוטומטית
לכתוב אודיו / וידאו שנוצר לקובץ Sink Writer בינונית אם צריך, אפשר לטפל יחד גם ב-encoder וגם ב-media sink
לטפל בניגון, עצירה, seek, סנכרון A/V ובקרת איכות Media Session גבוהה נדרשת הבנה של topology ושל session
להכניס ממיר עצמאי או רכיב בסגנון codec MFT גבוהה חושבים סביב IMFTransform
לראות את המועמדים שנמנו ולהחליט רק על מה שצריך IMFActivate גבוהה לעיתים מה שמוחזר הוא activation object ולא הגוף עצמו

5.1. מקרה שבו נכנסים קודם מ-Source Reader

‏Source Reader נוח למדי כנקודת כניסה כשרוצים להוציא נתונים מקובץ או מהתקן.

מתאים למקרים כמו אלה.

  • רוצים לקבל פריימים מקובץ וידאו
  • רוצים לפענח קובץ אודיו ולקבל דגימות
  • רוצים לקבל פריימים ממצלמה
  • רוצים לחבר את ה-source של Media Foundation לצינור העיבוד העצמי

‏Source Reader טוען decoder לפי הצורך, ומעביר נתונים לאפליקציה. מצד שני, הוא לא מטפל בניהול שעון הצגה (presentation clock), סנכרון A/V, או ציור המסך עצמו.

קל יותר להבין אותו כ-נקודת כניסה “להוצאת נתונים”, לא “לניגון”.

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

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

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

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

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

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

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

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

איור 16: Sink Writer הוא נקודת כניסה שמטפלת בקידוד ובכתיבה. שימוש יחד עם Reader לא חובה.

5.3. אם מטפלים גם בניגון ובסנכרון, Media Session

אם לא “רוצה להוציא נתונים” אלא רוצה לנגן כמו שצריך, טבעי יותר לחשוב סביב Media Session.

תורו של Media Session מגיע כשיש דרישות כאלה.

  • רוצים לטפל בניגון / עצירה / seek
  • רוצים להשאיר את סנכרון האודיו והווידאו לפלטפורמה
  • רוצים לטפל בצינור העיבוד כולל בקרת איכות ושינוי פורמט
  • רוצים להרכיב את זרימת source / transform / sink עם topology

בכניסה לשכבה הזו, מתקרבים יותר ל”גוף Media Foundation” מ-Source Reader /‏ Sink Writer. בהתאם, גדלים גם מושגים ייחודיים ל-Media Foundation כמו topology ו-session event.

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

איור 17: אם לא “רוצים להוציא נתונים” אלא “רוצים לנגן כמו שצריך”, Media Session הוא העיקרי.

5.4. אם מכניסים רכיב עצמאי, MFT

‏MFT הוא המודל המשותף של ה-transform ב-Media Foundation.

נכנסים לכאן במקרים כאלה.

  • רוצים ליצור decoder או encoder עצמאי
  • רוצים להכניס לצינור העיבוד רכיב לעיבוד וידאו או אודיו
  • רוצים למנות codec או ממיר ולבחור בעצמכם
  • רוצים שליטה עמוקה יותר מהפתרון האוטומטי הרגיל

בעולם ה-MFT, ‏IMFTransform,‏ IMFActivate, media type negotiation, ניהול דגימות/מאגרים - החוזה בסגנון COM בולט מאוד. לכן, קל יותר לבדוק תחילה איזה משלושת Source Reader /‏ Sink Writer /‏ Media Session באמת נחוץ, במקום להיכנס ישר ל-MFT כנקודת כניסה ראשונה.

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

איור 18: MFT היא נקודת הכניסה האחרונה. לא נכנסים ישר, אלא מוודאים קודם ששלושת האחרים לא מספיקים.

6. רשימת בדיקה לשימוש בפועל

לבסוף, נרכז בדף אחד את הנקודות שכדאי לבדוק ראשונות בפועל בשטח.

פריט מה בודקים מה נוטה לקרות אם מפספסים
אחריות האתחול מחליטים איפה קוראים ל-CoInitializeEx ול-MFStartup, ומי מחזיק את הסיום דליפת אתחול, בלבול בסדר הסיום
apartment מחליטים מראש אם הת’רד שנוגע ב-MF יהיה STA או MTA בלבול סביב ה-callback, התנגשות עם ה-UI
מצב Source Reader מחליטים בזמן היצירה אם סינכרוני או אסינכרוני ReadSample חוסם שלא כמצופה, אי אפשר להחליף אחר כך
media type negotiation מונים פורמטי פלט, ומציינים במפורש את הפורמט שבו באמת משתמשים. השלבים הם ארבעת השלבים של 3.3 (מנייה עם GetNativeMediaType → בדיקת major type → SetCurrentMediaType → קריאת ערך מוחלט עם GetCurrentMediaType) MF_E_INVALIDMEDIATYPE, מגיע פורמט שונה מהצפוי
אורך חיי אובייקטים מבהירים את האחריות של Release,‏ Unlock,‏ ShutdownObject דליפת זיכרון, החזקת מאגר, אי-עקביות בסיום
activation object מבחינים אם תוצאת המנייה היא הגוף עצמו או IMFActivate חושבים שאפשר QueryInterface וזה נכשל
topology מבינים אם מטפלים ב-partial topology או ב-full topology נתקעים בהנחה ש”זה אמור להתחבר אוטומטית”
בדיקת שגיאות בודקים כל פעם HRESULT, דגלי הזרם, ואירועים מפספסים כישלון חלקי
שילוב UI לא נוגעים ישירות ב-UI מתוך ה-callback, מחזירים רק את התוצאה לת’רד ה-UI תקיעה, מרוץ, תקלה קשה להבנה

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

  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
שלושת הפריטים עם העדיפות הגבוהה ביותר ברשימת הבדיקהתרשים המראה ששלוש הנקודות עם העדיפות הגבוהה ביותר הן לא לטעות ב-API של נקודת הכניסה, להחליט מראש על ה-apartment, ולא להתייחס ברשלנות ל-media type negotiation, וכולן מקטינות את הבלבול במימוש המאוחר.לא טועים ב-API של הכניסהמקטין בלבול במימוש המאוחרמחליטים מראש על ה-apartmentלא מתייחסים ברשלנות לתיאום הפורמט

איור 19: מתוך רשימת הבדיקה, שלושת אלה יעילים ביותר לתפוס ראשונים.

7. סיכום

זה לא במקרה שנושא ה-COM מתרבה פתאום כשנוגעים ב-Media Foundation.

  • ‏Media Foundation היא פלטפורמת עיבוד מדיה
  • הגבול בין source / transform / sink / activation / callback וכדומה מיוצג בממשקי COM
  • לכן, נושאי IUnknown,‏ HRESULT, GUID, apartment ו-callback עולים באופן טבעי
  • עם זאת, הגוף של Media Foundation הוא צינור עיבוד מדיה שמחזיק Media Session ו-topology, ולא רק שכפול של COM

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

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

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

אין צורך להבין הכול מיד מההתחלה. אם מסתכלים תחילה על “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. בפועל בשטח, כדאי להחליט מראש איזה ת'רד ישתמש ב-Media Foundation, האם הוא יהיה STA או MTA, ומי אחראי על MFStartup / MFShutdown ו-CoInitializeEx / CoUninitialize - זה מקל אחר כך על ה-callback והשילוב עם ה-UI.
איך בוחרים בין Source Reader,‏ Sink Writer,‏ Media Session ו-MFT?
אם רוצים להוציא פריימים או דגימות מקובץ או ממצלמה, נקודת הכניסה היא Source Reader; אם רוצים לכתוב אודיו/וידאו שיצרתם לקובץ, זה Sink Writer. אם רוצים להשאיר לפלטפורמה את הניגון, העצירה, ה-seek, סנכרון A/V ובקרת איכות, חושבים סביב Media Session. אם רוצים להכניס לצינור העיבוד decoder או ממיר משלכם, מתקדמים ל-MFT, אבל מכיוון שהחוזה מסוג COM בולט שם מאוד, מומלץ לבדוק קודם איזה משלושת הראשונים באמת נחוץ.
מה חשוב לשים לב אליו ב-callback האסינכרוני של Media Foundation?
העיבוד האסינכרוני של Media Foundation משתמש ב-work queue, וה-ת'רד שלו הוא MTA, ולכן אם גם צד האפליקציה מתקרב ל-MTA, המימוש נעשה פשוט יותר. כדאי שהמימוש של IMFSourceReaderCallback יהיה בטוח לריבוי ת'רדים, וחשוב לא לגעת ישירות באובייקטי STA של ת'רד ה-UI מתוך ה-callback. אם נדרש עדכון UI, מחזירים רק את התוצאה לת'רד ה-UI. כמו כן, המצב הסינכרוני/אסינכרוני של Source Reader נקבע בזמן היצירה, ואי אפשר להחליף אותו אחר כך.

פרופיל הכותב

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

Go Komura

מנהל KomuraSoft LLC

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

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

חזרה לבלוג