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