SkeyePlayer依靠D3DRender强大的渲染能力我们可以实现很多视频编辑功能,比如电子放大功能,本文将深入D3DRender渲染引擎库代码,重点讲述其如何采用surface离屏表面技术来实现渲染视频图像呈现,以实现在surface上做电子放大缩略图显示等功能。
首先,我们需要创建一个D3D9设备用于操作系统软硬件资源来为我们的视频渲染服务,这个代码很简单,按照Direct3D教程即可实现,如下代码所示:
pD3D = Direct3DCreate9(D3D_SDK_VERSION);
if (NULL == pD3D)
{
errCode = D3D_NOT_ENABLED;
return false;
}
//获取显卡张数
int adaptnum = pD3D->GetAdapterCount();
//_TRACE("共[%d]张显卡.\n", adaptnum);
//获取显卡支持的显示格式
if (FAILED(pD3D->GetAdapterDisplayMode(nAdapterNo, &d3dDisplayMode )) )
{
__SAFE_RELEASE(pD3D);
return false;
}
//_TRACE("显卡信息: %d X %d\tRefreshRate: %d\tFormat: %d\n", d3dDisplayMode.Width, d3dDisplayMode.Height, d3dDisplayMode.RefreshRate, d3dDisplayMode.Format);
if (FAILED(pD3D->CheckDeviceFormat(nAdapterNo, D3DDEVTYPE_HAL, d3dDisplayMode.Format, 0, D3DRTYPE_SURFACE, d3dFormat)))
{
_TRACE("CheckDeviceFormat 不支持指定的格式..\n");
errCode = D3D_FORMAT_NOT_SUPPORT;
__SAFE_RELEASE(pD3D);
return false;
}
//检查输入格式到显示格式的转换是否支持
if ( FAILED(pD3D->CheckDeviceFormatConversion(nAdapterNo, D3DDEVTYPE_HAL, d3dFormat, d3dDisplayMode.Format) ) )
{
errCode = D3D_FORMAT_NOT_SUPPORT;
__SAFE_RELEASE(pD3D);
return false;
}
//检查是否支持硬件顶点渲染方式
if ( FAILED(pD3D->GetDeviceCaps(nAdapterNo, D3DDEVTYPE_HAL, &d3dCaps) ) )
{
errCode = D3D_VERTEX_HAL_NOT_SUPPORT;
__SAFE_RELEASE(pD3D);
return false;
}
//检测硬件是否支持变换和灯光
INT vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
if ( d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //硬件支持
}
else
{
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //软件支持
}
memset(&d3dParameters, 0, sizeof(D3DPRESENT_PARAMETERS));
D3DFORMAT d3dFormatRender = D3DFMT_UNKNOWN;
d3dParameters.Windowed = TRUE;
d3dParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;//D3DSWAPEFFECT_DISCARD;//D3DSWAPEFFECT_COPY;//D3DSWAPEFFECT_DISCARD; //如果想通过GetBackBuffer获得后备缓冲内容打印屏幕画面,则不能使用 D3DSWAPEFFECT_DISCARD
d3dParameters.BackBufferFormat = d3dFormatRender;//D3DFMT_R5G6B5;//D3DFMT_R5G6B5;//D3DFMT_UNKNOWN; //使用桌面格式
d3dParameters.Flags = D3DPRESENTFLAG_VIDEO;//D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;//D3DPRESENTFLAG_VIDEO;// | D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;
d3dParameters.BackBufferCount = 1;
d3dParameters.BackBufferWidth = width;
d3dParameters.BackBufferHeight = height;
if (NULL == pD3dDevice)
{
HRESULT hr = pD3D->CreateDevice(nAdapterNo, D3DDEVTYPE_HAL, m_hWnd, vp, &d3dParameters, &pD3dDevice);
if ( FAILED(hr) )
{
_TRACE("D3D Create Device fail..");
switch (hr)
{
case D3DERR_DEVICELOST:
{
_TRACE("D3DERR_DEVICELOST\n");
}
break;
case D3DERR_INVALIDCALL:
{
_TRACE("D3DERR_INVALIDCALL\n");
}
break;
case D3DERR_NOTAVAILABLE:
{
_TRACE("D3DERR_NOTAVAILABLE\n");
}
break;
case D3DERR_OUTOFVIDEOMEMORY:
{
_TRACE("D3DERR_OUTOFVIDEOMEMORY\n");
}
break;
default:
break;
}
errCode = D3D_DEVICE_CREATE_FAIL;
__SAFE_RELEASE(pD3D);
return false;
}
}
if ( FAILED( pD3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &pD3dBackBuffer ) ) )
{
errCode = D3D_GETBACKBUFFER_FAIL;
__SAFE_RELEASE(pD3dDevice);
__SAFE_RELEASE(pD3D);
return false;
}
if ( FAILED( pD3dBackBuffer->GetDesc( &surfaceDesc ) ) )
{
errCode = D3D_GETBACKBUFFER_FAIL;
__SAFE_RELEASE(pD3dDevice);
__SAFE_RELEASE(pD3D);
return false;
}
如上代码所示,我们根据需要渲染的视频长宽创建一个支持硬件加速D3D渲染设备,为视频图像渲染做准备。
2D图像渲染有两种方式,一种是是采用纹理的方式加载,还有一张是通过surface离屏表面绘制的方式(类似于DDraw), 纹理加载方式较为复杂,本文采用比较简单surface表面绘制的方式渲染视频图像;首先,通过第一节中创建的D3D设备,在其后台缓冲区(硬件加速相当于在显卡的显存中)我们创建一个视频分辨率大小的离屏表面,代码如下:
HRESULT hr = pD3dDevice->CreateOffscreenPlainSurface( width, height,
d3dFormat, D3DPOOL_DEFAULT,
&d3d9SurfaceNode.pD3d9SurfaceNode[ch].pSurface, NULL);
if (FAILED(hr))
{
errCode = D3D_CREATESURFACE_FAIL;
return NULL;
}
d3d9SurfaceNode.pD3d9SurfaceNode[ch].width = width;
d3d9SurfaceNode.pD3d9SurfaceNode[ch].height = height;
创建好离屏表面(后文统称surface)后,我们就可以将视频解码后的图像数据(YUV/RGB)填充到surface上面去,如下代码所示:
// 视频图像渲染 [4/14/2019 Dingshuai]
if (NULL != pBuff)
{
//if (nSurfaceNum>1) EnterCriticalSection(&m_crit);
//if ( FAILED( pD3d9Surface->pSurface->LockRect( &lockedRect, NULL, D3DLOCK_DONOTWAIT | D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD) ))//D3DLOCK_DISCARD) ))// D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY ) ))
//if ( FAILED( pD3d9Surface->pSurface->LockRect( &lockedRect, NULL, D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY ) ))
DWORD dwFlag = D3DLOCK_DONOTWAIT | D3DLOCK_DISCARD | D3DLOCK_NOSYSLOCK;
//D3DERR_INVALIDCALL;
//D3DERR_WASSTILLDRAWING;
HRESULT hr = pD3d9Surface->pSurface->LockRect( &lockedRect, NULL, dwFlag);//D3DLOCK_DONOTWAIT | D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD);
if ( FAILED( hr ))//D3DLOCK_DISCARD) ))// D3DLOCK_NO_DIRTY_UPDATE | D3DLOCK_NOSYSLOCK | D3DLOCK_READONLY ) ))
{
if (hr == D3DERR_INVALIDCALL)
{
errCode = D3D_LOCKSURFACE_FAIL;
}
else if (hr == D3DERR_WASSTILLDRAWING)
{
errCode = D3D_LOCKSURFACE_FAIL;
}
errCode = D3D_LOCKSURFACE_FAIL;
//LeaveCriticalSection(&pD3d9Surface->crit);
break;
}
FillDataToSurface((unsigned char *)lockedRect.pBits, pBuff, width, height, lockedRect.Pitch, d3dFormat);
if ( FAILED (pD3d9Surface->pSurface->UnlockRect() ))
{
errCode = D3D_UNLOCKSURFACE_FAIL;
//LeaveCriticalSection(&pD3d9Surface->crit);
break;
}
}
向surface拷贝数据之前需要调用surface的LockRect函数锁住表面,以免出现内存访问冲突导致出现未知的错误。
surface填充数据以后,我们需要把surface上填充的内容拷贝到后台缓冲区中,然后做统一的渲染呈现,如下代码所示:
// 使用自定义表面填充后台缓存
RECT rcTmp;
SetRect(&rcTmp, 0, 0, width, height);
if ( FAILED(pD3dDevice->StretchRect(pD3d9Surface->pSurface, lpRectSrc/*&rcTmp*/, pD3dBackBuffer, &rcDst, D3DTEXF_NONE ) ))
{
errCode = D3D_UPDATESURFACE_FAIL;
break;
}
HRESULT hr = 0;
hr = pD3dDevice->Present(NULL, &rcDst, hWnd, NULL);
通过以上3节我们基本已经清楚了整个D3DRender渲染的详细流程,下面我们来实现电子放大功能;首先,我们通过libSkeyePlayerPro_SetElectronicZoomStartPoint接口设置放大的起始点坐标,通过libSkeyePlayerPro_SetElectronicZoomEndPoint接口设置放大的终点坐标,从而确定放大的视频区域大小;显示线程通过坐标换算处理,获取到选区在视频上的坐标区域,然后填充数据选区的矩形大小,如下代码所示:
//电子放大
if ((pMediaChannel->pElectoricZoom) && (pMediaChannel->pElectoricZoom->zoomIn==0x01) )
{
pChannelManager->ElectronicZoomProcess(pMediaChannel, &pMediaChannel->yuvFrame[iDisplayYuvIdx].frameinfo);
if (pMediaChannel->mediaDisplay.renderFormat == RENDER_FORMAT_RGB24_GDI)
{
GDI_ResetDragPoint(pMediaChannel->mediaDisplay.d3dHandle);
}
else
{
D3D_ResetSelZone(pMediaChannel->mediaDisplay.d3dHandle);
}
}
RECT rcSrc;
RECT rcDst;
if ( ! IsRectEmpty(&pMediaChannel->mediaDisplay.rcSrcRender))
{
CopyRect(&rcSrc, &pMediaChannel->mediaDisplay.rcSrcRender);
}
else
{
SetRect(&rcSrc, 0, 0, width, height);
}
在以上代码中,ElectronicZoomProcess函数负责把windows窗口坐标系转换成图像坐标系,然后将换算后的视频选择矩形区域拷贝到源选区rcSrc矩形区域内;最后通过D3D_UpdateData接口传入做电子放大处理;我们回顾第三节将传入的源选区视频图像拷贝到后台缓冲中,这时候通过后台缓冲渲染呈现出来的视频就是我们通过电子放大后的视频区域,为了方便观看,我们将完整的视频图像通过缩略图的方式绘制到右下角,如下代码所示:
bool bDrawThumnail = false;
RECT rcSrc;
SetRect(&rcSrc, 0, 0, width, height);
if (!EqualRect(lpRectSrc, &rcSrc))
{
RECT rcDst2;
CopyRect(&rcDst2, lpRectDst);
rcDst2.left = lpRectDst->right-(lpRectDst->right-lpRectDst->left)/4;
rcDst2.top = lpRectDst->bottom-(lpRectDst->bottom-lpRectDst->top)/4;
rcDst2.right -= 10;
rcDst2.bottom-= 10;
int tLeft = rcDst2.left+(int)(float)((float)(rcDst2.right-rcDst2.left)*((float)pD3d9Surface->pipRect.left/100.0f));
int tTop = rcDst2.top+(int)(float)((float)((rcDst2.bottom-rcDst2.top)*(float)pD3d9Surface->pipRect.top/100.0f));
int tRight = rcDst2.left+(int)(float)((float)(rcDst2.right-rcDst2.left)*((float)pD3d9Surface->pipRect.right/100.0f));
int tBottom = rcDst2.top+(int)(float)((float)((rcDst2.bottom-rcDst2.top)*(float)pD3d9Surface->pipRect.bottom/100.0f));
if ( FAILED(pD3dDevice->StretchRect(pD3d9Surface->pSurface, /*lpRectSrc*/&rcSrc, pD3dBackBuffer, &rcDst2, D3DTEXF_NONE ) ))
{
errCode = D3D_UPDATESURFACE_FAIL;
//LeaveCriticalSection(&pD3d9Surface->crit);
break;
}
bDrawThumnail = true;
}
至此,电子放大功能就完成了,由于surface渲染方式的方便性,我们几乎不用修改几行代码即可实现这个看似很复杂的功能,电子放大功能如下图所示:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。