SkeyePlayer 依附 D3DRender 弱小的渲染能力咱们能够实现很多视频编辑性能,比方电子放大性能,本文将深刻 D3DRender 渲染引擎库代码,重点讲述其如何采纳 surface 离屏外表技术来实现渲染视频图像出现,以实现在 surface 上做电子放大缩略图显示等性能。
1. D3DRender 初始化 D3D 创立设施
首先,咱们须要创立一个 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 渲染设施,为视频图像渲染做筹备。
2. D3DRender 创立离屏外表
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;
3. D3DRender 通过离屏外表渲染视频图像数据
创立好离屏外表(后文统称 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);
4. D3DRender 电子放大性能实现
通过以上 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 渲染形式的方便性,咱们简直不必批改几行代码即可实现这个看似很简单的性能,电子放大性能如下图所示: