FFMPEG錄屏(7)----捕獲桌面(GDI)

補充通過GDI捕獲桌面方法

微軟API和DEMO

GDI Capturing an Image
Desktop Duplication API

建議是WIN7使用GDI(錄製時關閉AERO會有效率上的顯著提升),WIN8及以上使用Duplication方式進行捕獲。

捕獲流程(翻譯過來會走味。。。還是自行體會)

To store an image temporarily, your application must call CreateCompatibleDC to create a DC that is compatible with the current window DC. After you create a compatible DC, you create a bitmap with the appropriate dimensions by calling the CreateCompatibleBitmap function and then select it into this device context by calling the SelectObject function.

After the compatible device context is created and the appropriate bitmap has been selected into it, you can capture the image. The BitBlt function captures images. This function performs a bit block transfer that is, it copies data from a source bitmap into a destination bitmap. However, the two arguments to this function are not bitmap handles. Instead, BitBlt receives handles that identify two device contexts and copies the bitmap data from a bitmap selected into the source DC into a bitmap selected into the target DC. In this case, the target DC is the compatible DC, so when BitBlt completes the transfer, the image has been stored in memory. To redisplay the image, call BitBlt a second time, specifying the compatible DC as the source DC and a window (or printer) DC as the target DC.

大概意思就是說先獲取桌面的DC句柄,然後創建一個兼容句柄,再利用BitBlt函數拷貝圖像數據到創建的兼容句柄中,可以在此時進行截取、縮放、繪製鼠標、繪製LOGO等,最後通過GetDIBits函數獲取RGB數據,保存或者壓縮即可。

獲取桌面DC
HDC hdc_screen = GetWindowDC(NULL);
創建兼容DC
HDC hdc_mem = CreateCompatibleDC(hdc_screen);
根據桌面DC創建兼容位圖句柄HBITMAP
int _width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
int _height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
HBITMAP hbm_mem = CreateCompatibleBitmap(hdc_screen, _width, _height);

值得一提的是,此處width,height取值爲桌面大小,在實際使用中可以是目標區域大小、目標窗口大小。

將位圖選入創建好的兼容句柄中
SelectObject(hdc_mem, hbm_mem);

請注意此函數的返回值,當進行繪圖時,如果hdc_mem是一個重複使用或者是一個目標窗口的DC時,你可能需要主動釋放返回的Object,否則會造成內存泄漏。

拷貝圖像到兼容句柄中
//must have CAPTUREBLT falg,otherwise some layered window can not be captured
BitBlt(hdc_mem, 0, 0, _width, _height, hdc_screen, _rect.left, _rect.top, SRCCOPY | CAPTUREBLT)

這裏又有大坑了,請Google搜索CAPTUREBLT標誌,因爲它將導致你的鼠標不停地閃爍。據說這是微軟的機制,會在每次調用此函數的時候對鼠標進行Show和Hide操作,說是要得到純淨的圖像????WTF!!
在Windows中,當窗體含有WS_EX_LAYERED標誌時,如果沒有CAPTUREBLT標誌將無法捕獲到。

CAPTUREBLT
Includes any windows that are layered on top of your window in the resulting image. By default, the image only contains your window. Note that this generally cannot be used for printing device contexts.

所以嗎請酌情附加此標誌,我所遇到的場景是在共享桌面的同時進行着視頻通話,其中雙方的視頻小窗口就是個Layered標誌的窗口,因此需要附加CAPTUREBLT

繪製鼠標
void record_desktop_win_gdi::draw_cursor(HDC hdc)
{
	if (!(_ci.flags & CURSOR_SHOWING))
		return;
		
	//is cursor in the tartet zone
	if (_ci.ptScreenPos.x < _rect.left ||
		_ci.ptScreenPos.x > _rect.right ||
		_ci.ptScreenPos.y < _rect.top ||
		_ci.ptScreenPos.y > _rect.bottom
		)
		return;

	HICON icon;
	ICONINFO ii;

	icon = CopyIcon(_ci.hCursor);
	if (!icon)
		return;

	if (GetIconInfo(icon, &ii)) {
		POINT pos;
		DrawIconEx(hdc, _ci.ptScreenPos.x, _ci.ptScreenPos.y, icon, 0, 0, 0, NULL, DI_NORMAL);

		DeleteObject(ii.hbmColor);
		DeleteObject(ii.hbmMask);
	}

	DestroyIcon(icon);
}
CURSORINFO _ci;
if (GetCursorInfo(&_ci)) {
	draw_cursor(hdc_mem);
}
初始化位圖信息
BITMAPINFOHEADER   bi;

bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = _width;
bi.biHeight = _height * (-1);
bi.biPlanes = 1;
bi.biBitCount = 32;//should get from system
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;

一個有趣的事是你需要將bi.biHeight設置爲負值,否則保存的圖像將是反轉的。

獲取位圖RGB數據
//scan colors by line order
GetDIBits(hdc_mem, hbm_mem, 0, _height, _buffer, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
釋放句柄
if(hbm_mem)
	DeleteObject(hbm_mem);

if(hdc_mem)
	DeleteObject(hdc_mem);

if(hdc_screen)
	ReleaseDC(NULL, hdc_screen);

至此已經完成了GDI方式的桌面捕獲,_buffer爲RGB數據緩存。

保存BMP文件預覽
//save bmp to test
BITMAPFILEHEADER bf;
bf.bfType = 0x4d42;
bf.bfReserved1 = 0;
bf.bfReserved2 = 0;
bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
bf.bfSize = bf.bfOffBits + _width * _height * 4;

FILE *fp = fopen("..\\..\\save.bmp", "wb+");

fwrite(&bf, 1, sizeof(bf), fp);
fwrite(&bi, 1, sizeof(bi), fp);
fwrite(_buffer, 1, _buffer_size, fp);

fflush(fp);
fclose(fp);

至於buffer大小,在這裏我們的RGB數據存儲格式爲BGRA,什麼意思呢就是整個屏幕安裝寬高進行線性掃描(感謝第一份工作老闆的解釋),從屏幕左上角開始,每一個像素點均有Red、Green、Blue、Alpha表示,也就是說每一個像素點佔用4byte。從左至右從高到低進行存儲,所以BGRA數據大小爲width*height*4
這也是GetDIBits函數中第三個(start)第四個參數(cline)的填法解釋。

GitHub傳送門

screen-recorder

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章