利用DSHOW中的VMR9 filter 将视频渲染成纹理 供D3D使用

先说下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的例子.


你可能感兴趣的:(GPU加速)