Media Foundation学习笔记(四)Media Foundation的架构 Platform API

Media Foundation Platform API

 

初始化

HRESULT hr = MFStartup(MF_VERSION);

注意:如果应用程序编译时使用的和系统上Media Foundation的dll不匹配的头文件,MFStartup会返回MF_E_BAD_STARTUP_VERSION错误!

 

MFShutdown();

 

异步方法

在Media Foundation中,大部分操作是使用的异步方式。总所周知,异步操作可以提升性能。一个异步操作在Media Foundation中是用Beginxxx和Endxxx开头字符定义的一对方法,这2个函数定义了流上的一个异步操作。

Beginxxx方法总是包含2个参数:一个IMFAsyncCallback接口对象和一个可选的状态对象。应用程序对流对象调用Beginxxx方法成功后,流对象执行完成会调用这个IMFAsyncCallback接口对象的IMFAsyncCallback::Invoke(IMFAsyncResult* pResult)方法通知应用程序,应用程序应该在IMFAsyncCallback接口对象的Invoke方法被调用后,在Invoke方法里面或者其他线程里面调用Endxxx方法。通常要将在Invoke方法中收到的IMFAsyncResult对象指针作为参数传给Endxxx方法

Beginxxx方法如果返回一个失败代码,则回调函数不会被流对象调用。

Endxxx方法返回异步操作是成功还是失败的结果。

IMFAsyncCallback::Invoke方法是被另外的线程调用的,应用程序要保证Invoke中的代码是线程安全的。Invoke方法中不要阻塞,不然可能会阻塞流的Pipeline。

 

CMyCallback展现了如何实现IMFAsyncCallback接口:

#include 

class CAsyncCallback : public IMFAsyncCallback 
{
public:
    CAsyncCallback () : m_cRef(1) { }
    virtual ~CAsyncCallback() { }

    STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
    {
        static const QITAB qit[] = 
        {
            QITABENT(CAsyncCallback, IMFAsyncCallback),
            { 0 }
        };
        return QISearch(this, qit, riid, ppv);
    }

    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    }
    STDMETHODIMP_(ULONG) Release()
    {
        long cRef = InterlockedDecrement(&m_cRef);
        if (cRef == 0)
        {
            delete this;
        }
        return cRef;
    }

    STDMETHODIMP GetParameters(DWORD* pdwFlags, DWORD* pdwQueue)
    {
        // Implementation of this method is optional.
        return E_NOTIMPL;
    }

    STDMETHODIMP Invoke(IMFAsyncResult* pAsyncResult) = 0;
    // TODO: Implement this method. 

    // Inside Invoke, IMFAsyncResult::GetStatus to get the status.
    // Then call the EndX method to complete the operation. 

private:
    long    m_cRef;
};

class CMyCallback : public CAsyncCallback
{
    HANDLE m_hEvent;
    IMFByteStream *m_pStream;

    HRESULT m_hrStatus;
    ULONG   m_cbRead;

public:

    CMyCallback(IMFByteStream *pStream, HRESULT *phr) 
        : m_pStream(pStream), m_hrStatus(E_PENDING), m_cbRead(0)
    {
        *phr = S_OK;

        m_pStream->AddRef();

        m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

        if (m_hEvent == NULL)
        {
            *phr = HRESULT_FROM_WIN32(GetLastError());
        }
    }
    ~CMyCallback()
    {
        m_pStream->Release();
        CloseHandle(m_hEvent);
    }

    HRESULT WaitForCompletion(DWORD msec)
    {
        DWORD result = WaitForSingleObject(m_hEvent, msec);

        switch (result)
        {
        case WAIT_TIMEOUT:
            return E_PENDING;

        case WAIT_ABANDONED:
        case WAIT_OBJECT_0:
            return m_hrStatus;

        default:
            return HRESULT_FROM_WIN32(GetLastError());
        }
    }

    ULONG   GetBytesRead() const { return m_cbRead; }

    STDMETHODIMP Invoke(IMFAsyncResult* pResult)
    {
        m_hrStatus = m_pStream->EndRead(pResult, &m_cbRead);

        SetEvent(m_hEvent);

        return S_OK;
    }
};


Work Queues

Work Queues中的Work Items都是实现了IMFAsyncCallback接口的对象,Work Queue拥有一个线程不断从Work Queue取出Work Item并分发它们。

Media Foundation创建了几个标准的Work Queues,称为“Platform work queues”。应用程序可以自己创建Work Queues,称为“Private work queues”。Work Queue用标识符来识别,例如,MFASYNC_CALLBACK_QUEUE_TIMER。

 

创建Work Queue:MFAllocateWorkQueue。

 

添加Work Item:MFPutWorkItem和MFPutItemEx。

 

添加定时执行的 Work Item:MFC ScheduledWorkItem和MFC ScheduledWorkItemEx。定时执行的Work Item总是使用标识符是MFASYNC_CALLBACK_QUEUE_TIMER的“Platform work queues”。

取消定时执行的Work Item:MFCancelWorkItem。

 

添加周期执行的回调:MFAddPeriodicCallback。定时周期是固定的不能被改变,大约是10毫秒,可以通过MFGetTimerPeriodicity获取精确的间隔。周期执行的回调总是使用标识符是MFASYNC_CALLBACK_QUEUE_TIMER的“Platform work queues”。注意,不像其他的Work Item,周期执行的回调不使用IMFAsyncCallbak接口,而是使用函数指针MFPERIODICCALLBACK

 

取消周期执行的回调:MFRemovePeriodicCallback。

 

如果有超过一个的线程或组件共享Work Queue,需要使用MFLockWorkQueue和MFUnlockWorkQueue来为Work Queue加锁(防止Work Queue被platform释放,该加锁实际是addref,并不会导致第二次调用MFLockQorkQueue阻塞)和解锁。注意:MFAllocateWorkQueue中自动调用了一次MFLockQWorkQueue,因此,MFAllocateWorkQueue后必须要出现一次MFUnlockWorkQueue。

 

使用Work Queue的例子:

DWORD idWorkQueue = 0;
HRESULT hr = S_OK;

// Create a new work queue.
hr = MFAllocateWorkQueue(&idWorkQueue);

// Put an item on the queue.
if (SUCCEEDED(hr))
{
    hr = MFPutWorkItem(idWorkQueue, pCallback, NULL);
}

// Wait for the callback to be invoked.
if (SUCCEEDED(hr))
{
    WaitForSingleObject(hEvent, INFINITE);
}

// Release the work queue.
if (SUCCEEDED(hr))
{
    hr = MFUnlockWorkQueue(idWorkQueue);
}

MFShutdown会自动关闭Work Queues的线程,所以应用程序需要在调用MFShutDown之前确保Work Queues占用的资源都被释放,否则会有内存泄露。如果在调用MFShutDown之前调用了MFLockPlatform,则MFShutDown不会马上关闭Work Queues的资源,而会等等几百毫秒,再关闭。调用MFLockPlatform,然后释放资源,释放资源后,需要调用MFUnlockPlatform。IMFAsyncResult接口的默认实现是,当IMFAsyncResult接口对象被创建的时候会自动锁住Media Foundation Platform,当IMFAsyncResult接口对象被释放的时候自动解锁platform,如果自己实现IMFAsyncResult接口,则需要手动调用MFLockPlatform和MFUnlockPlatform来锁住platfrom。

 

MFC ScheduledWorkItem和MFC ScheduledWorkItemEx的参数是负值,单位是毫秒,例如:设定一个5秒后执行的回调,参数是-5000。

 

Media Event Generators

对象使用events来通知异步操作的完成,或将对象状态相关的改变通知应用程序。

 

如果对象要发送时间,就需要暴露IMFMediaEventGenerator接口。应用程序使用以下2种方式查询event:

1)       IMFMediaEventGenerator::GetEvent,这个方法是同步方法。如果对象的event队列中有event,则立即返回,如果没有,则阻塞或者返回失败,是否阻塞还是返回失败依传入的参数而定;

2)       IMFMediaEventGenerator::BeginGetEvent,这个方法是异步方法。像前面讲的,提供一个IMFAsyncCallback接口,Invoke被调用后,调用EndGetEvent方法。

 

Event对象提供IMFMediaEvent接口。IMFMediaEvent从IMFAttributes继承。

 

Event对象队列:MFCreateEventQueue。

Event对象队列接口:IMFMediaEventQueue。

添加Event对象:IMFMediaEventQueue::QueueEvent、IMFMediaEventQueue::QueueEventParamVar、IMFMediaEventQueue::QueueEventParamUnk。

 

服务接口

在Media Foundataion中,有些接口不能通过QueryInterface获取,而只能通过IMFGetService::GetService获取。IMFGetService::GetService接收服务标识符的GUID,返回服务对象的接口指针。不同于QueryInterface,IMFGetService::GetService获取的对象是另一个对象而不是被调用对象。例如,在DShow中使用Enhanced Video Render就只能使用IMFGetService::GetService获取IMFVideoDisplayControl指针来设置播放窗口。

 

Activation对象

Activation对象用于创建其他对象,有点类似于类工厂。Activation对象暴露IMFActivate接口。

Activation对象让应用可以延迟创建目标对象。应用创建对象前可以通过检查Activation对象的属性来获取目标对象的信息。Activation对象也可以被序列化,这就使得可以在其他进程创建目标对象。

创建和销毁目标对象:IMFActivate::ActivateObject和IMFActivate::ShutdownObject。

Activation对象能拥有属性,IMFActivate接口从IMFAttributes接口继承。一些activation对象属性来配置要创建的对象。

 

PresentationClock

Presentation clock是为Presentation产生时钟时间的对象。Presentation clock中报告的时间称为“Presentation时间”。Presentation中所有的流都被同步到Presentation时间。

接口:IMFPresentationClock、IMFRateControl、IMFTimer和IMFShutdown。

 

Media Sink使用Presentation时间来安排什么时候Render样本。Media Sources和transforms不使用Presentation Clock,因为它们不安排什么时候Deliver样本,而是在Pipeline需要新样本的时候产生样本。

Media Session处理了“创建Presentation Clock”、“选择时间源”等等所有的细节,因此应用程序除了在回放过程中“获取当前的Presentation时间”外,不需要调用Presentation Clock的其他方法。

 

获取当前的Presentation时间使用IMFPresentationClock::GetTime。得到的时间是以100-nano秒(1万个为1毫秒)为单位,因此1秒就是10^7。

 

开始时钟:IMFPresentationClock::Start;

暂停时钟:IMFPresentationClock::Pause;

停止时钟:IMFPresentationClock::Stop;

时钟的跳动步进是1.0(100-nano秒)。要改变步进,获取IMFRateControl接口,使用IMFRateControl::SetRate。

对象可以获取Presentation Clock的状态改变(包括步进的改变)的通知。获取方式:实现IMFClockStateSink接口,调用IMFPresentationClock::AddClockStateSink,取消获取调用IMFPresentationClock::RemoveClockStateSink。

 

创建和销毁Presentation Clock:MFCreatePresentationClock和IMFShutdown::Shutdown。

 

Presentation Clock实际上并不实现一个时钟,它是从其他称为“Presentation时间源”的对象获取时钟时间。时间源对象是一个实现了IMFPresentationTimeSource接口和IMFClockStateSink接口的对象。

 

Presentation Clock对象被创建的时候,并不拥有一个时间源对象,需要调用IMFPresentationClock::SetTimeSources为它设置一个时间源对象。通常,由于音频Renderke可以使用声卡的频率最为时钟,因此音频Render可以作为时间源对象;另外,也可以通过MFCreateSystemTimeSource创建一个基于系统时间的时间源对象,系统时间源可以在没有Media Sink提供时间源的情况下使用。

 

有2中情况Media Sink不遵循Presentation Clock:

1)rateless Media Sink:如写文件。对该类Media Sink调用IMFMediaSink::GetCharacteristic方法返回MEDIASINK_RATELESS。

2)一些Media Sink不能Match Rate的时候。这时调用IMFMediaSink::GetCharacteristic返回MEDIASINK_CANNOT_MATCH_CLOCK。

 

 

你可能感兴趣的:(MediaFundation)