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;i
{
memcpy(lpSurf, PtrY, ddsd.dwWidth);
PtrY += BpS;
lpSurf += ddsd.lPitch;
}
for (int i=0;i
{
memcpy(lpSurf, PtrV, ddsd.dwWidth/2);
PtrV += BpS;
lpSurf += ddsd.lPitch/2;
}
for (int i=0;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占用率降低了一半。
首先要感谢ffmpeg, 如果没有它,所有做电脑视频----包括PC, 嵌入式, DV/DC,DVD机等公司(也包括我们公司), 一大半得关门。没有它,一些中小公司将无法研发这些编解码器, 没有了这些技术基础,产品将无从谈起;没有它, PC上常见的Mplayer, KMPlayer, 暴风影音等都不会存在!所以,在很多情况下,做视频软件,ffmpeg是软件的底层库,是基础平台。
其次要感谢显卡的超强功能。显卡越来越强大,有些显卡的GPU能力甚至要超过了PC上CPU的计算能力-----是不是主次颠倒了?正是有了这些显卡,逼真高效的游戏才成为了可能。
现在我要说的,就是ffmpeg的解码后,多路高效显示的一点点小技巧。
微软推出DirectShow时,可以说是天生为流媒体开发而制作的, 因为它提供的功能太强大了:多种格式视频显示、视频音频同步、视频合成、视频分离等等令人激动的功能。如果是单路或几路视频显示,当然用 DirectShow是最好的选择,但是,如果要显示的视频路非常多,例如25路,使用它就会发现资源占用率极高,一路视频显示,不算解码,要4个线程! 并且图像合成时CPU占有率极高----总而言之,DirectShow不适合多路视频的专业监控。
DirectDraw是我发现的在Windows平台下最佳的解决方案,唯一的缺点就是,你需要做一些视频图像的处理,这需要更强的专业知识和更多的开发时间。事实上,从某种程度上来说,你就是在开发一款mini型的DirectShow COM.
不必多说,转入正文。
ffmpeg解码出来后,一般会生成YV12格式,在2005年以后出产的显卡,它可以直接放到显存中直接显示的---当然,这并不 是绝对的,有些显卡,例如明基一些笔记本就不支持YV12。这种做法显然是最高效的,中间没有转换格式,数据量也是最小的。可是,有时我们需要对视频做一 些特殊处理,例如,在视频上放一些文字,显示一些时间等,这种情况下,因为在DirectX提供的YUV表面上是无法得到HDC句柄的,如果直接操作 YUV数据, 那非常的麻烦--你自已要完成提供画线,字体合成之类工作,也就是说,你不能使用Win32 API, 要自已写类似的API Function. 实际上有个很简单的办法,那就是利用显卡自已的格式转换功能!
显卡一般支持YUV格式直接转到RGB24/RGB32。至于显卡支持具体的格式,请用DirectX Caps来查询就知道了。要实现上述功能,其实是很简单,创建主表面-->创建RGB从表面---->创建YV12从表面,然后将YV12数 据复制到 YV12表面, Blt到RGB表面(在这一步中显卡自动完成YV12到RGB的转换), 然后取RGB表面的HDC, 就可以利用TextOut, FillRect, Line之类的Win32 API来绘图写字了,最后,将RGB表面Blt到主表面,这个过程就算是结束了。
需要说明的是,这个过程只用到显卡的运算能力,没有用到CPU,所以CPU占有率不会提高,但对于显卡来说,要占用显卡的GPU和显存的带宽。显卡的性能就显得比较重要了。
以下是实现代码:(由于商业原因,一些细节代码被取消了,但整体技术实现流程是完整的)
class CVideoDraw
{
LPDIRECTDRAW7 m_lpDD; // DirectDraw 对象指针
LPDIRECTDRAWSURFACE7 m_lpDDSPrimary; // DirectDraw 主表面指针
LPDIRECTDRAWSURFACE7 m_lpDDSOffScrYUV; // DirectDraw 离屏表面指针
LPDIRECTDRAWSURFACE7 m_lpDDSOffScrRGB;
DDSURFACEDESC2 m_ddsd; // DirectDraw 表面描述
RECT m_rcDraw;
HWND m_hWnd;
bool InitDirectX(HWND hWnd, int nWidth, int nHeight);
void ClearDirectX();
bool Draw(LPBYTE pBuffer, int nWidth, int nHeight, int nSec);
};
bool CVideoDraw::InitDirectX(HWND hWnd, int nWidth, int nHeight) {
if (DirectDrawCreateEx(NULL, (LPVOID*)&m_lpDD, IID_IDirectDraw7, NULL) != DD_OK)
return false;
if (m_lpDD->SetCooperativeLevel(hWnd, DDSCL_NORMAL) != DD_OK){
ClearDirectX();
return false;
}
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.dwFlags = DDSD_CAPS;
m_ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
if (m_lpDD->CreateSurface(&m_ddsd, &m_lpDDSPrimary, NULL) != DD_OK){
ClearDirectX();
return false;
}
LPDIRECTDRAWCLIPPER pcClipper;
if( m_lpDD->CreateClipper(0, &pcClipper, NULL) != DD_OK) {
ClearDirectX();
return false;
}
if( pcClipper->SetHWnd(0, m_hWnd) != DD_OK) {
ClearDirectX();
return false;
}
if( m_lpDDSPrimary->SetClipper(pcClipper) != DD_OK) {
ClearDirectX();
return false;
}
pcClipper->Release();
// 创建离屏表面对象
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.ddsCaps.dwCaps
= DDSCAPS_OFFSCREENPLAIN|DDSCAPS_VIDEOMEMORY ; // DDSCAPS_VIDEOMEMORY;
//DDSCAPS_OVERLAY DDSCAPS_OFFSCREENPLAIN;
m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
m_ddsd.dwWidth = nWidth;
m_ddsd.dwHeight = nHeight;
m_ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
m_ddsd.ddpfPixelFormat.dwFlags = DDPF_FOURCC | DDPF_YUV ;
m_ddsd.ddpfPixelFormat.dwFourCC = MAKEFOURCC('Y','V','1','2');
m_ddsd.ddpfPixelFormat.dwYUVBitCount = 8;
if (m_lpDD->CreateSurface(&m_ddsd, &m_lpDDSOffScrYUV, NULL) != DD_OK) {
ClearDirectX();
return false;
}
ZeroMemory(&m_ddsd, sizeof(m_ddsd));
m_ddsd.dwSize = sizeof(m_ddsd);
m_ddsd.ddsCaps.dwCaps
= DDSCAPS_OFFSCREENPLAIN|DDSCAPS_VIDEOMEMORY; // DDSCAPS_VIDEOMEMORY;
//DDSCAPS_OVERLAY DDSCAPS_OFFSCREENPLAIN;
m_ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
m_ddsd.dwWidth = nWidth;
m_ddsd.dwHeight = nHeight;
if (m_lpDD->CreateSurface(&m_ddsd, &m_lpDDSOffScrRGB, NULL) != DD_OK) {
ClearDirectX();
return false;
}
return true;
}
void CVideoDraw::ClearDirectX() {
if( m_lpDD != NULL ){
SAFE_RELEASES(m_lpDDSOffScrRGB);
SAFE_RELEASES(m_lpDDSOffScrYUV);
SAFE_RELEASES(m_lpDDSPrimary);
SAFE_RELEASES(m_lpDD);
}
}
bool CVideoDraw::Draw(LPBYTE pBuffer, int nWidth, int nHeight, int nSec) {
HRESULT ddRval;
RECT rctDest; // 目标区域
RECT rctSour; // 源区域
for(int nTry=0; nTry < 5; nTry++){
ddRval = m_lpDDSOffScrYUV->Lock(NULL,&m_ddsd, DDLOCK_WAIT|DDLOCK_WRITEONLY, NULL);
if( ddRval == DDERR_SURFACELOST ) {
ddRval = m_lpDDSOffScrYUV->Restore();
}
if( ddRval == DD_OK ){
break;
}
}
if( ddRval != DD_OK ) return false;
int i=0;
LPBYTE lpSurf = (LPBYTE)m_ddsd.lpSurface;
LPBYTE lpY = (LPBYTE)pBuffer;
LPBYTE lpV = (LPBYTE)(pBuffer + nWidth * nHeight);
LPBYTE lpU = (LPBYTE)(pBuffer + nWidth * nHeight * 5 / 4);
int nOffset = 0;
int value1 = 0 ;
int value2 = 0 ;
int value3 = 0 ;
int value4 = 0 ;
lpY += nOffset;
for(i=0; i<m_ddsd.dwHeight; i++)
{
memcpy(lpSurf, lpY, m_ddsd.dwWidth);
lpY += nWidth;
lpSurf += m_ddsd.lPitch;
}
value1 = m_ddsd.dwHeight/2;
value2 = m_ddsd.dwWidth / 2;
value3 = nWidth / 2;
value4 = m_ddsd.lPitch / 2;
for(i=0; i<value1; i++)
{
memcpy(lpSurf, lpU, value2);
lpU += value3;
lpSurf += value4;
}
for(i=0; i<value1; i++)
{
memcpy(lpSurf, lpV, value2);
lpV += value3;
lpSurf += value4;
}
m_lpDDSOffScrYUV->Unlock(NULL);
SetRect(&rctDest, 0,0, nWidth, nHeight);
ddRval = m_lpDDSOffScrRGB->Blt(&rctDest, m_lpDDSOffScrYUV, NULL, DDBLT_WAIT, NULL);
if( ddRval != DD_OK)
return false;
ddRval = m_lpDDSOffScrRGB->Lock(NULL,&m_ddsd, DDLOCK_WAIT|DDLOCK_WRITEONLY, NULL);
if( ddRval == DDERR_SURFACELOST )
ddRval = m_lpDDSOffScrRGB->Restore();
if( ddRval != DD_OK )
return false;
HDC hdc = NULL;
m_lpDDSOffScrRGB->GetDC(&hdc);
if( hdc )
{
TCHAR szText[64];
int thh = nSec/3600;
int tmm = (nSec%3600)/60;
int tss = nSec%60;
wsprintf(szText, _T("%02d:%02d:%02d"), thh, tmm, tss);
SetBkMode(hdc, TRANSPARENT);
::SetTextColor(hdc, RGB(255,0,0));
TextOut(hdc, 1, 1, szText, wcslen(szText));
m_lpDDSOffScrRGB->ReleaseDC(hdc);
}
m_lpDDSOffScrRGB->Unlock(NULL);
// Blt到主表面上
rctSour.left = 0;
rctSour.top = 0;
rctSour.right = m_ddsd.dwWidth;
rctSour.bottom = m_ddsd.dwHeight;
rctDest = m_rcDraw;
::ClientToScreen(m_hWnd, (LPPOINT)&rctDest.left);
::ClientToScreen(m_hWnd, (LPPOINT)&rctDest.right);
ddRval = m_lpDDSPrimary->Blt(&rctDest, m_lpDDSOffScrRGB, NULL, DDBLT_WAIT, NULL);
return (ddRval==DD_OK);
}
发表于 @ 2010年07月05日 10:53:00