FFmpeg DXVA2解碼得到的數據使用surface來承載的,surface限制很多,如果能用紋理來渲染的話,那我們就可以充分開發D3D,比如可以用坐標變換來實現電子放大的功能,還可以用坐標變換來實現視頻圖像任意角度的旋轉等功能。而對於我來說,最重要的是紋理渲染可以使得解碼後的數據能夠用像素著色... ...
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
運行工程的時候註意修改代碼中視頻文件的路徑。