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; }