在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)
);
}

change stream

这里面还需要额外的设置,在GIFSource.idl里面添加 import "windows.storage.idl";  引用头文件#include <Shcore.h>和对应的Shcore.lib。我试过添加#include <Windows.winmd>,这个会导致编译报错,在idl里面用import也要注意大小写要跟(C:\Program Files (x86)\Windows Kits\8.0\Include\winrt)里面的一致。

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);
}
}

call back

这段代码是创建一个异步请求的结果,然后再激发这个结果,最后的结果在CGIFByteStreamHandler::Invoke 中执行。

打开文件之后,我们要创建视频的描述,这段代码是在CGIFSource::CreateStream 中被DeliverPlayload()调用。

HRESULT CGIFSource::CreateStream(long stream_id)
{
OutputDebugString(L"call CreateStream function\n");
HRESULT hr = S_OK;
IMFMediaType *pType = NULL;
IMFStreamDescriptor *pSD = NULL;
CGIFStream *pStream = NULL;
IMFMediaTypeHandler *pHandler = NULL; hr = MFCreateMediaType(&pType); if (SUCCEEDED(hr))
{
hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
} if (SUCCEEDED(hr))
{
hr = pType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB24);
} // Format details.
if (SUCCEEDED(hr))
{
// Frame size
hr = MFSetAttributeSize(
pType,
MF_MT_FRAME_SIZE,
m_pImage->GetWidth(),
m_pImage->GetHeight()
);
}
if (SUCCEEDED(hr))
{
// Frame rate
hr = MFSetAttributeRatio(
pType,
MF_MT_FRAME_RATE,
, );
// 23.976 fps
} if (SUCCEEDED(hr))
{
// Sequence header. hr=pType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT,); } if (SUCCEEDED(hr))
{
// Create the stream descriptor from the media type.
hr = MFCreateStreamDescriptor(stream_id, , &pType, &pSD);
} // Set the default media type on the stream handler.
if (SUCCEEDED(hr))
{
hr = pSD->GetMediaTypeHandler(&pHandler);
}
if (SUCCEEDED(hr))
{
hr = pHandler->SetCurrentMediaType(pType);
} // Create the new stream.
if (SUCCEEDED(hr))
{
pStream = new (std::nothrow) CGIFStream(this, pSD, hr);
if (pStream == NULL)
{
hr = E_OUTOFMEMORY;
}
} if (SUCCEEDED(hr))
{
m_streams.AddStream(stream_id, pStream);
pStream->AddRef();
}
SafeRelease(&pType);
SafeRelease(&pSD);
SafeRelease(&pStream);
return hr;
}

create stream

steam创建好之后,要创建MF里面的描述 通过这个函数InitPresentationDescriptor()

这个函数执行完之后,初始化的工作就完成了。

用户点播放的时候,系统会调CGIFSource::CreatePresentationDescriptor这个函数来获取视频信息,然后调用CGIFSource::Start。接着内部的消息队列会调CGIFSource::DoStart,向MF中发MESourceStarted消息,接到这个消息之后CGIFStream::RequestSample会被触发,如果CGIFStream里面维护的队列为空的话,就会问CGIFSource要数据,最后定为到DeliverPayload()函数,

HRESULT CGIFSource::DeliverPayload()
{
// When this method is called, the read buffer contains a complete
// payload, and the payload belongs to a stream whose type we support. wchar_t buf[]; swprintf_s(buf,L"call do DeliverPayload function with frame %d",m_CurrentIndex);
OutputDebugString((LPCWSTR)buf);
HRESULT hr = S_OK;
//GIFPacketHeader packetHdr;
CGIFStream *pStream = NULL; // not AddRef'd IMFMediaBuffer *pBuffer = NULL;
IMFSample *pSample = NULL;
BYTE *pData = NULL; // Pointer to the IMFMediaBuffer data. IWICBitmapFrameDecode *pWicFrame = NULL; // If we are still opening the file, then we might need to create this stream.
if (SUCCEEDED(hr))
{
if (m_state == STATE_OPENING)
{
hr = CreateStream();
}
}
// Create a media buffer for the payload.
if (SUCCEEDED(hr))
{
hr = MFCreateMemoryBuffer(m_pImage->GetBuffSize(), &pBuffer);
} if (SUCCEEDED(hr))
{
hr = pBuffer->Lock(&pData, NULL, NULL);
} if (SUCCEEDED(hr))
{
hr=m_pImage->GetFrameBuffByIndex(m_CurrentIndex,&pData);
} if (SUCCEEDED(hr))
{
hr = pBuffer->Unlock();
} if (SUCCEEDED(hr))
{
hr = pBuffer->SetCurrentLength(m_pImage->GetBuffSize());
} // Create a sample to hold the buffer.
if (SUCCEEDED(hr))
{
hr = MFCreateSample(&pSample);
}
if (SUCCEEDED(hr))
{
hr = pSample->AddBuffer(pBuffer);
} // Time stamp the sample.
if (SUCCEEDED(hr))
{
//LONGLONG hnsStart = m_CurrentIndex * 1000000; // 1s hr = pSample->SetSampleTime(m_SampleTimespan);
m_SampleTimespan+=m_pImage->GetFrameDelay()*;
} // Deliver the payload to the stream.
if (SUCCEEDED(hr))
{
/*hr = m_pEventQueue->QueueEventParamUnk(
MEMediaSample, GUID_NULL, S_OK, pSample);*/
hr = m_streams[]->DeliverPayload(pSample);
} // If the open operation is still pending, check if we're done.
if (SUCCEEDED(hr))
{
if (m_state == STATE_OPENING)
{
hr = InitPresentationDescriptor();
goto done;
}
}
m_CurrentIndex++; if(m_CurrentIndex>=m_pImage->GetFrameNumbers())
{
hr = m_pEventQueue->QueueEventParamVar(
MEEndOfStream, GUID_NULL, S_OK, NULL); if (FAILED(hr))
{
goto done;
}
hr =QueueAsyncOperation(SourceOp::OP_END_OF_STREAM);
if (FAILED(hr))
{
goto done;
}
}else
{
if (SUCCEEDED(hr))
{
if (StreamsNeedData())
{
hr = RequestSample();
}
}
}
done:
SafeRelease(&pBuffer);
SafeRelease(&pSample);
return hr;
}

DeliverPayload

这个函数把图片里面的数据转换成sample,再加上时间传回CGIFStream,最后发送hr = m_pEventQueue->QueueEventParamUnk(MEMediaSample, GUID_NULL, S_OK, pSample);给MF,这样帧的数据就输出了。

写好了之后,还要把这个source引入到项目中,有一点要注意的是,要在Package.appxmanifast,里面添加额外的信息。右键单击这个文件->查看代码,在Package下面添加节点

<Extensions>
<Extension Category="windows.activatableClass.inProcessServer">
<InProcessServer>
<Path>GIFSource.dll</Path>
<ActivatableClass ActivatableClassId="GIFSource.GIFByteStreamHandler" ThreadingModel="both" />
</InProcessServer>
</Extension>
</Extensions>

manifest

然后在MediaExtensionManager中添加ByteStreamHandler

_extensionManager.RegisterByteStreamHandler("GIFSource.GIFByteStreamHandler", ".gif", "image/gif");

Windows Store GIF player 诞生记的更多相关文章

  1. Unity3D开发Windows Store应用程序 注意事项

    原地址:http://blog.csdn.net/jbjwpzyl3611421/article/details/12704491 针对最近在移植window store项目中遇到的问题,我整理了官方 ...

  2. chain33 区块链开发框架诞生记

    chain33 诞生记 很多年没有写博客了,应该说,自从2013年开始玩比特币,就没有写过了.这5年来,做了很多事情,也见了很多以前做梦都没有想到过都事情.我做的最开心的事情,也是觉得最有意义的事情, ...

  3. 谷歌Gmail诞生记:十年回首

    美国<时代>周刊网络版今天刊登题为<Gmail诞生记:10年前鲜为人知的故事>(How Gmail Happened: The Inside Story of Its Laun ...

  4. Unity for Windows: II – Publishing Unity games to Windows Store

    原地址:http://digitalerr0r.wordpress.com/2013/08/27/unity-for-windows-ii-publishing-to-windows-8/ Windo ...

  5. Windows store app[Part 3]:认识WinRT的异步机制

    WinRT异步机制的诞生背景 当编写一个触控应用程序时,执行一个耗时函数,并通知UI更新,我们希望所有的交互过程都可以做出快速的反应.流畅的操作感变的十分重要. 在连接外部程序接口获取数据,操作本地数 ...

  6. 在桌面程序上和Metro/Modern/Windows store app的交互(相互打开,配置读取)

    这个标题真是取得我都觉得蛋疼..微软改名狂魔搞得我都不知道要叫哪个好.. 这边记录一下自己的桌面程序跟windows store app交互的过程. 由于某些原因,微软的商店应用的安全沙箱导致很多事情 ...

  7. kiosk-mode,免密码登陆, sideload Windows Store apps 等

    MVVM带来的性能问题及其解决方案  MVVM 和语言性能提示:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/mt628050. ...

  8. SQLite in Windows Store Apps

    Using SQLite in Windows Store Apps : https://channel9.msdn.com/Shows/Visual-Studio-Toolbox/Using-SQL ...

  9. 【Win10 UWP】URI Scheme(一):Windows Store协议的解析和使用

    协议是Windows Phone和Windows Store应用的一个重要特点,可以做到在不同应用之间进行互相呼起调用.小小协议,学问大着呢.我打算写几篇关于协议在UWP中使用的文章. 这一讲的主要对 ...

随机推荐

  1. [CodeForces-178F]Representative Sampling

    题目大意: 给你n个字符串,要求从中选出k个字符串,使得字符串两两lcp之和最大. 思路: 动态规划. 首先将所有的字符串排序,求出相邻两个字符串的lcp长度(很显然,对于某一个字符串,和它lcp最长 ...

  2. Ubuntu 无线驱动问题

    最近一次更新系统后开机发现电脑只能连有线网络连不上无线网络.我平时都是连无线网络,没有买网线.所以查了下无线驱动相关问题资料,发现是由于更新系统造成无线网卡驱动莫名不见了.刚开始还以为是电脑将无线禁用 ...

  3. java中的多线程 // 基础

    java 中的多线程 简介 进程 : 指正在运行的程序,并具有一定的独立能力,即 当硬盘中的程序进入到内存中运行时,就变成了一个进程 线程 : 是进程中的一个执行单元,负责当前程序的执行.线程就是CP ...

  4. [转]软件版本号扫盲——Beta RC Preview release等

      1.软件版本阶段说明 *Alpha版:此版本表示该软件在此阶段主要是以实现软件功能为主,通常只在软件开发者内部交流,一般而言,该版本软件的Bug较多,需要继续修改. *Beta版:该版本相对于α版 ...

  5. Git配置非22端口,解决:ssh: connect to host xxx port 22: Connection timed out fatal: The remote end hung up unexpectedly

    背景:私自搭建了Git服务器,而Git本身就是SSH进行连接的,而Git命令上默认只能通过22端口实现. 解决方法: 第一种: 在系统的用户目录下的文件夹:.ssh 如果该路径下没有config文件, ...

  6. JAVA如何把一个float四舍五入到小数点后2位,4位,或者其它指定位数.

    怎么使float保留两位小数或多位小数 http://meryvn.blog.163.com/blog/static/36962664201173010402629/ 两种方法: import   j ...

  7. [c#基础]使用抽象工厂实现三层

    引言 昨天加了一天班,今天闲来无事,就在想如何将之前的三层和最近一直在学的设计模式给联系在一起,然后就动手弄了个下面的小demo. 项目结构 项目各个层实现 Wolfy.Model层中有一个抽象类Ba ...

  8. 咏南MORMOT中间件免费开源

    咏南MORMOT中间件免费开源 源码放QQ群(92449782)共享文件里面

  9. 如何使用花生壳 发布WCF服务 进行外网访问 z

    http://www.cnblogs.com/wanglg/p/5375230.html 当我们发布WCF服务的时候,可以直接通过服务器的域名或者IP进行. 但是如果仅仅是通过花生壳进行域名解析,需要 ...

  10. 第一章 Actionscript学习基本知识笔记及flashdevelop软件的安装问题

    OOP:封装.继承.多态. Pubilc :完全公开. Internal:包内类成员可以互相访问. Private:仅当前类可以访问. Protected:当前类和当前类的子类可以访问. 被关键词fi ...