Windows Store GIF player 诞生记
在Win8上面,Image source切换的时候有bug。当我们短时间定时切换的时候,Image不能正常地显示对应的图片。Image控件又不支持GIF播放,所以GIF图片的播放就是一个非常头痛的问题。
正巧,最近在研究MFSource,我就突发奇想,能不能用MFSource去播放GIF图片。
结果成功了,项目文件在这里 https://gifwin8player.codeplex.com/ 大家可以和我一起来维护这个项目。
接下来,跟大家分享一下我在开发中遇到的技术问题。希望能给大家一些帮助。
首先MFSource,可以帮助我们拓展MediaElement所支持的媒体类型,和添加一些新的编码解码。微软有一个非常好的例程 http://code.msdn.microsoft.com/windowsapps/Media-extensions-sample-8e1b8275 这个例程里面演示了很多技术,MFSource可以帮助我们添加播放器支持的协议,比如说我们需要播放器播放一个加密的url地址,我们也可以用他添加新的解码器。MFT可以帮助我们增加一些视频特效。
我的例程就是基于修改MFSource,这个项目里关于MFSource只有三个类。GIFByteStreamHandler, GIFSrc,GIFStream。GIFByteStreamHandler是主要处理媒体打开时候的操作,这个媒体既可以是流,也可以是URL。同时这个类也可以得到应用传来的参数(IPropertyStore *pProps)。GIFSrc,这个类是起主要作用的,需要填入视频的一些信息,比如尺寸,还要给出每一帧的数据。GIFStream这个类是维护应答的,播放器会调(RequestSample(IUnknown* pToken);)这个函数来请求帧,然后我们再用(m_pEventQueue->QueueEventParamUnk(MEMediaSample, GUID_NULL, S_OK, pSample);)来应答。
Media Foundation是基于消息应答的机制工作的。与Win32的MF不同,Windows Store 里面的MF没有消息队列的API,不过例程中已经实现了一个消息队列。
我修改的思路也很简单,就是在需要的时候,给出帧的数据和时间。
首先,修改的是CGIFByteStreamHandler::BeginCreateObject函数。如果我们应用中调用MediaElement.Source = new Uri(**)的时候,我们可以从LPCWSTR pwszURL 参数中获得这个Uri。如果我们是用MediaElement.SetSource()函数的话,传入的就是流。MediaElement.Source 不能指定任意的文件,比如不能指定图片库的某一个图片,但是像ms-appx这样的路径就可以获取。
CGIFSource是单例运行的,CGIFSource::BeginOpen函数用来打开流,WIC需要的流是IStream,IMFByteStream可以直接转换成IStream,但是那个函数不能在Windows Store 里面用。我们只能通过IRandomAccessStream做中转。代码如下
if (SUCCEEDED(hr)) { Microsoft::WRL::ComPtr<ABI::Windows::Storage::Streams::IRandomAccessStream> iRandomstream; hr = MFCreateStreamOnMFByteStreamEx( pStream, IID_PPV_ARGS(&iRandomstream) ); hr = CreateStreamOverRandomAccessStream( reinterpret_cast<IUnknown*>(iRandomstream.Get()), IID_PPV_ARGS(&istream) ); }
这里面还需要额外的设置,在GIFSource.idl里面添加 import "windows.storage.idl"; 引用头文件#include <Shcore.h>和对应的Shcore.lib。我试过添加#include <Windows.winmd>,这个会导致编译报错,在idl里面用import也要注意大小写要跟(C:Program Files (x86)Windows Kits8.0Includewinrt)里面的一致。
MF中的异步操作是通过callback模式来做的,
// Create an async result object. We'll use it later to invoke the callback. if (SUCCEEDED(hr)) { hr = MFCreateAsyncResult(NULL, pCB, pState, &m_pBeginOpenResult); } if (m_pBeginOpenResult) { hr = m_pBeginOpenResult->SetStatus(hr); if (SUCCEEDED(hr)) { hr = MFInvokeCallback(m_pBeginOpenResult); } }