D3D下使用VMR9播放视频文件
作者:骡子宝
2005.12.30
随着DX开发包的不断升级,其提供给媒体程序开发人员的接口功能也空前强大起来。本文就D3D下使用VMR9播放视频向初学者谈谈心得。高手请止步(不想让你看了吐血^_^)。
早在DX8时代,SDK中就提供了VMR的功能,但是不幸的是,有不少功能只限于WINXP下使用。现在好了,DX9中VMR升级了,提供了VMR9。VMR全称为Video Mixing Renderer,顾名思义就是可以使用它来进行视频渲染。那么它到底有多强大呢?我可以告诉你,他十分强大,从SDK的SAMPLE中大家就能看到,VMR9可以进行多部视频文件的混合播放,画中画播放,更酷的是只要是能支持DirectX9的系统都能使用它在3维物体的表面上进行视频渲染。那么这到底是什么样的效果呢?我可以设想这样一个效果来告诉你。(注意:这目前只是一个设想,本人还没有完全掌握这项技术)大家都知道“水晶球”有预知未来的能力吧,当法师操纵水晶球时,球体表面就会出现未来世界的一些情景。如果要把这个效果反映的程序里去,我们就可以使用VMR9了。我们可以首先建立一个球体,并进行水晶化效果处理,然后再使用VMR9将一个关于未来的视频片断渲染到球体表面,这样就形成了刚才所设想的效果了。
VMR9可以使用3种工作模式,分别为windowed, windowless, renderless模式。后两种模式多用于游戏中。本文将只谈及windowless模式。下面我们进入正题。
使用过DSHOW的朋友应该会对下面的流程有似曾相识的感觉。
使用VMR9前我们必须先初始化COM库!因为它是一个COM对象。
//关于D3D的设备初始化我就不在此讲述了
CoInitialize(NULL); //初始化COM库
程序中需要如下变量:
IGraphBuilder* g_graph; //FILTER管理器
IBaseFilter* g_filter; //用于创建VMR9
IBaseFilter* g_source; //用于流媒体数据处理
IEnumPins* g_enumpins; //用于获取PIN
IFilterGraph2* g_Graph2; //用于视频流渲染操作
IMediaControl* g_mediaControl; //媒体控制
IVMRFilterConfig9* g_filterConfig; //VMR9配置
IVMRWindowlessControl9* g_VMR; //VMR9的WINDOWED模式控制
HWND hWnd; //程序主窗口句柄
首先我们要创建FilterGraphManager来管理处理视频音频信息的Filter(过滤器)
//创建FilterGraphManager
if(FAILED(CoCreateInstance(CLSID_FilterGraph,NULL,CLSCTX_INPROC_SERVER,IID_IGraphBuilder,(void **)&g_graph)))
{
//错误处理
}
然后我们就要创建VMR9对象并将其功能接口返回给g_filter
//创建FilterGraph(VMR9)
if(FAILED(CoCreateInstance(CLSID_VideoMixingRenderer9,NULL,CLSCTX_INPROC_SERVER,IID_IBaseFilter,(void **)&g_filter)))
{
//ERROR
}
创建完毕后将其加入到FILTER管理器以便使用VMR9的功能处理视频流.
//add the VMR-9 to the filter graph
if(FAILED(g_graph->AddFilter(g_filter, L"VMR9")))
{
//ERROR
}
现在第一步完成。我们接下来该对VMR9进行属性配置,以便我们按照自己的目的使用它。
//创建VMRFilterConfig9
if(FAILED(g_filter->QueryInterface(IID_IVMRFilterConfig9, reinterpret_cast<void**>(&g_filterConfig))))
{
//ERROR;
}
g_filterConfig->SetRenderingMode(VMR9Mode_Windowless); //将其配置为WINDOWLESS模式
现在我们需要VMRWindowlessControl9来对渲染区进行描述
//创建VMRWindowlessControl9
if(FAILED(g_filter->QueryInterface(IID_IVMRWindowlessControl9, reinterpret_cast<void**>(&g_VMR))))
{
//error
}
g_VMR->SetVideoClippingWindow(hWnd); //指定要渲染的窗口为我们的应用程序
//主窗口
//下面的三行描述我们的渲染窗口为整个应用程序客户区(整个配置过程适用于窗口模//式和全屏模式)
RECT *clientRect = new RECT;
::GetClientRect(hWnd, clientRect );
g_VMR->SetVideoPosition( NULL, clientRect );
最后,我们需要加载资源并控制播放
g_graph->AddSourceFilter(“你的资源路径”, L"source", &g_source); //将资源载入
//创建IFilterGrahp2处理媒体流
if(FAILED(g_graph->QueryInterface(IID_IFilterGraph2,reinterpret_cast<void**>(&g_Graph2))))
{
//error
}
//指定RENDER渲染设备对上流设备的数据进行处理
g_Graph2->RenderEx(GetPin(g_source,PINDIR_OUTPUT),AM_RENDEREX_RENDERTOEXISTINGRENDERERS,NULL);
这里对GetPin()方法进行一下说明:
IPin* PowerVideo::GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir)
{
BOOL bFound = FALSE;
IEnumPins *pEnum;
IPin *pPin;
HRESULT hr = pFilter->EnumPins(&pEnum); //搜索上流设备的PIN
if (FAILED(hr))
{
return NULL;
}
while(pEnum->Next(1, &pPin, 0) == S_OK)
{
PIN_DIRECTION PinDirThis;
pPin->QueryDirection(&PinDirThis);
if (bFound = (PinDir == PinDirThis)) //找到上流设备的输出PIN
break;
pPin->Release();
}
pEnum->Release();
if(!bFound)MessageBox(NULL,"Pin连接失败","PowerVideo",MB_OK);
return (bFound ? pPin : NULL);
}//该方法主要就是帮助g_Graph2->RenderEx方法和上流设备的媒体输出PIN进行对连
//以便将上流设备解码后的媒体信息流出到RENDER(应用程序创建的渲染器)
//创建MediaControl控制播放
if(FAILED(g_graph->QueryInterface(IID_IMediaControl,reinterpret_cast<void**>(&g_mediaControl))))
{
//error
}
g_mediaControl->Run(); //播放