先说下VMR9,这个 filter是个视频混合的组件,可以很方便的将多路视频合成一路视频,添加字幕和静态图片,这个组件的内部实现采用了DX9的接口。如果想把VMR9混合输出后的视频图像当作纹理渲染到3D模型上,一个办法就是通过实现一个分配-演示器对象,然后将此对象替换掉VMR9中的默认分配-演示器对象。
所谓分配演示器对象指的是一个实现了VMR9规定的的分配接口和演示接口的对象。也就是此对象实现了以下两个接口:
1) 分配接口(用来完成分配VMR9需要的各种D3D 表面资源 ):virtual HRESULT STDMETHODCALLTYPE InitializeDevice(...) = 0;
virtual HRESULT STDMETHODCALLTYPE TerminateDevice(...) = 0;
virtual HRESULT STDMETHODCALLTYPE GetSurface(...) = 0;
virtual HRESULT STDMETHODCALLTYPE AdviseNotify( ...) = 0;
2 ) 演示接口 (用来将VMR9的输出视频展示出来):
virtual HRESULT STDMETHODCALLTYPE StartPresenting( ...) = 0;
virtual HRESULT STDMETHODCALLTYPE StopPresenting( ...) = 0;
virtual HRESULT STDMETHODCALLTYPE PresentImage( ...) = 0;
下面是主要的步骤:
// 获取VMR9滤波器CoCreateInstance( CLSID_VideoMixingRenderer9, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, ( void** )&m_vmrFilter );
// 生成分配演示器对象
CAllocatorPresent * pAllocPresent = new CAllocatorPresent( m_vmrFilter );// 然后将其替换VMR9的默认分配演示器对象
m_vmrFilter->QueryInterface( IID_IVMRSurfaceAllocatorNotify9, reinterpret_cast< void ** >( &lpIVMRSurfAllocNotify ) );
hr = lpIVMRSurfAllocNotify->AdviseSurfaceAllocator( usrId, m_allocator );
// 通知VMR9我们的DX9设备资源
HMONITOR hMonitor = Graphic::Instance().m_pD3D->GetAdapterMonitor( D3DADAPTER_DEFAULT );
hr = lpIVMRSurfAllocNotify->SetD3DDevice( Graphic::Instance().m_pd3dDevice, hMonitor );
// 添加我们的VMR9滤波器到dshow 图表里去
m_pGraphBuilder->AddFilter( m_vmrFilter, L"Video Mixing Renderer 9" );
// 连接所有滤波器
m_pGraphBuilder->RenderFile( L"C:\\Users\\sky\\Desktop\\big_buck_bunny_1080p.avi", NULL );
在完成了添加VMR9的基本操作之后,我们就可以在我们自定义的分配演示器中的PresentImage接口中获取视频合成的结果了。
HRESULT CAllocatorPresent::PresentImage( /* [in] */ DWORD_PTR dwUserID, /* [in] */ VMR9PresentationInfo * lpPresInfo )
{
// 将视频输出拷贝到D3D的纹理中去
SmartPtr< IDirect3DSurface9 > surface;
Graphic::Instance().m_pMovieTexture->GetSurfaceLevel( 0, &surface );
Graphic::Instance().m_pd3dDevice->StretchRect( lpPresInfo->lpSurf, NULL, surface, NULL, D3DTEXF_LINEAR );
// 绘制3D场景
m_scene.Render();
}
因为用的是DX9,所以需要注意下DX9的设备资源丢失问题。通过将绘制结果显示到屏幕上,可以知道设备是否丢失,vmr9allocato这个例子中对设备丢失的处理是不正确的,可以参考下面的处理方法。
// Flip it
HRESULT hr = device->Present( NULL, NULL, NULL, NULL );
if( D3DERR_DEVICELOST == hr )
{
while ( device->TestCooperativeLevel() != D3DERR_DEVICENOTRESET )
{
Sleep( 100 );
}
CVideoRenderingIn3DDlg::m_filterGraph.OnDeviceLost();
Graphic::Instance().Clean();
Graphic::Instance().OnCreateDevice();
CVideoRenderingIn3DDlg::m_filterGraph.OnDeviceReset();
hr = S_OK;
}
通过上面的方法可以看到利用VMR9渲染视频到D3D的纹理采用的是DSHOW驱动的,D3D是辅助的结合方法。如果将VMR9和D3D独立进行个自的渲染是否可行呢?我进行过一些测试,发现一个问题。就是在D3D独立进行场景渲染时,有时会出现闪屏现象。也就是D3D的一些调用如,BeginScene等会出现失败。这可能和VMR9利用DX9进行视频合成时会占用DX9资源,和D3D进行场景绘制时产生冲突所致。这个问题也许要等VMR的升级来解决。具体代码可以参考Microsoft Windows SDK v7.1 中一个叫做vmr9allocator的例子.