媒体基础和 COM

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

应用程序的最佳做法

在媒体基础中,异步处理和回调由工作队列处理。 工作队列始终具有多线程单元 (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 Foundatio 在进程内使用时直接指针,但在使用 Media Foundatio 跨进程时是 MTA 代理。

注意

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

 

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

Media Foundation 组件的最佳做法

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

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

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

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

 

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

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

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

总结

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

媒体基础平台 API

Media Foundation 体系结构