מבוא ל-Media Foundation — מבינים את ה-API מנקודת המבט של COM
· עודכן בתאריך: · Go Komura · 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 וכדומה.
תוכן עניינים
- קודם המסקנה (במשפט אחד)
- מילים ותמונה כללית
- 2.1. מילים שתופסים קודם רק את המשמעות
- 2.2. התמונה הכללית של Media Foundation (תרשים)
- הנקודות שבהן Media Foundation הופך לפנים של COM
- 3.1.
CoInitializeExו-MFStartupמופיעים באתחול יחד - 3.2. העברת אובייקטים ממוקדת ממשקים
- 3.3. הגדרות ומידע טיפוס ממוקדים ב-
IMFAttributesוב-GUID - 3.4. מופיע Activation Object
- 3.5. גם הטיפול באסינכרוני / callback / ת’רדים הוא בסגנון COM
- 3.1.
- אבל Media Foundation אינו COM
- מאיפה נוגעים (בחירת נקודת הכניסה)
- 5.1. מקרה שבו נכנסים קודם מ-Source Reader
- 5.2. אם כותבים לקובץ, Sink Writer
- 5.3. אם מטפלים גם בניגון ובסנכרון, Media Session
- 5.4. אם מכניסים רכיב עצמאי, MFT
- רשימת בדיקה לשימוש בפועל
- סיכום
- מקורות
מפת הידע של המאמר
המאמר הזה מסביר שMedia Foundation היא פלטפורמת עיבוד מדיה המבוססת על COM, דרך חמש נקודות: אתחול, העברת אובייקטים, הגדרות, מנייה, ועיבוד אסינכרוני. אתחול ספריית COM (CoInitializeEx) הוא תנאי מוקדם לאתחול של Media Foundation עצמו (MFStartup), ורכיבים כמו IMFSourceReader, IMFAttributes ו-IMFTransform הם כולם ממשקי COM עם IUnknown כבסיס, שמחזירים HRESULT. IMFActivate הוא כניסה ליצירת הגוף בהמשך, ורק אחרי קריאה ל-ActivateObject על תוצאת המנייה של MFTEnumEx מתקבל IMFTransform. מכיוון שהעיבוד האסינכרוני נקרא מת’רד ה-work queue שרץ ב-MTA, נדרש תכנון שלא נוגע ישירות באובייקטי UI של STA אלא מגשר רק את התוצאה.
flowchart LR
accTitle: מפת הידע של הקשר בין Media Foundation ל-COM
accDescr: תרשים המראה ש-Media Foundation היא פלטפורמת עיבוד מדיה מבוססת COM, שתכונות COM מופיעות בכל אחת מהנקודות - אתחול, ייצוג אובייקטים, הגדרות, מנייה, ועיבוד אסינכרוני - ושנדרש לגשר בין ה-work queue של MTA למודל ה-apartment של ה-UI.
media_foundation["Media Foundation"]
com["COM (Component Object Model)"]
mfstartup["MFStartup"]
coinitializeex["CoInitializeEx"]
iunknown["IUnknown"]
hresult["HRESULT"]
imfsourcereader["IMFSourceReader(Source Reader)"]
imftransform["IMFTransform(MFT)"]
imfattributes["IMFAttributes"]
imfactivate["IMFActivate(Activation Object)"]
imfsinkwriter["IMFSinkWriter(Sink Writer)"]
media_session["Media Session"]
mf_topology["Topology (טופולוגיה)"]
imfsourcereadercallback["IMFSourceReaderCallback"]
mf_work_queue["work queue"]
com_apartment_model["מודל ה-apartment של COM (STA/MTA)"]
com_smart_pointer["מצביע חכם ל-COM (ComPtr/wil::com_ptr)"]
media_type_negotiation["תיאום סוג המדיה (media type negotiation)"]
media_foundation -->|"משתמש ב"| com
mfstartup -->|"מחייב"| coinitializeex
media_foundation -->|"משתמש ב"| mfstartup
com -->|"מחייב"| iunknown
com -->|"משתמש ב"| hresult
imfsourcereader -->|"משתמש ב"| com
imftransform -->|"משתמש ב"| com
imfattributes -->|"משתמש ב"| com
imfactivate -->|"משתמש ב"| imfattributes
media_foundation -.->|"משתמש ב"| imfsourcereader
media_foundation -.->|"משתמש ב"| imfsinkwriter
media_foundation -.->|"משתמש ב"| media_session
media_session -->|"משתמש ב"| mf_topology
imfsourcereader -->|"משתמש ב"| imftransform
imfsourcereader -.->|"משתמש ב"| imfsourcereadercallback
imfsourcereadercallback -->|"משתמש ב"| mf_work_queue
mf_work_queue -->|"משתמש ב"| com_apartment_model
com_smart_pointer -->|"מענה מומלץ ל"| com
imfsourcereader -->|"מחייב"| media_type_negotiation
media_type_negotiation -->|"משתמש ב"| imfattributes
imftransform -.->|"מחייב"| imfactivate
בתרשים, קו מלא מציין קשר שמתקיים תמיד וקו מקווקו מציין קשר מותנה (תנאי ההתקיימות מפורטים בהסבר של כל קשר בעמוד המפורט). רשימת כל הקשרים (סך הכול 21, עם אסמכתה ורמת ודאות) והגדרות המושגים המרכזיים מרוכזות בעמוד המפורט של מפת הידע (ביפנית). נתונים: JSON-LD / Turtle
1. קודם המסקנה (במשפט אחד)
- Media Foundation היא פלטפורמה לטיפול בווידאו ואודיו, ולא כל ה-API כולו הוא COM טהור כפי שהוא
- עם זאת, הגבול בין source / transform / sink / activation / attributes / callback מיוצג בממשקי COM, ולכן כשמשתמשים בזה, נושאי
IUnknown,HRESULT, GUID ו-apartment עולים באופן טבעי - קל יותר לסדר את זה אם נכנסים תחילה מ-Source Reader / Sink Writer, ומתקדמים ל-Media Session כשנדרשת בקרת ניגון, ול-MFT כשנדרש ממיר עצמאי
במילים אחרות, Media Foundation היא פלטפורמת עיבוד מדיה, שבממשקי הגבולות שלה COM חודר לעומק.
אם תופסים את זה מראש, ה”למה זה הופך פתאום לפנים של COM” נעשה הרבה יותר ברור.
flowchart TB
accTitle: הקשר בין Media Foundation ל-COM
accDescr: תרשים המראה שגוף Media Foundation הוא פלטפורמת עיבוד מדיה ולא כל ה-API הוא COM טהור, אך מכיוון שהגבול בין הרכיבים מיוצג בממשקי COM, נושאי IUnknown, HRESULT ו-GUID עולים באופן טבעי.
mf["Media Foundation"] --> plat["פלטפורמת עיבוד מדיה"]
mf --> border["הגבול בין הרכיבים הוא ממשק COM"]
border --> com["עולים IUnknown, HRESULT, GUID"]
plat -.-> note["לא כל ה-API הוא COM טהור"]
איור 1: הגוף הוא פלטפורמת עיבוד מדיה, ובממשקי הגבולות שלה COM חודר לעומק.
2. מילים ותמונה כללית
לפני שניכנס לנושא COM, נעבור תחילה על המילים שנשתמש בהן במאמר הזה, ועל התמונה הכללית של Media Foundation.
2.1. מילים שתופסים קודם רק את המשמעות
| מונח | המשמעות כאן |
|---|---|
| Media Source | הכניסה שמכניסה נתוני מדיה לצינור העיבוד. קובץ, רשת, התקן צילום וכדומה |
| MFT | Media Foundation Transform. מודל משותף ל-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 חשוב, אבל קל יותר לסדר אם מסתכלים קודם על התמונה הכללית.
flowchart TB
accTitle: שני אופני השימוש ב-Media Foundation
accDescr: תרשים המראה מודל שבו Media Session מנהל את כל צינור העיבוד בין Media Source, MFT ו-Media Sink, לעומת מודל שבו האפליקציה עצמה מטפלת בנתונים ישירות בין Source Reader ל-Sink Writer.
subgraph Pipeline["מודל שמשתמש בצינור העיבוד כולו"]
Source1["Media Source"] --> Transform1["MFT"]
Transform1 --> Sink1["Media Sink"]
Session["Media Session"] --- Source1
Session --- Transform1
Session --- Sink1
end
subgraph Direct["מודל שבו האפליקציה מטפלת בנתונים ישירות"]
Source2["Media Source"] --> Reader["Source Reader(+ decoder)"]
Reader --> App["אפליקציה"]
App --> Writer["Sink Writer(+ encoder)"]
Writer --> Sink2["Media Sink"]
end
איור 2: שני אופני שימוש. מודל שבו Media Session מנהל את הצינור כולו, ומודל שבו Reader / Writer מאפשרים לאפליקציה לטפל בנתונים ישירות.
יש בעיקר שני אופני שימוש ב-Media Foundation.
- מודל שמשתמש בצינור העיבוד כולו
- מחברים source / transform / sink, ו-Media Session מנהל את זרימת הנתונים וסנכרון A/V
- מודל שבו האפליקציה מטפלת בנתונים ישירות
- מוציאים נתונים מה-source עם 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.
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 לבדו לא מספיק. האתחול הוא בשני שלבים, והסיום מוחזר בסדר הפוך.
בפועל בשטח, כדאי להחליט מראש בשלב הזה את הדברים הבאים, וזה מקל בהמשך.
- איזה ת’רד ישתמש ב-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.
flowchart TB
accTitle: החלוקה בין מצביע גולמי למצביע חכם
accDescr: תרשים המראה שהקוד במאמר הזה כתוב עם מצביע גולמי ו-SafeRelease כדי שרואים איפה AddRef ו-Release פועלים, אך קוד חדש בפועל בשטח כדאי שיתקרב ל-ComPtr או ל-wil::com_ptr, שמבצעים Release ברגע שיוצאים מהתחום.
raw["מצביע גולמי ו-SafeRelease"] -.-> why["כתיבה שמראה איפה Release פועל"]
smart["ComPtr או wil::com_ptr"] --> auto["Release ברגע שיוצאים מהתחום"]
auto --> rec["קוד חדש כדאי שיתחיל מ-ComPtr"]
איור 4: קוד המאמר נשאר עם מצביע גולמי לצורכי לימוד. קוד חדש בפועל בשטח כדאי שיתקרב למצביע חכם.
3.2. העברת אובייקטים ממוקדת ממשקים
כשקוראים את ה-API של Media Foundation, רוב ערכי ההחזרה והארגומנטים מסוג out הם ממשקי COM.
IMFSourceReaderIMFMediaTypeIMFTransformIMFActivateIMFSampleIMFMediaBuffer
מה שאופייני הוא שלא רק גוף הנתונים, אלא גם מידע הטיפוס ואובייקטי ההגדרה מיוצגים בממשק.
לדוגמה,
-
IMFTransformהוא ממשק שמייצג MFT -
IMFAttributesהוא מאגר key/value -
IMFMediaTypeהוא “תיאור פורמט המדיה” שיורש מ-IMFAttributes
גם משהו כמו media type, שנראה “כמו נתוני הגדרה”, מוחזק בממשק COM. כאן נכנס באופן טבעי ההקשר של 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 וכדומה)
- גודל הפריים
- קצב פריימים
- קצב הדגימה
- מספר ערוצים
flowchart LR
accTitle: IMFMediaType כמאגר תכונות
accDescr: תרשים המראה ש-IMFMediaType מחזיק כמפתחות GUID גם את MF_MT_MAJOR_TYPE, גם את MF_MT_SUBTYPE, וגם פרטים נוספים כמו גודל, FPS וקצב דגימה.
MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
MediaType --> Subtype["MF_MT_SUBTYPE"]
MediaType --> Detail["גודל / FPS / קצב דגימה וכדומה"]
איור 6: IMFMediaType הוא מאגר תכונות, שמחזיק מידע פורמט כמו major type ו-subtype כמפתחות GUID.
קל להרגיש את זה כ”יער של GUID”, אבל בפועל מה שעושים כאן די פשוט.
- משתמשים במאגר תכונות כדי להחזיק הגדרות
- גם media type מיוצג כמאגר תכונות
- בין source / transform / sink, מסתכלים על התכונה הזו כדי לתאם פורמט
זה פשוט שהייצוג של ההגדרות ומידע הטיפוס משתמש בממשק בסגנון COM וב-GUID.
אם רואים את זה בקוד, זה נראה כך. דוגמה של קריאת פריים אחד מווידאו עם Source Reader.
HRESULT ReadOneVideoSample(PCWSTR path)
{
IMFSourceReader* pReader = nullptr;
IMFMediaType* pType = nullptr;
IMFSample* pSample = nullptr;
HRESULT hr = MFCreateSourceReaderFromURL(path, nullptr, &pReader);
if (FAILED(hr)) goto done;
hr = MFCreateMediaType(&pType);
if (FAILED(hr)) goto done;
hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
if (FAILED(hr)) goto done;
hr = pType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
if (FAILED(hr)) goto done;
hr = pReader->SetCurrentMediaType(
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
nullptr,
pType);
if (FAILED(hr)) goto done;
DWORD streamFlags = 0;
LONGLONG timestamp = 0;
hr = pReader->ReadSample(
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
nullptr,
&streamFlags,
×tamp,
&pSample);
if (FAILED(hr)) goto done;
// מוציאים את ה-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 היא ארבעת השלבים האלה.
- מונים את הטיפוסים הנייטיביים — קוראים ל-
IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType), תוך הגדלתtypeIndexמ-0. כשעוברים את הטווח, מוחזרMF_E_NO_MORE_TYPES, וזה סוף המנייה (אםstreamIndexמחוץ לטווח, מוחזרMF_E_INVALIDSTREAMNUMBER). קובץ בדרך כלל מכיל סוג אחד לזרם, אבל מצלמת רשת יכולה להכיל כמה פורמטים - בודקים את ה-major type — קוראים
MF_MT_MAJOR_TYPEמה-media type שהתקבל במנייה, וקובעים אם זה אודיו או וידאו. אם מתקדמים בלי לבדוק את זה, אפשר לשלוח הגדרת וידאו לזרם אודיו - מרכיבים ומגדירים את פורמט הפלט הרצוי — יוצרים media type חדש עם
MFCreateMediaType, מגדיריםMF_MT_MAJOR_TYPEו-MF_MT_SUBTYPE, וקוראים ל-SetCurrentMediaType. אם רוצים לקבל דחוס כמו שהוא, מעבירים את הטיפוס שהתקבל בשלב 1 כמו שהוא; אם רוצים שיפוענח, מציינים פורמט לא דחוס (MFVideoFormat_RGB32,MFAudioFormat_PCMוכדומה). את ה-decoder Source Reader טוען אוטומטית - קוראים מחדש את הפורמט שהוחלט — אחרי
SetCurrentMediaTypeקוראים ל-GetCurrentMediaType, ומקבלים את פרטי הפורמט שבאמת הוחלט (גודל פריים, stride, קצב דגימה וכדומה). מה שמעבירים בשלב 3 הוא ציון חלקי, ולכן הערך המוחלט קוראים מכאן - זה הסדר הנכון
אם מדלגים על ארבעת השלבים האלה ומתקדמים עם “כנראה זה הפורמט”, מוחזר MF_E_INVALIDMEDIATYPE, או שגם אם עובר, קוראים מאגר בפורמט שונה מהצפוי.
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 הוא סוף המנייה"]
איור 7: תיאום הפורמט הוא ארבעה שלבים. מה שמעבירים הוא ציון חלקי, ולכן קוראים את הערך המוחלט בסוף.
3.4. מופיע Activation Object
הצבע של COM ב-Media Foundation בולט במיוחד ב-activation object.
IMFActivate הוא אובייקט עזר ליצירת הגוף בהמשך. קל להבין אותו כקרוב ל-class factory של COM.
במקומות שהוא מופיע, ערך ההחזרה של API המנייה לא תמיד “הגוף המוכן לשימוש”, אלא לעיתים תחילה מערך של IMFActivate*.
ואז, רק את מה שנדרש, יוצרים בפועל עם ActivateObject.
sequenceDiagram
accTitle: מ-API המנייה ועד אובייקט COM ממשי דרך IMFActivate
accDescr: תרשים רצף המראה שהאפליקציה קוראת ל-API המנייה ומקבלת מערך IMFActivate, בודקת בו תכונות, ורק אחרי קריאה ל-ActivateObject מקבלת בחזרה אובייקט COM ממשי כמו IMFTransform או Sink.
participant App as אפליקציה
participant Enum as API המנייה
participant Act as IMFActivate
participant Obj as IMFTransform / Sink וכדומה
App->>Enum: קורא למנייה
Enum-->>App: מערך IMFActivate*
App->>Act: בודק תכונות
App->>Act: ActivateObject(...)
Act-->>App: אובייקט COM ממשי
איור 8: מה ש-API המנייה מחזיר הוא IMFActivate, ורק כשקוראים ל-ActivateObject מתקבל אובייקט COM ממשי.
הצורה הזו מתאימה היטב ל-Media Foundation שמתוכנן למצוא ולהרכיב רכיבים ניתנים להחלפה בהמשך.
בנוסף, מכיוון של-activation object עצמו יכולות להיות תכונות, נוצרת זרימה של “קודם רואים את תכונות המועמד”, “אם צריך, מגדירים”, “בהמשך יוצרים בפועל”. גם זה די בסגנון 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”.
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: הזרימה היא מנייה → בדיקת מועמדים → יצירה בפועל → שחרור. תוצאת המנייה אינה הגוף המוכן לשימוש.
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, המימוש נעשה פשוט יותר.
sequenceDiagram
accTitle: זרימת ReadSample אסינכרוני דרך ה-work queue של MTA
accDescr: תרשים רצף המראה שת'רד האפליקציה קורא ל-ReadSample וה-Source Reader חוזר מיד, בעוד העיבוד הפנימי מועבר ל-work queue של MF ב-MTA שקוראת בתורה ל-OnReadSample של ה-callback.
participant App as ת'רד האפליקציה
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 נקרא מהת’רד של ה-work queue.
מה שכדאי להיזהר ממנו סביב ה-callback הן הנקודות האלה.
- לא לגעת ישירות באובייקטי STA של ת’רד ה-UI מתוך ה-callback
- מימוש ה-callback צריך להיות בטוח לריבוי ת’רדים
- אם נדרש עדכון UI, מחזירים רק את התוצאה לת’רד ה-UI
- לקבוע מראש “מאיזה ת’רד מגיע ה-callback של Media Foundation”
Media Foundation לא סופג אוטומטית את העניינים של אובייקט STA. לכן, טבעי יותר להקדיש worker שמשתמש ב-Media Foundation ל-MTA, ולבנות גשר מפורש לצד ה-UI.
flowchart TB
accTitle: איך בונים גשר בין callback לת'רד ה-UI
accDescr: תרשים המראה שה-callback מגיע מת'רד ה-work queue של MTA, ולכן המימוש צריך להיות בטוח לריבוי ת'רדים, לא נוגעים ישירות באובייקטי UI של STA, ואם נדרש עדכון מחזירים רק את התוצאה לת'רד ה-UI.
cb["ה-callback מגיע מה-work queue של MTA"] --> safe["המימוש בטוח לריבוי ת'רדים"]
cb -.-> ng["לא נוגעים ישירות באובייקטי UI של STA"]
safe --> bridge["מחזירים רק את התוצאה לת'רד ה-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 מחזיק כפלטפורמת עיבוד מדיה.
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 שמזרימה את הצינור.
5. מאיפה נוגעים (בחירת נקודת הכניסה)
בבחירת נקודת הכניסה הראשונה, לרוב מספיק התרשים הזה.
flowchart TD
accTitle: בחירת נקודת הכניסה ל-Media Foundation
accDescr: תרשים המראה שלפי מה שנדרש קודם בוחרים בין Source Reader לקריאת פריימים או דגימות, Sink Writer לכתיבה לקובץ, Media Session לבקרת ניגון וסנכרון A/V, או MFT להכנסת ממיר עצמאי.
Start["מה רוצים לעשות"] --> Q1{"מה נדרש קודם?"}
Q1 -- "רוצים לקרוא פריימים / דגימות" --> A1["Source Reader"]
Q1 -- "רוצים לכתוב לקובץ" --> A2["Sink Writer"]
Q1 -- "נדרש בקרת ניגון או סנכרון A/V" --> A3["Media Session"]
Q1 -- "רוצים להכניס ממיר עצמאי" --> A4["MFT"]
איור 14: בוחרים נקודת כניסה לפי מה שנדרש קודם. אם רוצים לקרוא - Reader, אם לכתוב - Writer, אם לנגן - Session.
בטבלה, זה נראה כך.
| מה רוצים לעשות | מה נוגעים בו קודם | עוצמת COM | הערה |
|---|---|---|---|
| לקבל פריימים / דגימות מקובץ או ממצלמה | Source Reader | בינונית | אם צריך, גם ה-decoder מטופל אוטומטית |
| לכתוב אודיו / וידאו שנוצר לקובץ | Sink Writer | בינונית | אם צריך, אפשר לטפל יחד גם ב-encoder וגם ב-media sink |
| לטפל בניגון, עצירה, 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, או ציור המסך עצמו.
קל יותר להבין אותו כ-נקודת כניסה “להוצאת נתונים”, לא “לניגון”.
flowchart TB
accTitle: היקף התפקיד של Source Reader
accDescr: תרשים המראה ש-Source Reader מוציא נתונים מקובץ או ממצלמה, טוען decoder לפי הצורך ומעביר לאפליקציה, אך לא מטפל בניהול שעון ההצגה, סנכרון A/V, או ציור המסך.
src["source כמו קובץ או מצלמה"] --> sr["Source Reader"]
sr --> app["מעביר נתונים לאפליקציה"]
sr -.-> dec["אם צריך, טוען decoder"]
sr -.-> not["לא מטפל בניגון או סנכרון"]
איור 15: Source Reader הוא נקודת כניסה ל”הוצאת נתונים”, לא ל”ניגון”.
5.2. אם כותבים לקובץ, Sink Writer
Sink Writer הוא נקודת כניסה כשרוצים לכתוב אודיו או וידאו לקובץ.
מבחינת שימוש, אלה טיפוסיים.
- רוצים לשמור פריימים שיצרתם לקובץ וידאו
- רוצים לקודד דגימות אודיו ולכתוב אותן
- רוצים להמיר נתונים שקראתם לפורמט אחר ולשמור
Sink Writer מוצא ומטעין encoder לפי הצורך, ומנהל את זרימת הנתונים ל-media sink. לעיתים קרובות משלבים אותו עם Source Reader, אבל שניהם רכיבים עצמאיים, ואין חובה להשתמש בהם יחד.
flowchart TB
accTitle: היקף התפקיד של Sink Writer
accDescr: תרשים המראה שכשהאפליקציה מעבירה ל-Sink Writer פריימים או דגימות אודיו שיצרה, הוא טוען לפי הצורך encoder, מנהל את זרימת הנתונים ל-media sink וכותב לקובץ.
app2["פריימים / אודיו שיצרה האפליקציה"] --> sw["Sink Writer"]
sw -.-> enc["אם צריך, טוען encoder"]
sw --> sink["כותב אל media sink"]
sw -.-> ind["רכיב עצמאי מ-Source Reader"]
איור 16: Sink Writer הוא נקודת כניסה שמטפלת בקידוד ובכתיבה. שימוש יחד עם Reader לא חובה.
5.3. אם מטפלים גם בניגון ובסנכרון, Media Session
אם לא “רוצה להוציא נתונים” אלא רוצה לנגן כמו שצריך, טבעי יותר לחשוב סביב Media Session.
תורו של Media Session מגיע כשיש דרישות כאלה.
- רוצים לטפל בניגון / עצירה / seek
- רוצים להשאיר את סנכרון האודיו והווידאו לפלטפורמה
- רוצים לטפל בצינור העיבוד כולל בקרת איכות ושינוי פורמט
- רוצים להרכיב את זרימת source / transform / sink עם topology
בכניסה לשכבה הזו, מתקרבים יותר ל”גוף Media Foundation” מ-Source Reader / Sink Writer. בהתאם, גדלים גם מושגים ייחודיים ל-Media Foundation כמו topology ו-session event.
flowchart TB
accTitle: ההחלטה לבחור ב-Media Session
accDescr: תרשים המראה שאם רוצים להשאיר לפלטפורמה גם ניגון, עצירה, seek, סנכרון A/V ובקרת איכות, חושבים סביב Media Session, בונים את הזרימה עם topology, ומושגים ייחודיים ל-MF גדלים בהתאם.
need["רוצים להשאיר ניגון, seek וסנכרון"] --> ms["חושבים סביב Media Session"]
ms --> topo["בונים מ-source ל-sink עם topology"]
ms -.-> deep["מושגים ייחודיים ל-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 כנקודת כניסה ראשונה.
flowchart TB
accTitle: הבדיקה לפני מעבר ל-MFT
accDescr: תרשים המראה שכשרוצים להכניס decoder או ממיר עצמאי לצינור העיבוד מתקדמים ל-MFT, אך מכיוון שהחוזה בסגנון COM בולט שם, כדאי לבדוק קודם אם Source Reader, Sink Writer או Media Session מספיקים.
first["קודם בודקים אם שלושת נקודות הכניסה האחרות מספיקות"] --> q{"נדרש רכיב המרה עצמאי?"}
q -->|"לא"| use3["ממשיכים עם Reader, Writer או Session"]
q -->|"כן"| mft["מתקדמים ל-MFT(IMFTransform)"]
mft -.-> heavy["החוזה בסגנון COM בולט"]
איור 18: MFT היא נקודת הכניסה האחרונה. לא נכנסים ישר, אלא מוודאים קודם ששלושת האחרים לא מספיקים.
6. רשימת בדיקה לשימוש בפועל
לבסוף, נרכז בדף אחד את הנקודות שכדאי לבדוק ראשונות בפועל בשטח.
| פריט | מה בודקים | מה נוטה לקרות אם מפספסים |
|---|---|---|
| אחריות האתחול | מחליטים איפה קוראים ל-CoInitializeEx ול-MFStartup, ומי מחזיק את הסיום |
דליפת אתחול, בלבול בסדר הסיום |
| apartment | מחליטים מראש אם הת’רד שנוגע ב-MF יהיה STA או MTA | בלבול סביב ה-callback, התנגשות עם ה-UI |
| מצב Source Reader | מחליטים בזמן היצירה אם סינכרוני או אסינכרוני | ReadSample חוסם שלא כמצופה, אי אפשר להחליף אחר כך |
| media type negotiation | מונים פורמטי פלט, ומציינים במפורש את הפורמט שבו באמת משתמשים. השלבים הם ארבעת השלבים של 3.3 (מנייה עם 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 | תקיעה, מרוץ, תקלה קשה להבנה |
מה שהעדיפות הכי גבוהה עבורו הן שלוש אלה.
- לא לטעות בבחירת ה-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: שלושת הפריטים עם העדיפות הגבוהה ביותר ברשימת הבדיקה
accDescr: תרשים המראה ששלוש הנקודות עם העדיפות הגבוהה ביותר הן לא לטעות ב-API של נקודת הכניסה, להחליט מראש על ה-apartment, ולא להתייחס ברשלנות ל-media type negotiation, וכולן מקטינות את הבלבול במימוש המאוחר.
c1["לא טועים ב-API של הכניסה"] --> ease["מקטין בלבול במימוש המאוחר"]
c2["מחליטים מראש על ה-apartment"] --> ease
c3["לא מתייחסים ברשלנות לתיאום הפורמט"] --> ease
איור 19: מתוך רשימת הבדיקה, שלושת אלה יעילים ביותר לתפוס ראשונים.
7. סיכום
זה לא במקרה שנושא ה-COM מתרבה פתאום כשנוגעים ב-Media Foundation.
- Media Foundation היא פלטפורמת עיבוד מדיה
- הגבול בין source / transform / sink / activation / callback וכדומה מיוצג בממשקי COM
- לכן, נושאי
IUnknown,HRESULT, GUID, apartment ו-callback עולים באופן טבעי - עם זאת, הגוף של Media Foundation הוא צינור עיבוד מדיה שמחזיק Media Session ו-topology, ולא רק שכפול של COM
בפועל בשטח, קל יותר לסדר אם חושבים תחילה בסדר הזה.
- מפרידים תחילה איזה מ-Source Reader / Sink Writer / Media Session / MFT נחוץ
- מחליטים מראש על מדיניות ה-apartment וה-callback
- מטפלים בזהירות ב-media type negotiation ובאורך חיי האובייקטים
flowchart TB
accTitle: הסדר לחשיבה בפועל בשטח
accDescr: תרשים המראה שקודם מפרידים איזו נקודת כניסה נחוצה, אחר כך מחליטים על מדיניות ה-apartment וה-callback, ולבסוף מטפלים בזהירות בתיאום הפורמט ובאורך החיים - סדר החשיבה המומלץ בפועל בשטח.
o1["מפרידים איזו נקודת כניסה נחוצה"] --> o2["מחליטים על מדיניות apartment ו-callback"]
o2 --> o3["מטפלים בזהירות בתיאום הפורמט ובאורך החיים"]
איור 20: הסדר לחשיבה בפועל בשטח. בחירת נקודת הכניסה, מדיניות הת’רדים, ואז הפורמט ואורך החיים.
אין צורך להבין הכול מיד מההתחלה. אם מסתכלים תחילה על “Media Foundation היא פלטפורמת עיבוד מדיה, ו-COM חודר לעומק בממשקי הגבולות שלה”, גם התיעוד וגם הקוד נעשים הרבה יותר קלים לעקוב אחריהם.
8. מקורות
- Media Foundation and COM - Microsoft Learn
- Overview of the Media Foundation Architecture - Microsoft Learn
- Initializing Media Foundation - Microsoft Learn
- Source Reader - Microsoft Learn
- Using the Source Reader to Process Media Data - Microsoft Learn
- Using the Source Reader in Asynchronous Mode - Microsoft Learn
- Sink Writer - Microsoft Learn
- Activation Objects - Microsoft Learn
- About Topologies - Microsoft Learn
- IMFAttributes interface - Microsoft Learn
- IMFMediaType interface - Microsoft Learn
- IMFTransform interface - Microsoft Learn
- MFTEnumEx function - Microsoft Learn
- IMFSourceReader::GetNativeMediaType - Microsoft Learn
- ComPtr Class (Microsoft::WRL) - Microsoft Learn
-
[ידע בסיסי על STA/MTA ב-COM — מודל השרשור ואיך נמנעים מתקיעה KomuraSoft Blog](/he/blog/sta-mta-com-relationship/) -
[קריאה מ-C# ל-DLL נייטיב: עטיפת C++/CLI מול P/Invoke KomuraSoft Blog](/he/blog/cpp-cli-wrapper-for-native-dlls/)
מאמרים קשורים
מאמרים עדכניים עם אותן תגיות, להעמקה בנושאים קרובים.
איך ממירים YUV ל-RGB עם Media Foundation
מסדרים איך ממירים מסגרת YUV ל-RGB עם Media Foundation, דרך ההמרה האוטומטית של Source Reader וההמרה העצמית של NV12/YUY2, ה-stride ומרחב הצבע.
איך לחלץ תמונת סטילס מ-MP4 בזמן נתון עם Media Foundation
מסכמים את שלבי המימוש לחילוץ מסגרת קרובה לזמן נתון מתוך MP4 עם Source Reader, יישור ה-stride ו-alpha של RGB32, ושמירה כ-PNG.
מה זה Reg-Free COM - שימוש ב-COM בלי רישום
המאמר מסדר את היסודות של Reg-Free COM - תפקיד ה-activation context וה-manifest, היתרונות, המגבלות, וקריטריוני ההחלטה בפועל.
בניית פלט דוחות Excel - COM, Open XML, תבנית
פלט דוחות Excel משתנה מהותית לפי השאלה אם מפעילים את Excel אוטומטית, יוצרים xlsx ישירות, או משמרים VBA קיים. המאמר מסדר את קריטריוני הבחי...
צריבת תמונה וטקסט על MP4 עם Media Foundation
המאמר מסדר את הגישה לצריבת תמונה וטקסט על כל מסגרת בסרטון MP4 ויצירת MP4 חדש עם Media Foundation - חלוקת התפקידים בין Source Reader, ציור...
נושאים קשורים
העמודים האלה ממקמים את הנושא בהקשר רחב יותר של שירותים והחלטות.
נושאים טכניים ב-Windows
שער לנושאי פיתוח Windows, חקירת תקלות וניצול נכסים קיימים.
העברת ActiveX
החלטות אם לשמר, לעטוף או להחליף רכיבי COM / ActiveX / OCX.
שירותים הקשורים לנושא הזה
המאמר קשור ישירות לשירותים הבאים.
פיתוח יישומי Windows
עיבוד מדיה ב-Windows שכולל Media Foundation, COM ו-HRESULT קרוב לנושאי המימוש שמטופלים כפיתוח אפליקציות Windows.
ייעוץ טכני וסקירת תכנון
אם תרצו לסדר מראש את הגבולות מסוג COM וסדר האתחול, אפשר להיכנס לזה מצד התכנון דרך ייעוץ טכני וסקירת תכנון.
שאלות נפוצות
שאלות נפוצות בפניות בנושא המאמר.
- מה זה Media Foundation? זה שונה מ-COM?
- Media Foundation היא פלטפורמת עיבוד מדיה לטיפול בווידאו ואודיו ב-Windows, ולא כל ה-API כולו הוא COM טהור כפי שהוא. עם זאת, הגבולות בין הרכיבים - source / transform / sink / activation / attributes / callback - מיוצגים על ידי ממשקי COM, ולכן כשמשתמשים בזה, נושאי IUnknown, HRESULT, GUID ו-apartment עולים באופן טבעי. נכון יותר לתפוס את זה כ"פלטפורמת עיבוד מדיה, שבממשקי הגבולות שלה COM חודר לעומק".
- למה צריך גם MFStartup וגם CoInitializeEx?
- כי התפקידים שונים. CoInitializeEx הוא אתחול ספריית COM, ו-MFStartup הוא אתחול פלטפורמת Media Foundation. אתחול COM לבדו לא מספיק, ונדרש גם אתחול בצד Media Foundation. בפועל בשטח, כדאי להחליט מראש איזה ת'רד ישתמש ב-Media Foundation, האם הוא יהיה STA או MTA, ומי אחראי על MFStartup / MFShutdown ו-CoInitializeEx / CoUninitialize - זה מקל אחר כך על ה-callback והשילוב עם ה-UI.
- איך בוחרים בין Source Reader, Sink Writer, Media Session ו-MFT?
- אם רוצים להוציא פריימים או דגימות מקובץ או ממצלמה, נקודת הכניסה היא Source Reader; אם רוצים לכתוב אודיו/וידאו שיצרתם לקובץ, זה Sink Writer. אם רוצים להשאיר לפלטפורמה את הניגון, העצירה, ה-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 נקבע בזמן היצירה, ואי אפשר להחליף אותו אחר כך.