教学、会议、展厅大并发无线互联网直播同屏实现之LibEasyScreenLive通过GDI方式实现屏幕捕获采集方法介绍
EasyScreenLive是一款简单、高效、稳定的集采集,编码,组播,推流和流媒体RTSP服务于一身的同屏功能组件,具低延时,高效能,低丢包等特点。
LibEasyScreenLive通过GDI方式实现屏幕捕获采集
windows端最通用的屏幕捕获方式就是通过GDI(图形设备接口)获取桌面的设备上下文DC,然后将其内容转换为RGB位图,从而转换成视频帧实现屏幕的采集,GDI是一种微软较为古老的技术,其采集效率相对较低,不过实现30fps帧率的桌面采集还是绰绰有余的,而且在设备性能较差或者操作系统版本较低的系统上也能兼容。
LibEasyScreenLive通过GDI方式实现屏幕捕获采集的实现主要是通过CCaptureScreen
类实现,该类声明如下:
class CCaptureScreen
{
public:
CCaptureScreen(void);
~CCaptureScreen(void);
public:
//Interface Function
int SetCaptureCursor(bool bShow);
int Init(HWND hShowWnd);
void UnInit();
int StartScreenCapture(int nCapMode = 2, int fps = 25);
void StopScreenCapture();
void GetCaptureScreenSize(int& nWidth, int& nHeight );
//设置捕获数据回调函数
void SetCaptureScreenCallback(CaptureScreenCallback callBack, void * pMaster);
BOOL IsInCapturing()
{
return m_bCapturing;
}
public:
//////////////////////////////////////////////////////////////////////////
//屏幕捕获帧主函数
//Use these 2 functions to create frames and free frames
void* CaptureScreenFrame(int left,int top,int width, int height,int tempDisableRect);
void FreeFrame(void*) ;
void InsertHighLight(HDC hdc,int xoffset, int yoffset);
//inner call function
BOOL isRectEqual(RECT a, RECT b);
void SaveBitmapCopy(HDC hdc,HDC hdcbits, int x, int y, int sx, int sy);
void RestoreBitmapCopy(HDC hdc,HDC hdcbits, int x, int y, int sx, int sy) ;
void DeleteBitmapCopy();
void NormalizeRect(LPRECT prc);
void FixRectSizePos(LPRECT prc,int maxxScreen, int maxyScreen);
//Mouse Capture functions
HCURSOR FetchCursorHandle();
HANDLE Bitmap2Dib(HBITMAP, UINT);
//创建线程进行屏幕捕获
int CreateCaptureScreenThread();
static UINT WINAPI CaptureScreenThread(LPVOID pParam);
void CaptureVideoProcess();
};
主要桌面采集流程在线程CaptureScreenThread中实现,采集线程执行体函数实现代码如下:
void CCaptureScreen::CaptureVideoProcess()
{
int top=m_rcUse.top;
int left=m_rcUse.left;
int width=m_rcUse.right-m_rcUse.left+1;
int height=m_rcUse.bottom - m_rcUse.top + 1;
// [1/27/2016 SwordTwelve]
//长宽做一下修正,修正为16的倍数
int nDivW = width%16;
int nDivH = height%16;
if (nDivW<8)
width -= nDivW;
else
width += (16 - nDivW);
if (nDivH<8)
height -= nDivH;
else
height += (16 - nDivH);
if (width>m_nMaxxScreen)
{
width = m_nMaxxScreen;
}
if (height>m_nMaxyScreen)
{
height = m_nMaxyScreen;
}
//设置捕获帧率
int nFps = m_nFps;
/*LPBITMAPINFOHEADER*/VOID* alpbi = NULL;
RECT panrect_current;
RECT panrect_dest;
if (m_bAutopan)
{
panrect_current.left = left;
panrect_current.top = top;
panrect_current.right = left + width - 1;
panrect_current.bottom = top + height - 1;
}
_LARGE_INTEGER nowTime;
_LARGE_INTEGER lastTime;
LARGE_INTEGER cpuFreq; //cpu频率
timeBeginPeriod(1);
QueryPerformanceFrequency(&cpuFreq);
timeEndPeriod(1);
//Into Capture Loop
while (m_bCapturing)
{
timeBeginPeriod(1);
QueryPerformanceCounter(&lastTime);
timeEndPeriod(1);
//Autopan
if ((m_bAutopan) && (width < m_nMaxxScreen) && (height < m_nMaxyScreen))
{
POINT xPoint;
GetCursorPos(&xPoint);
int extleft = ((panrect_current.right - panrect_current.left)*1)/4 + panrect_current.left;
int extright = ((panrect_current.right - panrect_current.left)*3)/4 + panrect_current.left;
int exttop = ((panrect_current.bottom - panrect_current.top)*1)/4 + panrect_current.top;
int extbottom = ((panrect_current.bottom - panrect_current.top)*3)/4 + panrect_current.top;
if (xPoint.x < extleft ) //need to pan left
{
panrect_dest.left = xPoint.x - width/2;
panrect_dest.right = panrect_dest.left + width - 1;
if (panrect_dest.left < 0)
{
panrect_dest.left = 0;
panrect_dest.right = panrect_dest.left + width - 1;
}
}
else if (xPoint.x > extright ) //need to pan right
{
panrect_dest.left = xPoint.x - width/2;
panrect_dest.right = panrect_dest.left + width - 1;
if (panrect_dest.right >= m_nMaxxScreen)
{
panrect_dest.right = m_nMaxxScreen - 1;
panrect_dest.left = panrect_dest.right - width + 1;
}
}
else
{
panrect_dest.right = panrect_current.right;
panrect_dest.left = panrect_current.left;
}
if (xPoint.y < exttop ) //need to pan up
{
panrect_dest.top = xPoint.y - height/2;
panrect_dest.bottom = panrect_dest.top + height - 1;
if (panrect_dest.top < 0)
{
panrect_dest.top = 0;
panrect_dest.bottom = panrect_dest.top + height - 1;
}
}
else if (xPoint.y > extbottom ) { //need to pan down
panrect_dest.top = xPoint.y - height/2;
panrect_dest.bottom = panrect_dest.top + height - 1;
if (panrect_dest.bottom >= m_nMaxyScreen)
{
panrect_dest.bottom = m_nMaxyScreen - 1;
panrect_dest.top = panrect_dest.bottom - height + 1;
}
}
else
{
panrect_dest.top = panrect_current.top;
panrect_dest.bottom = panrect_current.bottom;
}
//Determine Pan Values
int xdiff,ydiff;
xdiff = panrect_dest.left - panrect_current.left;
ydiff = panrect_dest.top - panrect_current.top;
if (abs(xdiff) < m_nAutopanSpeed)
{
panrect_current.left += xdiff;
}
else
{
if (xdiff<0)
panrect_current.left -= m_nAutopanSpeed;
else
panrect_current.left += m_nAutopanSpeed;
}
if (abs(ydiff) < m_nAutopanSpeed)
{
panrect_current.top += ydiff;
}
else
{
if (ydiff<0)
panrect_current.top -= m_nAutopanSpeed;
else
panrect_current.top += m_nAutopanSpeed;
}
panrect_current.right = panrect_current.left + width - 1;
panrect_current.bottom = panrect_current.top + height - 1;
alpbi=CaptureScreenFrame(panrect_current.left,panrect_current.top,width, height,0);
}
else
alpbi=CaptureScreenFrame(left,top,width, height,0);
#if 1
if (m_hMainWnd&&IsWindowVisible(m_hMainWnd)&&IsWindow(m_hMainWnd))
{
RECT rcClient;
::GetClientRect(m_hMainWnd, &rcClient);
if (rcClient.right-rcClient.left>0&&rcClient.bottom-rcClient.top>0)
{
vdev_setrect(m_videoRender, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom);
// Capture Data callBack
//测试显示
void* buffer = NULL;
int stride = 0;
vdev_request (m_videoRender, &buffer, &stride);
int swnew = ((VDEV_COMMON_CTXT*)m_videoRender)->sw;
int shnew = ((VDEV_COMMON_CTXT*)m_videoRender)->sh;
//memcpy(buffer, alpbi, width*height*3);
vdev_convert(AV_PIX_FMT_BGR24, width, height, alpbi, AV_PIX_FMT_RGB32, swnew, shnew, (unsigned char**)&buffer);
vdev_post (m_videoRender, clock());
}
}
if (m_pCallback&&m_pMaster)
{
ScreenCapDataInfo sCapScreenInfo;
sCapScreenInfo.nWidth = width;
sCapScreenInfo.nHeight = height;
sCapScreenInfo.nDataType = 24;
strcpy(sCapScreenInfo.strDataType, "RGB24");
m_pCallback(m_nId, (unsigned char*)(alpbi), /*alpbi->biSizeImage*/m_dibBufferLen, 1, &sCapScreenInfo, m_pMaster);
}
#endif
timeBeginPeriod(1);
QueryPerformanceCounter(&nowTime);
timeEndPeriod(1);
if (cpuFreq.QuadPart < 1)
cpuFreq.QuadPart = 1;
int lInterval = (int)(((nowTime.QuadPart - lastTime.QuadPart) / (double)cpuFreq.QuadPart * (double)1000));
//Slowly thread By framerate
int nDelay = 1000/nFps - lInterval;
#if 0
char sMsg[64] = {0,};
sprintf(sMsg, "lInterval = %d nFps =%d nDelay = %d\r\n", lInterval, nFps, nDelay);
OutputDebugStringA( sMsg );
#endif
if (nDelay>0)
{
Sleep(nDelay);
}
}
}
其中,通过GDI获取屏幕DC并将其内容转换为RGB图像实现核心函数如下:
void* CCaptureScreen::CaptureScreenFrame(int left,int top,int width, int height,int tempDisableRect)
{
//获取桌面屏幕设备DC
HDC hScreenDC = ::GetDC(NULL);
HDC hMemDC = ::CreateCompatibleDC(hScreenDC);
HBITMAP hbm;
hbm = CreateCompatibleBitmap(hScreenDC, width, height);
HBITMAP oldbm = (HBITMAP) SelectObject(hMemDC, hbm);
BitBlt(hMemDC, 0, 0, width, height, hScreenDC, left, top, SRCCOPY);
//Get Cursor Pos
POINT xPoint;
GetCursorPos( &xPoint );
HCURSOR hcur= FetchCursorHandle();
xPoint.x-=left;
xPoint.y-=top;
//Draw the HighLight
if (m_bHighlightCursor==1)
{
POINT highlightPoint;
highlightPoint.x = xPoint.x -64 ;
highlightPoint.y = xPoint.y -64 ;
InsertHighLight( hMemDC, highlightPoint.x, highlightPoint.y);
}
//Draw the Cursor
if (m_bRecordCursor==1)
{
ICONINFO iconinfo ;
BOOL ret;
ret = GetIconInfo( hcur, &iconinfo );
if (ret)
{
xPoint.x -= iconinfo.xHotspot;
xPoint.y -= iconinfo.yHotspot;
//need to delete the hbmMask and hbmColor bitmaps
//otherwise the program will crash after a while after running out of resource
if (iconinfo.hbmMask) DeleteObject(iconinfo.hbmMask);
if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor);
}
// 修正鼠标信息 [7/19/2018 SwordTwelve]
::DrawIcon( hMemDC, xPoint.x*m_fScreenxScale, xPoint.y*m_fScreenyScale, hcur);
}
SelectObject(hMemDC,oldbm);
void* pBM_HEADER = /*(LPBITMAPINFOHEADER)*/(Bitmap2Dib(hbm, 24)); //m_nColorBits
//LPBITMAPINFOHEADER pBM_HEADER = (LPBITMAPINFOHEADER)GlobalLock(Bitmap2Dib(hbm, 24));
if (pBM_HEADER == NULL)
{
return NULL;
}
DeleteObject(hbm);
DeleteDC(hMemDC);
ReleaseDC(NULL,hScreenDC) ;
return pBM_HEADER;
}