使用DirectDraw直接顯示YUV視頻數據
最近在編寫一個進行視頻播放的ActiveX控件,工作已經接近尾聲,現將其中顯示YUV數據的使用DirectDraw的一些經驗總結如下:(解碼部分不是我編寫的,我負責從網絡接收數據,將數據傳給解碼器,並將解碼得到的YUV數據進行顯示,最初在顯示部分我是先將YUV數據轉換為RGB數據,再以位圖的形式顯示到屏幕上,但發現CPU占用率比較高,後來改用DirectDraw直接顯示YUV數據)
1.在DirectDraw中創建YUV表麵
與一般表麵不同的是,創建YUV表麵時需要指定象素格式,並指定YUV數據的FourCC碼,關於FourCC碼可以參考微軟MSDN站點上的說明,下麵是具體的創建方法:(以YUV4:2:0格式為例,其中drawwidth和drawheight是欲顯示圖像的寬度和高度,以象素為單位)
LPDIRECTDRAW7 lpDD; // DirectDraw 對象指針
LPDIRECTDRAWSURFACE7 lpDDSPrimary; // DirectDraw 主表麵指針
LPDIRECTDRAWSURFACE7 lpDDSOffScr; // DirectDraw 離屏表麵指針
DDSURFACEDESC2 ddsd; // DirectDraw 表麵描述
// 創建DirectCraw對象
if (DirectDrawCreateEx(NULL, (VOID**)&lpDD, IID_IDirectDraw7, NULL) != DD_OK)
{
//MessageBox("Error Create DDraw.");
return FALSE;
}
// 設置協作層
if (lpDD->SetCooperativeLevel(hWnd,
DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES) != DD_OK)
{
//MessageBox("Error Create Level.", s);
return FALSE;
}
// 創建主表麵
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
if (lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL) != DD_OK)
{
//MessageBox("Error Create Primary Surface.", s);
return FALSE;
}
LPDIRECTDRAWCLIPPER pcClipper; // Cliper
if( lpDD->CreateClipper( 0, &pcClipper, NULL ) != DD_OK )
return FALSE;
if( pcClipper->SetHWnd( 0, hWnd ) != DD_OK )
{
pcClipper->Release();
return FALSE;
}
if( lpDDSPrimary->SetClipper( pcClipper ) != DD_OK )
{
pcClipper->Release();
return FALSE;
}
// Done with clipper
pcClipper->Release();
// 創建YUV表麵
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
ddsd.dwWidth = drawwidth;
ddsd.dwHeight = drawheight;
ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
ddsd.ddpfPixelFormat.dwFlags = DDPF_FOURCC | DDPF_YUV ;
ddsd.ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','V', '1', '2');
ddsd.ddpfPixelFormat.dwYUVBitCount = 8;
if (lpDD->CreateSurface(&ddsd, &lpDDSOffScr, NULL) != DD_OK)
{
//MessageBox("Error Create Off Surface.", s);
return FALSE;
}
2.將解碼得到的YUV數據拷貝到YUV表麵
設解碼得到的YUV數據的指針分別是Y,U,V, 每行數據長度為BPS,具體拷貝代碼如下,這裏需要特別注意每拷一行都要對寫指針加ddsd.lPitch(對於Y)或ddsd.lPitch/2(對於UV):
LPBYTE lpSurf = (LPBYTE)ddsd.lpSurface;
LPBYTE PtrY = Y;
LPBYTE PtrU = U;
LPBYTE PtrV = V;
do {
ddRval = lpDDSOffScr->Lock(NULL,&ddsd,DDLOCK_WAIT | DDLOCK_WRITEONLY,NULL);
} while(ddRval == DDERR_WASSTILLDRAWING);
if(ddRval != DD_OK)
return 1;
// 填充離屏表麵
if(lpSurf)
{
for (int i=0;iHeight;i++)
{
memcpy(lpSurf, PtrY, ddsd.dwWidth);
PtrY += BpS;
lpSurf += ddsd.lPitch;
}
for (int i=0;iHeight/2;i++)
{
memcpy(lpSurf, PtrV, ddsd.dwWidth/2);
PtrV += BpS;
lpSurf += ddsd.lPitch/2;
}
for (int i=0;iHeight/2;i++)
{
memcpy(lpSurf, PtrU, ddsd.dwWidth/2);
PtrU += BpS;
lpSurf += ddsd.lPitch/2;
}
}
lpDDSOffScr->Unlock(NULL);
3.YUV表麵的顯示
現在就可以直接將YUV表麵Blt到主表麵或後備表麵進行顯示了:(設lpDDSBack為後備表麵)
ddRval = lpDDSBack->Blt(NULL, lpDDSOffScr, NULL, DDBLT_WAIT, NULL);
這樣就實現了YUV數據的顯示,對比發現使用DirectDraw直接進行YUV數據顯示,CPU占用率降低了一半。
最後更新:2017-04-03 16:48:57