Direct3D显示视频
对于视频的显示已经不推荐使用DirectDraw了,但可以使用Direct3D技术。对此,本人做了一些摸索之后,写了如下一个简单的例子,该例子创建Direct3D对象、Direct3D设备、Direct3D的RGB32纹理,然后装载一个位图并将位图数据更新纹理并显示。我们可以稍作修改,即可实现显示视频的包装类:创建D3DFMT_UYVY或D3DFMT_YUY2纹理,将解码的I420数据转换成对应的UYVY或YUY2格式,然后更新纹理。既然是例子,就要简单,因此有些细节及一些严密的判断没有实现。
#ifndef _BOFUL_DIRECT3D_HELPER_H_
#define _BOFUL_DIRECT3D_HELPER_H_
#include "d3dx9.h"
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "d3dx9.lib")
#pragma pack(push, 1)
typedef struct
{
FLOAT x,y,z; // vertex untransformed position
FLOAT rhw; // eye distance
D3DCOLOR diffuse; // diffuse color
FLOAT tu, tv; // texture relative coordinates
} CUSTOMVERTEX;
#pragma pack(pop, 1)
class CD3DHelper
{
public:
CD3DHelper();
~CD3DHelper();
unsigned long InitDirect3D( HWND hwnd, unsigned long lWidth, unsigned long lHeight );
bool DrawImage(byte *pVideoBuff,int iVideoPitch);
void ReleaseDirect3D();
private:
void Lock();
void UnLock();
private:
CRITICAL_SECTION m_critial;
HWND m_hVideoWnd; // 视频窗口
private:
LPDIRECT3D9 m_pDirect3D9;
LPDIRECT3DDEVICE9 m_pDirect3DDevice;
LPDIRECT3DSURFACE9 m_pDirect3DSurfaceRender;
LPDIRECT3DTEXTURE9 m_pDirect3DTexture;
LPDIRECT3DVERTEXBUFFER9 m_pDirect3DVertexBuffer;
// 图像的大小
unsigned long m_lVideoWidth;
unsigned long m_lVideoHeight;
};
#endif //_BOFUL_DIRECT3D_HELPER_H_
// Custom flexible vertex format (FVF), which describes custom vertex structure
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1)
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
CD3DHelper::CD3DHelper()
{
InitializeCriticalSection(&m_critial);
// Direct3D
m_pDirect3D9 = NULL;
m_pDirect3DDevice = NULL;
m_pDirect3DSurfaceRender = NULL;
m_pDirect3DTexture = NULL;
m_pDirect3DVertexBuffer = NULL;
m_lVideoWidth = 0;
m_lVideoHeight = 0;
}
CD3DHelper::~CD3DHelper()
{
ReleaseDirect3D();
DeleteCriticalSection(&m_critial);
}
void CD3DHelper::Lock()
{
EnterCriticalSection(&m_critial);
}
void CD3DHelper::UnLock()
{
LeaveCriticalSection(&m_critial);
}
void CD3DHelper::ReleaseDirect3D()
{
Lock();
SAFE_RELEASE(m_pDirect3DVertexBuffer);
SAFE_RELEASE(m_pDirect3DTexture);
SAFE_RELEASE(m_pDirect3DSurfaceRender);
SAFE_RELEASE(m_pDirect3DDevice);
SAFE_RELEASE(m_pDirect3D9);
UnLock();
}
unsigned long CD3DHelper::InitDirect3D( HWND hwnd, unsigned long lWidth, unsigned long lHeight )
{
HRESULT lRet;
ReleaseDirect3D();
Lock();
// 创建Direct3D对象
m_pDirect3D9 = Direct3DCreate9( D3D_SDK_VERSION );
if ( m_pDirect3D9 == NULL )
{
UnLock();
return 1;
}
if ( lWidth == 0 || lHeight == 0 )
{
CRect rt;
GetClientRect( hwnd, &rt );
lWidth = rt.Width();
lHeight = rt.Height();
}
// 检查是否支持后台渲染和窗口的显示
D3DDISPLAYMODE d3dDisplayMode;
lRet = m_pDirect3D9->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3dDisplayMode );
if ( FAILED(lRet) )
{
UnLock();
return 1;
}
// 创建Direct3D设备
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Flags = D3DPRESENTFLAG_VIDEO;
d3dpp.Windowed = TRUE;
d3dpp.hDeviceWindow = hwnd;
d3dpp.BackBufferWidth = lWidth;
d3dpp.BackBufferHeight = lHeight;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
d3dpp.BackBufferFormat = d3dDisplayMode.Format;
d3dpp.BackBufferCount = 1;
d3dpp.EnableAutoDepthStencil = FALSE;
m_hVideoWnd = hwnd;
m_lVideoWidth = lWidth;
m_lVideoHeight = lHeight;
// 创建硬件渲染设备
lRet = m_pDirect3D9->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, NULL,
D3DCREATE_SOFTWARE_VERTEXPROCESSING|D3DCREATE_MULTITHREADED, &d3dpp, &m_pDirect3DDevice );
// Texture coordinates outside the range [0.0, 1.0] are set
// to the texture color at 0.0 or 1.0, respectively.
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
// Set linear filtering quality
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
IDirect3DDevice9_SetSamplerState(m_pDirect3DDevice, 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
// set maximum ambient light
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_AMBIENT, D3DCOLOR_XRGB(255,255,255));
// Turn off culling
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_CULLMODE, D3DCULL_NONE);
// Turn off the zbuffer
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ZENABLE, D3DZB_FALSE);
// Turn off lights
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_LIGHTING, FALSE);
// Enable dithering
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_DITHERENABLE, TRUE);
// disable stencil
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_STENCILENABLE, FALSE);
// manage blending
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHABLENDENABLE, TRUE);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHATESTENABLE,TRUE);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHAREF, 0x10);
IDirect3DDevice9_SetRenderState(m_pDirect3DDevice, D3DRS_ALPHAFUNC,D3DCMP_GREATER);
// Set texture states
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLOROP,D3DTOP_MODULATE);
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLORARG1,D3DTA_TEXTURE);
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_COLORARG2,D3DTA_DIFFUSE);
// turn off alpha operation
IDirect3DDevice9_SetTextureStageState(m_pDirect3DDevice, 0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
// 创建纹理
lRet = m_pDirect3DDevice->CreateTexture(lWidth, lHeight, 1, D3DUSAGE_SOFTWAREPROCESSING,
D3DFMT_X8R8G8B8,
D3DPOOL_MANAGED,
&m_pDirect3DTexture, NULL );
if ( FAILED(lRet) )
{
TRACE( TEXT("创建纹理失败!返回值:0x%08x/n"), lRet );
UnLock();
return 1;
}
// 创建顶点缓冲区
lRet = m_pDirect3DDevice->CreateVertexBuffer( 4 * sizeof(CUSTOMVERTEX),
0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pDirect3DVertexBuffer, NULL );
if ( FAILED(lRet) )
{
UnLock();
return 1;
}
// 填充顶点缓冲区
CUSTOMVERTEX *pVertex;
lRet = m_pDirect3DVertexBuffer->Lock( 0, 4 * sizeof(CUSTOMVERTEX), (void**)&pVertex, 0 );
if ( FAILED(lRet) )
{
UnLock();
return 1;
}
/* -0.5f is a "feature" of DirectX and it seems to apply to Direct3d also */
pVertex[0].x = -0.5f; // left
pVertex[0].y = -0.5f; // top
pVertex[0].z = 0.0f;
pVertex[0].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[0].rhw = 1.0f;
pVertex[0].tu = 0.0f;
pVertex[0].tv = 0.0f;
pVertex[1].x = lWidth - 0.5f; // right
pVertex[1].y = -0.5f; // top
pVertex[1].z = 0.0f;
pVertex[1].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[1].rhw = 1.0f;
pVertex[1].tu = 1.0f;
pVertex[1].tv = 0.0f;
pVertex[2].x = lWidth - 0.5f; // right
pVertex[2].y = lHeight - 0.5f; // bottom
pVertex[2].z = 0.0f;
pVertex[2].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[2].rhw = 1.0f;
pVertex[2].tu = 1.0f;
pVertex[2].tv = 1.0f;
pVertex[3].x = -0.5f; // left
pVertex[3].y = lHeight - 0.5f; // bottom
pVertex[3].z = 0.0f;
pVertex[3].diffuse = D3DCOLOR_ARGB(255, 255, 255, 255);
pVertex[3].rhw = 1.0f;
pVertex[3].tu = 0.0f;
pVertex[3].tv = 1.0f;
m_pDirect3DVertexBuffer->Unlock();
UnLock();
return 0;
}
bool CD3DHelper::DrawImage(byte *pVideoBuff,int iVideoPitch)
{
LRESULT lRet;
if(pVideoBuff == NULL || m_pDirect3DDevice == NULL) return false;
HRESULT hr;
hr = IDirect3DDevice9_Clear(m_pDirect3DDevice, 0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(255, 0, 0), 1.0f, 0);
if ( FAILED(m_pDirect3DDevice->BeginScene()) )
{
return false;
}
lRet = m_pDirect3DDevice->SetTexture( 0, m_pDirect3DTexture );
D3DLOCKED_RECT d3dLock;
lRet = m_pDirect3DTexture->LockRect( 0, &d3dLock, 0, 0 );
if ( SUCCEEDED(lRet) )
{
// 拷贝图像数据到纹理中
byte *pSrc = pVideoBuff;
byte *pDest = (byte *)d3dLock.pBits;
for(unsigned long i=0; i< m_lVideoHeight; i++)
{
memcpy( pDest, pSrc, iVideoPitch );
pDest += d3dLock.Pitch;
pSrc += iVideoPitch;
}
m_pDirect3DTexture->UnlockRect( 0 );
}
m_pDirect3DDevice->SetStreamSource( 0, m_pDirect3DVertexBuffer,
0, sizeof(CUSTOMVERTEX) );
lRet = m_pDirect3DDevice->SetFVF( D3DFVF_CUSTOMVERTEX );
m_pDirect3DDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
m_pDirect3DDevice->EndScene();
m_pDirect3DDevice->Present( NULL, NULL, NULL, NULL );
return true;
}
上面的类非常简单,那如何使用呢?请参看如下代码:
CD3DHelper m_d3dHelper;
m_d3dHelper.InitDirect3D( GetSafeHwnd(), 720, 576 );
HDC hScreenDC = CreateDC( TEXT("DISPLAY"), NULL, NULL, NULL);
if(!hScreenDC) return;
HBITMAP hBmpPreview = (HBITMAP)LoadImage(AfxGetInstanceHandle(), TEXT("c://720_576.bmp"),IMAGE_BITMAP,0,0, LR_LOADFROMFILE);
unsigned long lBufferSize = 720 * 576 * 4;
byte *buff = new byte[lBufferSize];
int iVideoPitch = 0;
GetRGBFromBitmap(buff,hScreenDC,hBmpPreview,720,576,&lBufferSize,&iVideoPitch);
if(hBmpPreview) DeleteObject(hBmpPreview);
if(hScreenDC) DeleteDC(hScreenDC);
if(buff) delete buff;
m_d3dHelper.DrawImage( buff, iVideoPitch );
m_d3dHelper.ReleaseDirect3D();
此时则实现了初始化、装载位图、画位图、释放等功能。至于上面用到的GetRGBFromBitmap()函数见后面的代码:
#define WIDTHBYTES(bits) ((DWORD)(((bits)+31) & (~31)) / 8)
// vertically flip a buffer
// note, this operates on a buffer of widthBytes bytes, not pixels!!!
BOOL VertFlipBuf(BYTE * inbuf, UINT widthBytes, UINT height)
{
BYTE *tb1;
BYTE *tb2;
if (inbuf==NULL)
return FALSE;
UINT bufsize;
bufsize=widthBytes;
tb1= (BYTE *)new BYTE[bufsize];
if (tb1==NULL) {
return FALSE;
}
tb2= (BYTE *)new BYTE [bufsize];
if (tb2==NULL) {
delete [] tb1;
return FALSE;
}
UINT row_cnt;
ULONG off1=0;
ULONG off2=0;
for (row_cnt=0;row_cnt<(height+1)/2;row_cnt++) {
off1=row_cnt*bufsize;
off2=((height-1)-row_cnt)*bufsize;
memcpy(tb1,inbuf+off1,bufsize);
memcpy(tb2,inbuf+off2,bufsize);
memcpy(inbuf+off1,tb2,bufsize);
memcpy(inbuf+off2,tb1,bufsize);
}
delete [] tb1;
delete [] tb2;
return TRUE;
}
//根据位图句柄得到其位图数据的RGB,pBuffSize为以字节为单位的图像大小,这主要是考虑DWORD对齐的问题
BYTE * GetRGBFromBitmap(BYTE *buff,HDC hDC,HBITMAP hBitmap,int iWidth,int iHeight,unsigned long *pBuffSize,int *piPitch)
{
if(buff == NULL) return NULL;
BITMAPINFOHEADER bi; //位图文件头结构
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = iWidth;
bi.biHeight = iHeight;
bi.biPlanes = 1;
bi.biBitCount = 32;
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
int lines = GetDIBits(hDC, hBitmap, 0, iHeight, buff, (LPBITMAPINFO)&bi, DIB_RGB_COLORS);
//如果高度为正数,则DIB为从下到上的,此时需要垂直翻转数据
iWidth = WIDTHBYTES(iWidth * 32) ; //借用iWidth用于存放DWORD对齐的宽度
VertFlipBuf(buff,iWidth,iHeight);
*pBuffSize = iWidth * abs(iHeight);
*piPitch = iWidth;
return buff;
}
要想看到运行的结果,需要设置系统的显示属性窗口中的“颜色质量”为“最高(32位)”(在桌面上单击鼠标右键,在弹出菜单中选择“属性”,此时就会出现“显示 属性”窗口),同时在c盘根目录下存在位图文件720_576.bmp,最后新建一个MFC的对话框程序,将上面的代码加入并调用显示图片。
原创作者陈丹宇(http://hi.csdn.net/happyweb ),注明作者及出处,欢迎转载。