本文介绍如何使用windows GDI抓取图像并将图像转化为RGB,这样可以传递给FFMPEG进行编解码。步骤如下:
1.获取屏幕或者窗口句柄
首先通过FindWindow找到对应窗口的HWND,桌面HWND 为NULL。
录制分为屏幕录制和窗口录制,对于使用GDI抓图来讲就是获取对应对象的句柄
使用GetWindowDC来获取对应的设备描述符;
hdc_screen = GetWindowDC(hwnd)
2.创建内存hdc
hdc_mem = CreateCompatibleDC(hdc_screen)
3.创建位图
hbm_mem = CreateCompatibleBitmap(hdc_screen, _width, _height);
4.关联设备描述符
SelectObject(hdc_mem, hbm_mem);
5.将窗口或者屏幕图像绘制到位图对象
PrintWindow(_main_record_hwnd, hdc_mem, PW_RENDERFULLCONTENT );
GDI有两种方式来实现绘制PrintWindow和BitBlt,PrintWindow方式效率稍高一点。最好的方式是先使用PrintWindow,如果失败再使用
BitBlt(hdc_mem, 0, 0, _width, _height, hdc_screen, _rect.left, _rect.top, SRCCOPY | CAPTUREBLT)
注意参数PW_RENDERFULLCONTENT和CAPTUREBLT,这两个参数必须有,不然获取不到透明窗体。
6.拿到位图数据
bi.biSize = sizeof(BITMAPINFOHEADER);
bi.biWidth = _width;
bi.biHeight = _height * (-1);
bi.biPlanes = 1;
bi.biBitCount = 32;//should get from system color bits
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
//scan colors by line order
int ret = GetDIBits(hdc_mem, hbm_mem, 0, _height, _buffer, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
if (ret <= 0 || ret == ERROR_INVALID_PARAMETER) {
al_error("get dibits failed:%lu", GetLastError());
error = AE_GDI_GET_DIBITS_FAILED;
break;
}
7.释放内存
if(hbm_mem)
DeleteObject(hbm_mem);
if(hdc_mem)
DeleteObject(hdc_mem);
if(hdc_screen)
ReleaseDC(NULL, hdc_screen);
完整代码:
int RecordWindowGDI::do_record()
{
//int width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
//int height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
HDC hdc_screen = NULL, hdc_mem = NULL;
HBITMAP hbm_mem = NULL;
int error = AE_ERROR;
do {
if(_main_record_hwnd == nullptr)
{
std::string windowname = get_main_record_window();
if(_main_record_hwnd == 0)
{
_main_record_hwnd = ::FindWindowA(NULL, windowname.c_str());
}
}
//if window Minimized use last frame data
if(::IsIconic(_main_record_hwnd))
{
memcpy(_buffer, _last_buffer, _buffer_size);
return AE_NO;
}
hdc_screen = GetWindowDC(_main_record_hwnd);
if (!hdc_screen) {
al_error("get window dc failed:%lu", GetLastError());
error = AE_GDI_GET_DC_FAILED;
break;
}
hdc_mem = CreateCompatibleDC(hdc_screen);
if (!hdc_mem) {
al_error("create compatible dc failed:%lu", GetLastError());
error = AE_GDI_CREATE_DC_FAILED;
break;
}
hbm_mem = CreateCompatibleBitmap(hdc_screen, _width, _height);
if (!hbm_mem) {
al_error("create compatible bitmap failed:%lu", GetLastError());
error = AE_GDI_CREATE_BMP_FAILED;
break;
}
SelectObject(hdc_mem, hbm_mem);
//first use PrintWindow cpu low than BitBlt
BOOL result = PrintWindow(_main_record_hwnd, hdc_mem, PW_RENDERFULLCONTENT );
if(!result)
{
if (!BitBlt(hdc_mem, 0, 0, _width, _height, hdc_screen, _rect.left, _rect.top, SRCCOPY | CAPTUREBLT)) {
al_error("bitblt data failed:%lu", GetLastError());
//error = AE_GDI_BITBLT_FAILED;
//administrator UAC will trigger invalid handle error
break;
}
}
for(int i =0 ; i<_record_windows.size(); ++i)
{
//noly child window
if(_record_windows.at(i).main_record_window)
continue;
draw_chid_window(_record_windows.at(i).name, _record_windows.at(i).need_transparent, _record_windows.at(i).transparent_color, 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 color bits
bi.biCompression = BI_RGB;
bi.biSizeImage = 0;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0;
//scan colors by line order
int ret = GetDIBits(hdc_mem, hbm_mem, 0, _height, _buffer, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
if (ret <= 0 || ret == ERROR_INVALID_PARAMETER) {
al_error("get dibits failed:%lu", GetLastError());
error = AE_GDI_GET_DIBITS_FAILED;
break;
}
error = AE_NO;
} while (0);
if(hbm_mem)
DeleteObject(hbm_mem);
if(hdc_mem)
DeleteObject(hdc_mem);
if(hdc_screen)
ReleaseDC(NULL, hdc_screen);
memcpy(_last_buffer, _buffer, _buffer_size);
return AE_NO;
}