FFmpeg DXVA2解码得到的数据使用surface来承载的,surface限制很多,如果能用纹理来渲染的话,那我们就可以充分开发D3D,比如可以用坐标变换来实现电子放大的功能,还可以用坐标变换来实现视频图像任意角度的旋转等功能。而对于我来说,最重要的是纹理渲染可以使得解码后的数据能够用像素着色器来做简单的视频图像处理,如果是用的是D3D11,对于更为复杂的视频图像处理算法也是有望可以用Compute Shader实现,以便充分利用显卡来加速和释放CPU。
DXVA2解码数据用纹理渲染的方法其实就是D3D中的渲染到纹理,只是有几个参数需要注意一下。
1.纹理设置
纹理设置与常规的纹理使用流程一样。
static bool setup_texture(IDirect3DDevice9* Device, int Width, int Height,D3DFORMAT format) { if (!Device) { return false ; } HRESULT hr = 0; hr = Device->CreateVertexBuffer( 4 * sizeof(Dxva2TexVertex), D3DUSAGE_WRITEONLY, Dxva2TexVertex::FVF, D3DPOOL_MANAGED, &QuadVB, 0); Dxva2TexVertex* v = 0; QuadVB->Lock(0, 0, (void**)&v, 0); v[0] = Dxva2TexVertex(-20.0f, 20.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f); v[1] = Dxva2TexVertex( 20.0f, 20.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); v[2] = Dxva2TexVertex( 20.0f, -20.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f); v[3] = Dxva2TexVertex(-20.0f, -20.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f); QuadVB->Unlock(); D3DXMATRIX P; D3DXMatrixPerspectiveFovLH(&P, D3DX_PI * 0.5f, 1.0f, 1.0f, //近裁减面到坐标原点的距离 1000.0f //远裁减面到原点的距离 ); Device->SetTransform(D3DTS_PROJECTION, &P); Device->SetRenderState(D3DRS_LIGHTING, false); D3DXVECTOR3 position( 0.0f, 0.0f, -20.0f); D3DXVECTOR3 target(0.0f, 0.0f, 0.0f); D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX V; D3DXMatrixLookAtLH(&V, &position, &target, &up);//计算取景变换矩阵 Device->SetTransform(D3DTS_VIEW, &V);//取景变换 hr = Device->CreateTexture ( Width, Height, 1, D3DUSAGE_RENDERTARGET, format, D3DPOOL_DEFAULT, &g_SurfaceTexture, NULL ) ; if (FAILED(hr)) return false; g_SurfaceTexture->GetSurfaceLevel(0, &g_OffScreenSurface); return true; }
其中需要注意其中的以下代码:
hr = Device->CreateTexture ( Width, Height, 1, D3DUSAGE_RENDERTARGET, format, D3DPOOL_DEFAULT, &g_SurfaceTexture, NULL ) ; if (FAILED(hr)) return false; g_SurfaceTexture->GetSurfaceLevel(0, &g_OffScreenSurface);
CreateTexture的第四个参数注意设置为D3DUSAGE_RENDERTARGET,第五个参数format与设置D3D时的参数中的 d3dpp.BackBufferFormat = d3ddm.Format; 保持一致,详见工程源码。GetSurfaceLevel能够拿到具体某个level的mipmap的surface,我获取的是g_SurfaceTexture在level为0的surface,即g_OffScreenSurface。
2.渲染到纹理
渲染过程是先把DXVA2解码的数据先渲染到纹理,然后通过纹理来显示数据的。
static int dxva2_retrieve_data(AVCodecContext *s, AVFrame *frame) { LPDIRECT3DSURFACE9 surface = (LPDIRECT3DSURFACE9)frame->data[3]; InputStream *ist = (InputStream *)s->opaque; DXVA2Context *ctx = (DXVA2Context *)ist->hwaccel_ctx; HRESULT hr ; int ret = 0 ; EnterCriticalSection(&cs); if (ctx->d3d9device && g_OffScreenSurface) { ctx->d3d9device->SetRenderTarget(0, g_OffScreenSurface); ctx->d3d9device->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(200, 200, 200), 1.0f, 0); ctx->d3d9device->BeginScene(); ctx->d3d9device->SetTexture(0, NULL); GetClientRect(d3dpp.hDeviceWindow, &m_rtViewport); ctx->d3d9device->StretchRect(surface, NULL, g_OffScreenSurface, NULL, D3DTEXF_LINEAR); ctx->d3d9device->EndScene(); ctx->d3d9device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &m_pBackBuffer); ctx->d3d9device->SetRenderTarget(0, m_pBackBuffer); ctx->d3d9device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); ctx->d3d9device->BeginScene(); ctx->d3d9device->SetTexture(0, g_SurfaceTexture); ctx->d3d9device->SetFVF(Dxva2TexVertex::FVF); ctx->d3d9device->SetStreamSource(0, QuadVB, 0, sizeof(Dxva2TexVertex)); ctx->d3d9device->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2); ctx->d3d9device->EndScene(); hr = ctx->d3d9device->Present(NULL, NULL, NULL, NULL); if (FAILED(hr)) { if (ctx->d3d9device->TestCooperativeLevel() == D3DERR_DEVICENOTRESET) { printf("Failed to Present !") ; ret = -1 ; } } else { ret = 0 ; } } LeaveCriticalSection(&cs); return ret; }
可以看到代码中有两组
ctx->d3d9device->BeginScene();
····
ctx->d3d9device->EndScene();
第一组通过 ctx->d3d9device->SetRenderTarget(0, g_OffScreenSurface); 把渲染目标设置为纹理的surface,把DXVA2解码得到的数据渲染到前面准备好的纹理的surface中;第二组则把渲染目标设为后台缓存,直接把纹理渲染出来即可。做这一层折腾的原因在于StretchRect函数的一个限制,
大意就是如果源或者目的surface是个纹理surface,就需要查看驱动是否支持,而
详见https://msdn.microsoft.com/en-us/library/windows/desktop/bb174471(v=vs.85).aspx
所以我在前面强调创建纹理的第四个参数注意设置为D3DUSAGE_RENDERTARGET。起初我也是因为这个参数设错了,一直没法成功,后来突然想到RT texture可能是指设为D3DUSAGE_RENDERTARGET的texture,RT可能是RENDERTARGET的缩写,然后才成功的。Off-screen plain指离屏表面,我对D3D的一些概念不是特别清楚,不知道承载DXVA2解码数据的surface是不是离屏表面,但我试了许多方法,只有这样才最后成功。如果这一块的理解有问题,欢迎拍砖指教。
对于FFmpeg DXVA2硬解有疑问的,可以参考http://www.cnblogs.com/betterwgo/p/6125507.html。对于D3D有疑问的,请自行上网查询,我懂的也不多,可以相互交流一下。
完整工程源码:http://download.csdn.net/download/qq_33892166/9742467
运行工程的时候注意修改代码中视频文件的路径。