媒体基础和 COM

Microsoft Media Foundation 混合使用 COM 构造,但不是完全基于 COM 的 API。 本主题介绍 COM 与媒体基础之间的交互。 它还定义了开发 Media Foundation 插件组件的一些最佳做法。 遵循这些做法有助于避免一些常见但微妙的编程错误。

应用程序的最佳做法

在 Media Foundation 中,异步处理和回调由 工作队列处理。 工作队列始终具有多线程单元 (MTA) 线程,因此,如果应用程序也运行在 MTA 线程上,则其实现将更简单。 因此,建议使用 COINIT_MULTITHREADED 标志调用 CoInitializeEx

Media Foundation 不会将单线程单元 (STA) 对象封送到工作队列线程。 它也不确保维护 STA 固定值。 因此,STA 应用程序必须小心不要将 STA 对象或代理传递给媒体基础 API。 Media Foundation 不支持仅 STA 的对象。

如果具有 MTA 或自由线程对象的 STA 代理,则可以使用工作队列回调将对象封送到 MTA 代理。 CoCreateInstance 函数可以返回原始指针或 STA 代理,具体取决于在注册表中为该 CLSID 定义的对象模型。 如果返回 STA 代理,则不得将指针传递给媒体基础 API。

例如,假设要将 IPropertyStore 指针传递给 IMFSourceResolver::BeginCreateObjectFromURL 方法。 可以调用 PSCreateMemoryPropertyStore 来创建 IPropertyStore 指针。 如果从 STA 调用,则必须先封送指针,然后再将其传递给 BeginCreateObjectFromURL

以下代码演示如何将 STA 代理封送到媒体基础 API。

class CCreateSourceMarshalCallback
    : public IMFAsyncCallback
{
public:
    CCreateSourceMarshalCallback(
        LPCWSTR szURL, 
        IMFSourceResolver* pResolver, 
        IPropertyStore* pSourceProps, 
        IMFAsyncCallback* pCompletionCallback, 
        HRESULT& hr
        )
        : m_szURL(szURL), 
          m_pResolver(pResolver), 
          m_pCompletionCallback(pCompletionCallback),
          m_pGIT(NULL),
          m_cRef(1)
    {
        hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable, NULL, 
            CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pGIT));

        if(SUCCEEDED(hr))
        {
            hr = m_pGIT->RegisterInterfaceInGlobal(
                pSourceProps, IID_IPropertyStore, &m_dwInterfaceCookie);
        }
    }
    ~CCreateSourceMarshalCallback()
    {
        SafeRelease(&m_pResolver);
        SafeRelease(&m_pCompletionCallback);
        SafeRelease(&m_pGIT);
    }


    STDMETHOD_(ULONG, AddRef)()
    {
        return InterlockedIncrement(&m_cRef);
    }

    STDMETHOD_(ULONG, Release)()
    {
        LONG cRef = InterlockedDecrement(&m_cRef);
        if (0 == cRef)
        {
            delete this;
        }
        return cRef;
    }

    STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppvObject)
    {
        static const QITAB qit[] = 
        {
            QITABENT(CCreateSourceMarshalCallback, IMFAsyncCallback),
            { 0 }
        };
        return QISearch(this, qit, riid, ppvObject);

    }

    STDMETHOD(GetParameters)(DWORD* pdwFlags, DWORD* pdwQueue)
    {
        return E_NOTIMPL;
    }

    STDMETHOD(Invoke)(IMFAsyncResult* pResult)
    {
        IPropertyStore *pSourceProps = NULL;

        HRESULT hr = m_pGIT->GetInterfaceFromGlobal(
            m_dwInterfaceCookie, 
            IID_PPV_ARGS(&pSourceProps)
            );

        if(SUCCEEDED(hr))
        {
            hr = m_pResolver->BeginCreateObjectFromURL(
                m_szURL, MF_RESOLUTION_MEDIASOURCE, pSourceProps, NULL, 
                m_pCompletionCallback, NULL);
        }

        SafeRelease(&pSourceProps);
        return hr;
    }

private:
    LPCWSTR m_szURL;
    IMFSourceResolver *m_pResolver;
    IMFAsyncCallback *m_pCompletionCallback;
    IGlobalInterfaceTable *m_pGIT;
    DWORD m_dwInterfaceCookie;
    LONG m_cRef;
};

有关全局接口表的详细信息,请参阅 IGlobalInterfaceTable

如果在进程内使用 Media Foundation,则从 Media Foundation 方法和函数返回的对象是指向对象的直接指针。 对于跨进程媒体基础,这些对象可能是 MTA 代理,如果需要,应将其封送到 STA 线程中。 同样,在回调中获取的对象(例如,来自 MESessionTopologyStatus 事件的拓扑)是在进程内使用 Media Foundation 时的直接指针,但在跨进程使用 Media Foundation 时是 MTA 代理。

注意

使用媒体基础跨进程最常见的方案是使用 受保护的媒体路径 (PMP) 。 但是,这些备注适用于通过 RPC 使用媒体基础 API 的任何情况。

 

IMFAsyncCallback 的所有实现都应与 MTA 兼容。 这些对象根本不需要是 COM 对象。 但是,如果是,则无法在 STA 中运行。 IMFAsyncCallback::Invoke 函数将在 MTA 工作队列线程上调用,提供的 IMFAsyncResult 对象将是直接对象指针或 MTA 代理。

媒体基础组件的最佳做法

有两类媒体基础对象需要关注 COM。 某些组件(如转换或字节流处理程序)是由 CLSID 创建的完整 COM 对象。 对于进程内媒体基础和跨进程媒体基础,这些对象必须遵循 COM 单元的规则。 其他 Media Foundation 组件不是完整的 COM 对象,但需要 COM 代理才能进行跨进程播放。 此类别中的对象包括媒体源和激活对象。 如果这些对象将仅用于进程内媒体基础,则可以忽略单元问题。

尽管并非所有 Media Foundation 对象都是 COM 对象,但所有 Media Foundation 接口都派生自 IUnknown。 因此,所有 Media Foundation 对象都必须根据 COM 规范实现 IUnknown ,包括引用计数和 QueryInterface 的规则。 所有引用计数的对象还应确保 DllCanUnloadNow 不允许卸载模块,同时对象仍然保留。

媒体基础组件不能是 STA 对象。 许多 Media Foundation 对象根本不需要是 COM 对象。 但是,如果是,则无法在 STA 中运行。 所有 Media Foundation 组件都必须是线程安全的。 某些媒体基础对象也必须是自由线程对象或单元中性对象。 下表指定了自定义接口实现的要求:

接口 类别 必需单元
IMFActivate 跨进程代理 自由线程或中性
IMFByteStreamHandler COM 对象 MTA
IMFContentProtectionManager 跨进程代理 自由线程或中性
IMFQualityManager COM 对象 自由线程或中性
IMFMediaSource 跨进程代理 自由线程或中性
IMFSchemeHandler COM 对象 MTA
IMFTopoLoader COM 对象 自由线程或中性
IMFTransform COM 对象 MTA

 

可能会有其他要求,具体取决于实现。 例如,如果媒体接收器实现另一个接口,使应用程序能够对接收器进行直接函数调用,则接收器需要是自由线程或中性,以便它可以处理直接跨进程调用。 任何对象都可以是自由线程的;此表指定了最低要求。

实现自由线程或中性对象的推荐方法是聚合自由线程封送处理程序。 有关更多详细信息,请参阅 有关 CoCreateFreeThreadedMarshaler 的 MSDN 文档。 根据不将 STA 对象或代理传递给媒体基础 API 的要求,自由线程对象无需担心在自由线程组件中封送 STA 输入指针。

使用长函数工作队列 (MFASYNC_CALLBACK_QUEUE_LONG_FUNCTION) 的组件必须更加谨慎。 长函数工作队列中的线程创建自己的 STA。 对回调使用长函数工作队列的组件应避免在这些线程上创建 COM 对象,并且需要注意根据需要将代理封送给 STA。

总结

如果应用程序从 MTA 线程与 Media Foundation 交互,则时间会更容易,但可以谨慎地从 STA 线程使用 Media Foundation。 Media Foundation 不处理 STA 组件,应用程序应小心不要将 STA 对象传递给 Media Foundation API。 某些对象具有其他要求,尤其是在跨进程情况下运行的对象。 遵循这些准则有助于避免媒体处理中的 COM 错误、死锁和意外延迟。

媒体基础平台 API

Media Foundation 体系结构