ما هو Media Foundation - لماذا يبدأ في الإحساس بأنّه COM وواجهات Windows الإعلاميّة في آنٍ واحد

· · Media Foundation, COM, C++, تطوير Windows

عندما تبدأ بلمس Media Foundation للمرّة الأولى، غالباً ما يكون الإحساس هكذا:

«اعتقدت أنّني أستخدم واجهة Windows للفيديو / الصوت، لكنّي فجأة أغرق في COM.»

وهذا الردّ طبيعيّ. بمجرّد أن تظهر CoInitializeEx و MFStartup و IMFSourceReader و IMFMediaType و IMFTransform و IMFActivate و HRESULT، إضافةً إلى كثرة الإعدادات المعتمدة على GUID دفعةً واحدة، يبدأ كلّ شيء بالشعور بأنّه أقرب إلى Win32 / COM منه إلى مكتبة وسائط بسيطة.

لا يحاول هذا المقال تغطية واجهة Media Foundation كاملةً كما لو كانت قاموساً. بل يرتّب أوّلاً ثلاثة أسئلة عمليّة:

  • لماذا تظهر مفاهيم COM طبيعيّاً عند استخدام Media Foundation
  • أين يصبح الطابع الـ COM قويّاً بشكل خاصّ
  • من أين تدخل أوّلاً بين Source Reader / Sink Writer / Media Session / MFT

أمثلة الكود مبنيّة على C++، لكنّ طريقة التفكير تكاد تكون نفسها حتّى عندما تصل إلى Media Foundation من .NET عبر غلاف.

المحتويات

  1. النسخة المختصرة
  2. جدول التوجيه الأوّل
  3. الشكل العامّ لـ Media Foundation
  4. الأماكن التي يبدأ فيها Media Foundation بالظهور بمظهر COM
  5. استخدام تقريبيّ كقاعدة عامّة
  6. قائمة فحص عمليّة
  7. مقتطفات من الكود
  8. الخلاصة
  9. المراجع

1. النسخة المختصرة

  • Media Foundation هو منصّة للتعامل مع الصوت والفيديو. سطح الـ API كاملاً ليس ببساطة COM خالصاً.
  • لكنّ الحدود بين source / transform / sink / activation / attributes / callbacks تُعبَّر عنها عبر واجهات COM، لذلك بمجرّد استخدامه، تظهر طبيعيّاً مفاهيم IUnknown و HRESULT و GUIDs ونقاشات apartment / threading.
  • عادةً يكون الأسهل أن تبدأ بـ Source Reader / Sink Writer أوّلاً، ثمّ تنتقل إلى Media Session عندما تحتاج إلى التحكّم بالتشغيل، وتدخل إلى MFT عندما تحتاج إلى مكوّن transform خاصّ بك.

بعبارة أخرى، Media Foundation هو منصّة لمعالجة الوسائط حدودها مشكَّلة بعمق بواسطة COM.

بمجرّد أن تفهم ذلك أوّلاً، يصبح من الأسهل بكثير رؤية لماذا تبدأ الـ API فجأةً بارتداء وجه COM.

2. جدول التوجيه الأوّل

2.1. ما الذي تلمسه أوّلاً حسب هدفك

هذا الجدول الأوّل يسهّل اختيار نقطة الدخول.

ما تريد فعله أوّل شيء تلمسه مدى قوّة الإحساس بالـ COM ملاحظات
سحب frames / samples من ملفّ أو كاميرا Source Reader متوسّط يمكنه أيضاً الاهتمام بتحميل الـ decoder عند الحاجة
كتابة صوت / فيديو مولَّد إلى ملفّ Sink Writer متوسّط يمكنه إدارة الـ encoders و media sinks معاً
التعامل مع التشغيل والإيقاف المؤقّت والـ seek والتزامن A/V والتحكّم بالجودة Media Session عالٍ تحتاج إلى فهم مفهومَي topology و session
إدراج مكوّن transform أو codec من إنشائك MFT عالٍ تصبح الواجهة الأساسيّة هي IMFTransform
تعداد المرشّحين أوّلاً وإنشاء النسخة الفعليّة لما تحتاجه فقط IMFActivate عالٍ ما تستعيده قد لا يكون الكائن الحقيقيّ بعد

2.2. أين يظهر وجه الـ COM

المكان ما يظهر ما يجب فهمه أوّلاً
التهيئة CoInitializeEx, MFStartup تهيئة COM وتهيئة Media Foundation منفصلتان
إنشاء / نقل الكائنات IMFSourceReader, IMFMediaType, IMFTransform معظم الأشياء عبارة عن مؤشّرات واجهة بالإضافة إلى HRESULT
الإعداد IMFAttributes, GUIDs تُمثَّل الإعدادات ومعلومات نوع الوسائط على شكل بيانات key/value بالإضافة إلى GUIDs
التعداد / الإنشاء المؤجَّل IMFActivate, ActivateObject نتائج التعداد ليست دائماً الكائن النهائيّ
التدفّق غير المتزامن IMFSourceReaderCallback, work queues تحتاج إلى التفكير في الـ callbacks والـ apartments
التحكّم بالتشغيل topology, Media Session تدفّق خطّ الأنابيب الكامل هو مفهوم خاصّ بـ Media Foundation

2.3. مصطلحات تستحقّ الفهم أوّلاً

المصطلح المعنى هنا
Media Source نقطة الدخول التي تدخل منها بيانات الوسائط إلى خطّ الأنابيب. يمكن أن يكون ملفّاً أو مصدر شبكة أو جهاز التقاط، وهكذا
MFT Media Foundation Transform. النموذج الموحَّد للـ decoders والـ encoders وكتل معالجة الفيديو/الصوت
Media Sink وجهة بيانات الوسائط، مثل التشغيل أو الإخراج أو كتابة الملفّ
Media Session المكوّن الذي يدير خطّ الأنابيب ككلّ، خاصّةً التشغيل والتزامن
Topology رسم بيانيّ يصف كيفيّة ربط عقد source / transform / sink
Activation Object كائن مساعد يُستخدم لإنشاء الكائن الحقيقيّ لاحقاً. في Media Foundation يُمثَّل عادةً بـ IMFActivate
Attributes مخزن key/value مبنيّ على مفاتيح GUID. يستخدمه Media Foundation في كلّ مكان

إذا أبقيت هذه المصطلحات في ذهنك أوّلاً، يصبح قراءة التوثيق أسهل بكثير.

3. الشكل العامّ لـ Media Foundation

على مستوى عالٍ، Media Foundation يدور حول media pipeline. الـ COM مهمّ، لكن من الأسهل غالباً فهم الشكل العامّ أوّلاً.

flowchart TB
    subgraph Pipeline["Using the whole pipeline"]
        Source1["Media Source"] --> Transform1["MFT"]
        Transform1 --> Sink1["Media Sink"]
        Session["Media Session"] --- Source1
        Session --- Transform1
        Session --- Sink1
    end

    subgraph Direct["Application handles data more directly"]
        Source2["Media Source"] --> Reader["Source Reader (+ decoder)"]
        App["Application"] --> Writer["Sink Writer (+ encoder)"]
        Writer --> Sink2["Media Sink"]
    end

هناك تقريباً طريقتان لاستخدام Media Foundation:

  • استخدام خطّ الأنابيب ككلّ
    • ربط source / transform / sink والسماح لـ Media Session بإدارة تدفّق البيانات والتزامن A/V
  • التعامل مع البيانات بشكل أكثر مباشرةً في التطبيق
    • سحب البيانات من مصدر باستخدام Source Reader ودفعها نحو مخرج باستخدام Sink Writer

الأخير غالباً ما يكون أسهل عندما تريد معالجة الـ frames أو الـ samples بنفسك. الأوّل هو المسار الأكثر طبيعيّة إذا كنت تريد أن تتولّى المنصّة التشغيل والتزامن.

النقطة المهمّة هي أنّ Media Foundation هو منصّة لمعالجة الوسائط، وليس مجرّد كيس من كائنات COM تعبث بها مباشرةً.

لكن بمجرّد أن تبدأ بالنظر إلى الحدود بين تلك المكوّنات، يصبح وجه الـ COM أقوى بكثير. هذا هو موضوع القسم التالي.

4. الأماكن التي يبدأ فيها Media Foundation بالظهور بمظهر COM

4.1. CoInitializeEx و MFStartup يظهران بجانب بعضهما عند التهيئة

هذا أحد أوائل الأشياء التي يبدو غريباً.

قبل أن تتمكّن من «فتح ملفّ فقط» أو «سحب البيانات من كاميرا فقط»، ترى كلاً من CoInitializeEx و MFStartup.

  • CoInitializeEx يهيّئ COM
  • MFStartup يهيّئ منصّة Media Foundation

لذلك، تهيئة COM وحدها ليست كافية. هذه غالباً هي اللحظة الأولى التي يصبح فيها واضحاً أنّ هذا ليس مجرّد «واجهة فيديو» بل منصّة فيها عقد قائم على COM بعمق تحت السطح.

في الكود العمليّ، يساعد أن تقرّر هذه الأمور مبكّراً:

  • أيّ thread سيستخدم Media Foundation
  • ما إذا كان هذا الـ thread سيكون STA أم MTA
  • مَن يملك MFStartup / MFShutdown و CoInitializeEx / CoUninitialize

إذا أبقيت ذلك غامضاً، تصبح مشاكل الـ callback ودمج الـ UI أصعب بكثير في الفهم لاحقاً.

4.2. حدود الكائنات قائمة في معظمها على الواجهات

بمجرّد أن تبدأ بقراءة واجهات Media Foundation، تكتشف أنّ كثيراً من القيم المُعادة ومعاملات الإخراج هي واجهات COM:

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

ما يهمّ هنا هو أنّ ليس بيانات الوسائط نفسها فحسب، بل أوصاف الأنواع وكائنات الإعداد أيضاً تُمثَّل كواجهات.

على سبيل المثال:

  • IMFTransform يمثّل MFT
  • IMFAttributes هو مخزن key/value
  • IMFMediaType هو في جوهره وصف لتنسيق الوسائط مبنيّ فوق IMFAttributes

لذلك حتّى الشيء الذي يبدو وكأنّه «بيانات إعداد» مُشكَّل أيضاً على هيئة واجهة COM. لذلك يدخل IUnknown و QueryInterface و AddRef / Release و HRESULT إلى الصورة بشكل طبيعيّ.

flowchart TD
    IUnknown["IUnknown"]
    IUnknown --> IMFAttributes["IMFAttributes"]
    IMFAttributes --> IMFMediaType["IMFMediaType"]
    IMFAttributes --> IMFActivate["IMFActivate"]
    IUnknown --> IMFSourceReader["IMFSourceReader"]
    IUnknown --> IMFTransform["IMFTransform"]

عند هذه النقطة يصبح من الأسهل بكثير القول:

«Media Foundation هو واجهة وسائط، لكنّ حدوده مُشكَّلة بطريقة COM للغاية.»

4.3. تظهر كائنات التفعيل

أحد الأماكن التي يصبح فيها الطابع الـ COM واضحاً بشكل خاصّ هو نموذج كائنات التفعيل.

IMFActivate كائن مساعد يُستخدم لإنشاء الكائن الحقيقيّ لاحقاً. أسهل نموذج ذهنيّ هو أن يكون الإحساس به نوعاً ما مشابهاً لـ COM class factory.

يظهر هذا في الأماكن التي لا ترجع فيها واجهات التعداد فوراً كائن transform أو sink قابل للاستخدام مباشرةً. بدلاً من ذلك، تُرجع أوّلاً مصفوفة من IMFActivate*. ثمّ تفحص المرشّح، وفقط لاحقاً تستدعي ActivateObject لإنشاء النسخة الفعليّة.

sequenceDiagram
    participant App as App
    participant Enum as Enumeration API
    participant Act as IMFActivate
    participant Obj as IMFTransform / Sink / etc.

    App->>Enum: enumerate candidates
    Enum-->>App: array of IMFActivate*
    App->>Act: inspect attributes
    App->>Act: ActivateObject(...)
    Act-->>App: real COM object

تنسجم هذه البنية جيّداً مع كيفيّة رغبة Media Foundation في:

  • اكتشاف الكتل القابلة للتبادل
  • فحصها
  • وإنشاء النسخة الفعليّة فقط لما هو مطلوب فعلاً

بما أنّ كائنات التفعيل يمكن أن تحمل خصائص أيضاً، فإنّ التدفّق يصبح طبيعيّاً:

  • فحص بيانات تعريف المرشّح
  • ربّما تكوينه
  • إنشاء النسخة الفعليّة لاحقاً

هذا شكل يشبه COM إلى حدّ بعيد.

4.4. تدور معلومات الإعداد ونوع الوسائط حول IMFAttributes و GUIDs

لحظة أخرى يبدو فيها Media Foundation فجأةً «شديد الـ COM» هي عندما تتحوّل الإعدادات إلى غابة من GUIDs. مركز تلك الغابة هو IMFAttributes.

IMFAttributes هو مخزن key/value مفاتيحه GUIDs، ويستخدمه Media Foundation باستمرار.

الشيء المهمّ بشكل خاصّ هو IMFMediaType. IMFMediaType يرث من IMFAttributes ويخزّن معلومات تنسيق الوسائط كخصائص.

الأمثلة النموذجيّة تشمل:

  • النوع الرئيسيّ (صوت أو فيديو)
  • النوع الفرعيّ (H.264 و AAC و RGB32 و PCM وغيرها)
  • حجم الإطار
  • معدّل الإطارات
  • معدّل العيّنات
  • عدد القنوات
flowchart LR
    MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
    MediaType --> Subtype["MF_MT_SUBTYPE"]
    MediaType --> Detail["frame size / FPS / sample rate / etc."]

من السهل أن تختبر هذا كـ «غابة من GUIDs.» لكنّ التصميم الكامن وراء ذلك مباشر فعلاً:

  • استخدام مخزن خصائص للاحتفاظ بالإعدادات
  • تمثيل أنواع الوسائط من خلال نموذج مخزن الخصائص نفسه
  • التفاوض على التنسيقات بين source / transform / sink بقراءة تلك الخصائص ومطابقتها

لذلك النقطة الفعليّة هي:

Media Foundation يستخدم واجهات شبيهة بـ COM و GUIDs لتمثيل الإعدادات وبيانات تعريف نوع الوسائط.

4.5. عدم التزامن و callbacks وخيوط التنفيذ تُعالج أيضاً بأسلوب يشبه COM

مكان آخر يسهل التهوين من شأنه عمليّاً هو السلوك غير المتزامن ونموذج الخيوط.

على سبيل المثال، Source Reader متزامن افتراضيّاً. في الوضع المتزامن، يَحجز ReadSample. حسب الملفّ أو الشبكة أو الجهاز، يمكن أن يصبح زمن الحجز هذا ملحوظاً جدّاً.

إذا أردت الوضع غير المتزامن، تنشئ Source Reader مع callback. هذا يعني:

  • تنفيذ IMFSourceReaderCallback
  • وضع ذلك الكائن في خاصّيّة MF_SOURCE_READER_ASYNC_CALLBACK
  • ثمّ إنشاء Source Reader

هناك أيضاً نقطة threading تهمّ كثيراً: المعالجة غير المتزامنة في Media Foundation تستخدم work queue، وخيوط work queue الخاصّة بـ Media Foundation هي MTA.

هذا يعني أنّه من الأبسط غالباً إبقاء جانب التطبيق في MTA أيضاً.

sequenceDiagram
    participant App as App thread
    participant Reader as Source Reader
    participant Queue as MF work queue (MTA)
    participant Cb as IMFSourceReaderCallback

    App->>Reader: ReadSample(...)
    Reader-->>App: returns immediately
    Reader->>Queue: internal processing
    Queue->>Cb: OnReadSample(...)

النقاط العمليّة هي:

  • لا تلمس كائنات UI الخاصّة بـ STA مباشرةً داخل الـ callback
  • اجعل تنفيذ الـ callback آمناً لخيوط التنفيذ المتعدّدة
  • إذا كانت تحديثات الـ UI مطلوبة، أعد النتيجة فقط إلى thread الـ UI
  • قرّر مبكّراً أيّ خيوط من المتوقّع أن تستقبل callbacks الخاصّة بـ Media Foundation

Media Foundation لا يمتصّ افتراضات STA الخاصّة بك سحريّاً. لذلك في كثير من التطبيقات يكون من الأبسط أن:

تُبقي كود عمّال Media Foundation على جانب MTA، وتبني جسراً صريحاً للعودة إلى الـ UI.

4.6. لكنّ Media Foundation ليس «مجرّد COM»

عند هذه النقطة، يسهل أن تفكّر:

«إذاً Media Foundation هو في الأساس COM.»

هذا ليس صحيحاً تماماً.

Media Foundation له مفاهيم تتجاوز COM العامّ:

  • MFStartup / MFShutdown
  • Media Session
  • topology
  • topology loader
  • presentation clock
  • Source Reader / Sink Writer

هذه ليست مجرّد مفاهيم COM. إنّها النموذج الخاصّ بـ Media Foundation على مستوى المنصّة للتعامل مع خطوط أنابيب الوسائط.

على سبيل المثال، في Media Session، يمكن للتطبيق أن يقدّم topology جزئيّاً، ويمكن لـ topology loader أن يحلّه إلى topology كامل بتزويد الـ transforms المطلوبة. هذا ليس مجرّد «COM بشكل عامّ.» إنّه Media Foundation يتصرّف كمنصّة لمعالجة الوسائط.

flowchart LR
    Partial["Partial Topology<br/>Source -> Output"] --> Loader["Topology Loader"]
    Loader --> Full["Full Topology<br/>Source -> Decoder MFT -> Output"]

لذلك أسهل طريقة لفهمه هي كصورة من طبقتين:

  • Media Foundation يستخدم COM للتعبير عن عقود المكوّنات
  • وفوق ذلك، يعمل كمنصّة لمعالجة الوسائط بمفاهيم خطّ أنابيب خاصّة به

هذا التأطير يجعل الـ API أسهل بكثير في الاستيعاب.

5. استخدام تقريبيّ كقاعدة عامّة

هذا التفرّع الأوّل يكفي بشكل مفاجئ في كثير من الأحيان:

flowchart TD
    Start["What do you want to do?"] --> Q1{"What is the first thing you need?"}
    Q1 -- "I want to read frames / samples" --> A1["Source Reader"]
    Q1 -- "I want to write media to a file" --> A2["Sink Writer"]
    Q1 -- "I need playback control and A/V sync" --> A3["Media Session"]
    Q1 -- "I want to insert a custom transform" --> A4["MFT"]

5.1. ابدأ بـ Source Reader عندما تريد سحب samples

Source Reader هو نقطة دخول سهلة المنال جدّاً عندما تريد سحب بيانات الوسائط من ملفّ أو جهاز.

التطابقات الجيّدة تشمل:

  • استخراج الـ frames من ملفّ فيديو
  • فكّ ترميز الـ samples الصوتيّة
  • سحب الـ frames من كاميرا
  • تغذية مصادر Media Foundation إلى خطّ أنابيب المعالجة الخاصّ بك

يمكن لـ Source Reader أن يهتمّ بتحميل الـ decoder عند الحاجة. لكنّه لا يدير presentation clocks أو التزامن A/V أو التشغيل الفعليّ نيابةً عنك.

من الأسهل التفكير فيه على أنّه:

نقطة دخول لـ قراءة البيانات، وليست نقطة دخول للتشغيل الكامل.

5.2. استخدم Sink Writer عندما تريد كتابة الملفّات

Sink Writer هو النظير على جانب الإخراج.

التطابقات الجيّدة تشمل:

  • كتابة الـ frames المولَّدة إلى ملفّ فيديو
  • ترميز الـ samples الصوتيّة
  • تحويل تنسيق إلى آخر وتخزين النتيجة

يمكن لـ Sink Writer أيضاً جلب الـ encoders و media sinks إلى التدفّق عند الضرورة.

5.3. استخدم Media Session عندما يهمّ التشغيل والتزامن

إذا كان هدفك الفعليّ ليس مجرّد «سحب samples» بل «تشغيل الوسائط بشكل صحيح»، فإنّ Media Session عادةً هو المركز الصحيح.

هو تطابق جيّد عندما تحتاج إلى:

  • تشغيل / إيقاف مؤقّت / seek
  • تزامن الصوت/الفيديو
  • التحكّم بالجودة عبر خطّ الأنابيب
  • الربط القائم على topology بين sources / transforms / sinks

عند هذا المستوى، أنت أقرب بكثير إلى Media Foundation نفسه منك إلى مساعدات Source Reader / Sink Writer الأبسط.

5.4. استخدم MFT عندما تحتاج إلى إدراج مكوّناتك الخاصّة

MFT هو نموذج الـ transform الموحَّد في Media Foundation.

تدخل هذا العالم عندما تريد:

  • بناء decoder أو encoder خاصّ بك
  • إدراج كتلة معالجة صوت / فيديو مخصّصة
  • تعداد مكوّنات الـ transform واختيارها بنفسك
  • التحكّم بخطّ الأنابيب بشكل أكثر صراحةً من المسار التلقائيّ

عالم MFT يجلب IMFTransform و IMFActivate والتفاوض على نوع الوسائط وإدارة الـ samples / buffers أقرب بكثير إلى السطح. لهذا السبب يكون عادةً أوضح عدم البدء هناك أوّلاً ما لم تكن بحاجة فعليّة لذلك.

6. قائمة فحص عمليّة

البند ما يجب تقريره / فحصه ما يميل إلى الخطأ إذا تجاوزته
ملكيّة التهيئة قرّر أين يُستدعى CoInitializeEx و MFStartup وأين تعيش نظائر الإيقاف الخاصّة بهما تهيئة مفقودة وترتيب إيقاف مربك
نموذج apartment قرّر مسبقاً ما إذا كانت الخيوط ذات الصلة STA أم MTA ارتباك الـ callback وتصادمات الـ UI
وضع Source Reader قرّر متزامن مقابل غير متزامن وقت الإنشاء حجز غير متوقّع أو تغييرات وضع متأخّرة مستحيلة
التفاوض على نوع الوسائط عدّد بشكل صريح واختر تنسيقات الإخراج الفعليّة MF_E_INVALIDMEDIATYPE وعدم تطابق تنسيقات غير متوقّع
عمر الكائنات اجعل مسؤوليّة Release و Unlock والإيقاف صريحة تسرّبات و buffers عالقة وعدم اتّساق في الإيقاف
التعامل مع كائنات التفعيل ميّز بين الكائن الحقيقيّ وغلاف IMFActivate فشل لأنّك افترضت أنّ المرشّح كان فعلاً الكائن النهائيّ
فهم topology اعرف ما إذا كنت تنظر إلى topology جزئيّ أم topology كامل محلول التعلّق وأنت تتوقّع أن يحدث الربط التلقائيّ سحريّاً
فحص الأخطاء افحص HRESULT و stream flags والأحداث باتّساق تفوّت الإخفاقات الجزئيّة
دمج الـ UI لا تدع الـ callbacks تلمس الـ UI مباشرةً؛ مرّر النتيجة بشكل صريح تعليقات وحالات سباق وإخفاقات عبر الخيوط مربكة

الأولويّات الثلاث الأكثر أهمّيّة هي:

  1. اختيار واجهة الدخول الصحيحة أوّلاً
  2. تقرير نموذج apartment قبل أن ينمو كلّ شيء حوله
  3. عدم التعامل مع التفاوض على نوع الوسائط باستهانة

7. مقتطفات من الكود

هذه ليست مقصودة كأمثلة كاملة. إنّها قصيرة بما يكفي لإظهار أين يبدأ Media Foundation بالشعور بأنّه COM.

7.1. التهيئة

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();
}

هذه إحدى أوائل النقاط التي يصبح فيها شكل الـ COM مرئيّاً:

  • CoInitializeEx
  • MFStartup

إذا كانت طبقة أخرى تملك بالفعل تهيئة COM، فلا بأس بذلك أيضاً. المهمّ هو جعل المسؤوليّة صريحة.

7.2. إنشاء 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;

    // Process IMFMediaBuffer extracted from pSample here

done:
    SafeRelease(&pSample);
    SafeRelease(&pType);
    SafeRelease(&pReader);
    return hr;
}

ما يجعله هذا المثال مرئيّاً هو:

  • الـ reader ونوع الوسائط هما واجهات COM
  • الإعداد قائم على GUID
  • النتائج محمولة عبر HRESULT
  • الوضع المتزامن يعني أنّ ReadSample يَحجز

حتّى «أريد فقط إطاراً واحداً» يبدو بالفعل مُشكَّلاً بطريقة COM للغاية عند حدود Media Foundation.

7.3. إنشاء Source Reader في الوضع غير المتزامن

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 في مجموعة الخصائص قبل إنشاء الـ reader.

هذا يعني:

  • الـ callback نفسه واجهة COM
  • يُكوَّن الوضع غير المتزامن من خلال IMFAttributes
  • يُقرَّر الوضع وقت الإنشاء

عمليّاً، من المهمّ أن يكون تنفيذ IMFSourceReaderCallback آمناً لخيوط التنفيذ المتعدّدة وألّا يلتقط كائنات UI مباشرةً.

7.4. تعداد وتفعيل MFTs باستخدام 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) || count == 0)
    {
        return FAILED(hr) ? hr : MF_E_TOPO_CODEC_NOT_FOUND;
    }

    hr = ppActivate[0]->ActivateObject(IID_PPV_ARGS(ppTransform));

    for (UINT32 i = 0; i < count; ++i)
    {
        SafeRelease(&ppActivate[i]);
    }
    CoTaskMemFree(ppActivate);

    return hr;
}

ما يهمّ هنا هو أنّ التعداد لا يسلّمك بالضرورة الـ transform النهائيّ مباشرةً. قد تحصل على كائنات تفعيل أوّلاً وتفحصها وفقط بعدها تنشئ النسخة الفعليّة من الـ transform الفعليّ.

هذا جزء مميّز جدّاً من تصميم Media Foundation المُشكَّل بطريقة COM.

8. الخلاصة

Media Foundation ليس «مجرّد COM»، لكنّه يستخدم COM بالتأكيد لتعريف كثير من حدوده الأكثر أهمّيّة.

إذا فهمت:

  • لماذا يجلس CoInitializeEx و MFStartup جنباً إلى جنب
  • لماذا الكثير من الكائنات الأساسيّة قائم على الواجهات
  • لماذا تظهر طبيعيّاً كائنات التفعيل وخصائص GUID و callbacks ومخاوف apartment

فإنّ الـ API يصبح أسهل بكثير في القراءة.

أبسط قاعدة عمليّة هي:

  • ابدأ بـ Source Reader / Sink Writer إذا استطعت
  • انتقل إلى Media Session عندما يكون تنظيم التشغيل مهمّاً
  • ادخل إلى MFT فقط عندما تكون مشكلتك الفعليّة هي دمج transform مخصّص

هذا يُبقي حجم العبء المفهوميّ أكثر قابليّة للإدارة بكثير.

9. المراجع

مقالات ذات صلة

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

كيفيّة دمج الصورة والنصّ في كلّ إطار من فيديو MP4 باستخدام Media Foundation - ترتيب Source Reader والرسم وتحويل الألوان وSink Writer مع نسخة بملفّ .cpp واحد قابل للّصق مباشرةً

نُنظِّم في هذا المقال خطوات Media Foundation لرسم شعار ونصّ على كلّ إطار MP4 بعقليّة Source Reader ثمّ Sink Writer، مع نموذج C++ بملفّ وا...

قراءة المقالة

أين يتصل هذا الموضوع

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

العودة إلى المدونة