Windows下最简单的D3D视频显示(YV12)
很久没写日志了,很久没写代码了!
首先在.h中定义变量:
变量声明
int m_nWidth;
int m_nHeight;//保存视频宽高信息
IDirect3D9 * m_pD3D;
IDirect3DDevice9 * m_pd3dDevice;
IDirect3DSurface9 * m_pd3dSurface;//D3D绘图用变量
CRect m_rtViewport;//视频显示区域(要保持宽高比)
要在.cpp中构造函数中初始化NULL,在析构函数或者反初始化函数中Release变量,这里略过!
其次,添加函数SetVideoSize:
SetVideoSize
void Cxxx::SetVideoSize(long lWidth,long lHeight)
{
m_nWidth = lWidth;
m_nHeight = lHeight;
if(m_pD3D != NULL)
{
m_pD3D->Release();
m_pD3D = NULL;
}
if(m_pd3dDevice != NULL)
{
m_pd3dDevice->Release();
m_pd3dDevice = NULL;
}
if(m_pd3dSurface != NULL)
{
m_pd3dSurface->Release();
m_pd3dSurface = NULL;
}
m_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
if( m_pD3D == NULL )
return;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
double dbAspect = (double)lWidth / lHeight;
CRect rtClient;
m_PresentWnd.GetClientRect(&rtClient);
m_rtViewport = rtClient;
if(rtClient.Width() > rtClient.Height() * dbAspect)
{
//width lager than height,adjust the width
int nValidW(static_cast<int>(rtClient.Height() * dbAspect));
int nLost(rtClient.Width() - nValidW);
m_rtViewport.left += nLost / 2;
m_rtViewport.right = m_rtViewport.left + nValidW;
}
else
{
//height lager than width,adjust the height
int nValidH(static_cast<int>(rtClient.Width() / dbAspect));
int nLost(rtClient.Height() - nValidH);
m_rtViewport.top += nLost / 2;
m_rtViewport.bottom = m_rtViewport.top + nValidH;
}
if( FAILED( m_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_PresentWnd.GetSafeHwnd(),
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &m_pd3dDevice ) ) )
return;
if( FAILED(m_pd3dDevice->CreateOffscreenPlainSurface(
lWidth,lHeight,
(D3DFORMAT)MAKEFOURCC('Y', 'V', '1', '2'),
D3DPOOL_DEFAULT,
&m_pd3dSurface,
NULL)))
return;
}
其中,m_PresentWnd为定义显示区域的窗口对象。
再次添加SetYUVbuffer函数用于设置显示数据(这里只表达了显示YV12的数据格式):
SetYUVbuffer
void Cxxx::SetYUVbuffer(LPBYTE pBuffer,long lLen)
{
if(m_pd3dSurface == NULL)return;
D3DLOCKED_RECT d3d_rect;
if( FAILED(m_pd3dSurface->LockRect(&d3d_rect,NULL,D3DLOCK_DONOTWAIT)))
return ;
const int w = m_nWidth,h = m_nHeight;
BYTE * const p = (BYTE *)d3d_rect.pBits;
const int stride = d3d_rect.Pitch;
int i = 0;
for(i = 0;i < h;i ++)
{
memcpy(p + i * stride,pBuffer + i * w, w);
}
for(i = 0;i < h/2;i ++)
{
memcpy(p + stride * h + i * stride / 2,pBuffer + w * h + w * h / 4 + i * w / 2, w / 2);
}
for(i = 0;i < h/2;i ++)
{
memcpy(p + stride * h + stride * h / 4 + i * stride / 2,pBuffer + w * h + i * w / 2, w / 2);
}
if( FAILED(m_pd3dSurface->UnlockRect()))
{
return ;
}
DrawImage();
}
注意:这里的函数参数pBuffer中所传递的YV12数据,其Pitch等于Width,并且YUV数据一次排列,无Padding数据。
最后添加函数DrawImage函数将图像显示出来:
DrawImage
void Cxxx::DrawImage()
{
if (m_pd3dDevice != NULL)
{
m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 );
m_pd3dDevice->BeginScene();
IDirect3DSurface9 * pBackBuffer = NULL;
m_pd3dDevice->GetBackBuffer(0,0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer);
m_pd3dDevice->StretchRect(m_pd3dSurface,NULL,pBackBuffer,&m_rtViewport,D3DTEXF_LINEAR);
m_pd3dDevice->EndScene();
m_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
}
至此,函数编码结束。
调用过程:
当图像画幅改变时,首先调用SerVideoSize设置新的图像画幅;
然后就定时的调用SetYUVBuffer来输入视频数据即可。