教学、会议、展厅大并发无线互联网直播同屏实现之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;
}
posted on 2019-10-16 11:32  TSINGSEE  阅读(472)  评论(0编辑  收藏  举报