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 <shlwapi.h>
- 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。