ddraw 视频下绘图 不闪烁的方法
我们假设是在在RGB视频上绘图(直线,矩形等),一般採用双缓冲区继续,使用内存MemoryDC,来实现画的图形在视频上显示不闪烁的功能,可是我们知道用RGB显示视频都是使用GDI进行渲染,这样非常耗CPU,那么我们能不能在YUV上进行视频渲染呢,答案是肯定的,使用ddraw直接显示yuv就ok了,能够支持yuv422和yuv420的直接使用显卡显示,不耗CPU,可是我们在使用ddraw显示时,然后在配合GDI绘图(直线或矩形等),画的图形是闪烁的,原因是我们在ddraw直接显示yuv视频时,使用的是离屏表面的方法,将yuv数据复制到离屏表面,然后在blt到主表面,这样用gdi绘图时,和视频刷新不同步,造成闪烁,那么我们怎么解决该问题呢?方法例如以下:
新添加一个离屏表面,我们定义成osd离屏表面吧,我们将yuv数据复制到离屏表面后,在将该离屏表面blt到osd离屏表面,然后在osd离屏表面上画直线或矩形,画完后在blt到主表面,这样画的图形就不会闪烁了。
直接上源代码吧,注意,我没有对绘图的部分进行封装,感兴趣的朋友能够自己封装;
#ifndef _DIRECTDRAW_H_ #define _DIRECTDRAW_H_ #pragma once #include "ddraw.h" #define FOURCC_YUYV 0x32595559 // MAKEFOURCC( 'Y ', 'U ', 'Y ', '2 ') #define FOURCC_UYVY 0x59565955 // MAKEFOURCC( 'U ', 'Y ', 'V ', 'Y ') #define YUV_UYVY 1 #define YUV_YUYV 2 class __declspec(dllexport) CDirectDraw { public: CDirectDraw(void); ~CDirectDraw(void); bool DirectDrawInit(HWND hWnd, int width, int height,DWORD dwYuvFourCC); bool DisPlayYUVData(byte *pYUVData,int bYuvType,RECT rect);
void DirectDrawDeInit(void);
protected: LPDIRECTDRAW7 lpDD; // DirectDraw 对象指针 LPDIRECTDRAWSURFACE7 lpDDSPrimary; // DirectDraw 主表面指针 LPDIRECTDRAWSURFACE7 lpDDSOffScr; // DirectDraw 离屏表面指针 DDSURFACEDESC2 ddsd; // DirectDraw 表面描写叙述 LPDIRECTDRAWSURFACE7 m_pOsdSurface; //绘图表面 }; #endif
DirectDraw.cpp 源文件例如以下:
#include "StdAfx.h" #include "DirectDraw.h" #include <string> using namespace std; CDirectDraw::CDirectDraw(void) { lpDD=NULL; // DirectDraw 对象指针 lpDDSPrimary=NULL; // DirectDraw 主表面指针 lpDDSOffScr=NULL; // DirectDraw 离屏表面指针 m_pOsdSurface=NULL; } CDirectDraw::~CDirectDraw(void) { DirectDrawDeInit(); } //yuv_type控件不同的yuv格式
bool CDirectDraw::DirectDrawInit(HWND hWnd, int width, int height,DWORD dwYuvFourCC) { HRESULT hr; // 创建DirectCraw对象 if (DirectDrawCreateEx(NULL, (VOID**)&lpDD, IID_IDirectDraw7, NULL) != DD_OK) { return false; } // 设置协作层 if (lpDD->SetCooperativeLevel(hWnd, DDSCL_NORMAL | DDSCL_NOWINDOWCHANGES) != DD_OK) { return false; } // 创建主表面 LPVOID lpSurface = NULL; ZeroMemory(&ddsd, sizeof(ddsd)); ZeroMemory(&ddsd.ddpfPixelFormat, sizeof(DDPIXELFORMAT)); ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; if (lpDD->CreateSurface(&ddsd, &lpDDSPrimary, NULL) != DD_OK) { 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表面 ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY ; ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT ; ddsd.dwWidth = width; ddsd.dwHeight =height; ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); ddsd.ddpfPixelFormat.dwFlags = DDPF_FOURCC | DDPF_YUV ; ddsd.ddpfPixelFormat.dwYUVBitCount = 8; ddsd.ddpfPixelFormat.dwFourCC =dwYuvFourCC; hr=lpDD->CreateSurface(&ddsd, &lpDDSOffScr, NULL); if ( hr!= DD_OK) { return false; } #if 1 //创建OSD绘图离屏表面 // ZeroMemory(&ddsd, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH; ddsd.dwWidth = width; ddsd.dwHeight = height; hr = lpDD->CreateSurface(&ddsd, &m_pOsdSurface, NULL); if ( hr != DD_OK) { //lpDD->Release(); //lpDDSPrimary = NULL; //lpDD = NULL; return false; } #endif return true; } //add rect參数,将图像缩放到rect内 bool CDirectDraw::DisPlayYUVData(byte *pYUVdata,int bYuvType,RECT rect) { byte *pSurf; int yuv_type=bYuvType; HRESULT hr; hr=lpDDSOffScr->Lock(NULL,&ddsd,DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT,NULL); //2012-02-24 if (hr==DDERR_SURFACELOST) { TRACE("off surface lost,restore offscr\n"); hr=lpDDSOffScr->Restore(); hr=lpDDSOffScr->Lock(NULL,&ddsd,DDLOCK_SURFACEMEMORYPTR|DDLOCK_WAIT,NULL); } if (FAILED(hr)) { return DD_FALSE; } //2012-02-11 if (yuv_type==YUV_UYVY) { ddsd.ddpfPixelFormat.dwFourCC =FOURCC_UYVY; }else { ddsd.ddpfPixelFormat.dwFourCC =FOURCC_YUYV; } pSurf=(LPBYTE)ddsd.lpSurface; if (pSurf) { for(unsigned int i=0; i < ddsd.dwHeight; i++) { memcpy(pSurf,pYUVdata,ddsd.dwWidth*2); pYUVdata+=ddsd.dwWidth*2; pSurf+=ddsd.lPitch; } } lpDDSOffScr->Unlock(NULL); #if 1 //增加Osd离屏表面内容 HRESULT ddrval; ddrval = m_pOsdSurface->Blt(&rect, lpDDSOffScr, NULL, DDBLT_WAIT, NULL); if (ddrval != DD_OK) { ddrval = lpDDSPrimary->Blt(&rect, lpDDSOffScr, &rect, DDBLT_WAIT, NULL); } else { HDC hDC = NULL; ddrval = m_pOsdSurface->GetDC(&hDC); if ((ddrval == DD_OK)&&(hDC != NULL)) { //叠加文字 SetTextColor(hDC,RGB(255,0,0)); SetBkColor(hDC,RGB(0,255,0)); CString m_sOsdMsg=_T("hello world"); TextOut(hDC, rect.left+100,rect.top+200 , m_sOsdMsg, m_sOsdMsg.GetLength()); //画实心矩形 HPEN hpen = CreatePen (PS_SOLID, 1, RGB(255, 0, 0)); SelectObject (hDC, hpen); HBRUSH hbrush = CreateSolidBrush (RGB(0, 255, 0)); //创建刷子 SelectObject (hDC, hbrush); //使用刷子 Rectangle(hDC, rect.left+100, rect.top+100, rect.left+200, rect.top+200); //画矩形 //画空心矩形 RECT rect1; rect1.left=rect.left+200; rect1.top=rect.top+200; rect1.right=rect.left+300; rect1.bottom=rect.top+300; FrameRect(hDC,&rect1,CreateSolidBrush(RGB(255,0,0))); //画直线 MoveToEx(hDC,rect.left+50,rect.top+50,NULL); LineTo(hDC,rect.left+350,rect.top+350); m_pOsdSurface->ReleaseDC(hDC); lpDDSPrimary->Blt(&rect, m_pOsdSurface, &rect, DDBLT_WAIT, NULL); } } #else //仅仅有主表面和离屏表面 HRESULT ddrval; ddrval=lpDDSPrimary->Blt(&rect, lpDDSOffScr, NULL, DDBLT_WAIT, NULL); if (ddrval==DDERR_SURFACELOST) { TRACE("primary surface lost,restore all surfaces\n"); lpDDSPrimary->Restore(); } #endif return DD_OK; } //ddraw deInit void CDirectDraw::DirectDrawDeInit(void) { if (lpDDSOffScr != NULL) { lpDDSOffScr->Release(); lpDDSOffScr = NULL; } if (lpDDSPrimary != NULL) { lpDDSPrimary->Release(); lpDDSPrimary = NULL; } if (lpDD != NULL) { lpDD->Release(); lpDD = NULL; } if (m_pOsdSurface!=NULL) { m_pOsdSurface->Release(); m_pOsdSurface=NULL; } }
測试结果例如以下:
ddraw gdi 绘图知识例如以下:
因为DirectDraw并没有提供画点、线,圆等的语句,所以我们要借助Windows GDI函数来完毕这些工作。就像输出文字时一样,我们先要获得页面的HDC:
HDC hdc;
lpDDSXXX->GetDC(&hdc);
画点是最简单的,SetPixel (hdc, x, y, RGB(r, g, b));就可以在屏幕的(x,y)坐标处画上一个指定颜色的点。
假设须要画线等,我们须要创建"画笔":
HPEN hpen = CreatePen (PS_SOLID, 5, RGB(r, g, b));
CreatePen的第一个參数意义为画笔样式,经常使用的有PS_SOLID(普通画笔)和PS_DOT(由间断点组成的画笔,须要设置画笔宽度为1)。第二个參数是画笔的宽度,第三个參数是画笔的颜色。
接着将画笔给HDC:
SelectObject (hdc, hpen);
移动画笔到(x1,y1):
MoveToEx (hdc, x1, y1, NULL);
从绘图起始位置向(x2,y2)坐标处画线:
LineTo (hdc, x2, y2);
以下列出一些经常使用的绘图语句,用法和画线差点儿相同,设定完画笔就可以使用:
Rectangle(hdc, x1, y1, x2, y2); //画矩形
Ellipse(hdc, x1, y1, x2, y2); //画椭圆
值得注意的是我们画的图形将由一个"刷子"来填充,使用最简单的单色刷子的方法是:
HBRUSH hbrush = CreateSolidBrush (RGB(r, g, b));//创建刷子
SelectObject (hdc, hbrush); //使用刷子
画完后,我们要记住释放HDC:
lpDDSXXX->ReleaseDC(hdc);