教學、會議、展廳大併發無線互聯網直播同屏實現之LibEasyScreenLive通過GDI方式實現屏幕捕獲採集方法介紹

EasyScreenLive功能介紹

青犀團隊根據市場需求研發的EasyScreenLive,就是一款簡單、高效、穩定的集採集,編碼,組播,推流和流媒體RTSP服務於一身的同屏功能組件,具低延時,高效能,低丟包等特點。目前支持Windows,Android平臺,通過EasyScreenLive我們就可以避免接觸到稍顯複雜的音視頻源採集,編碼和流媒體推送以及RTSP/RTP/RTCP/RTMP服務流程,只需要調用EasyScreenLive的幾個API接口,就能輕鬆、穩定地把流媒體音視頻數據RTMP推送給EasyDSS服務器以及發佈RTSPServer服務, 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章