#include "record_desktop_gdi.h" #include "error_define.h" #include "log_helper.h" namespace am { record_desktop_gdi::record_desktop_gdi() { _data_type = RECORD_DESKTOP_DATA_TYPES::AT_DESKTOP_BGRA; _buffer = NULL; _buffer_size = 0; _draw_cursor = true; _hdc = NULL; _bmp = NULL; _bmp_old = NULL; _ci = { 0 }; } record_desktop_gdi::~record_desktop_gdi() { stop(); clean_up(); } int record_desktop_gdi::init(const RECORD_DESKTOP_RECT & rect, const int fps) { int error = AE_NO; if (_inited == true) { return error; } _fps = fps; _rect = rect; do { _width = rect.right - rect.left; _height = rect.bottom - rect.top; _buffer_size = (_width * 32 + 31) / 32 * _height * 4; _buffer = new uint8_t[_buffer_size]; _start_time = av_gettime_relative(); _time_base = { 1,AV_TIME_BASE }; _pixel_fmt = AV_PIX_FMT_BGRA; _inited = true; } while (0); al_info("init gdi finished,error: %s %ld", err2str(error), GetLastError()); return error; } int record_desktop_gdi::start() { if (_running == true) { al_warn("record desktop gdi is already running"); return AE_NO; } if (_inited == false) { return AE_NEED_INIT; } _running = true; _thread = std::thread(std::bind(&record_desktop_gdi::record_func, this)); return AE_NO; } int record_desktop_gdi::pause() { _paused = true; return AE_NO; } int record_desktop_gdi::resume() { _paused = false; return AE_NO; } int record_desktop_gdi::stop() { _running = false; if (_thread.joinable()) _thread.join(); return AE_NO; } void record_desktop_gdi::clean_up() { _inited = false; if (_buffer) delete[] _buffer; _buffer = nullptr; } void record_desktop_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; int dstx = 0, dsty = 0; dstx = abs(_ci.ptScreenPos.x - _rect.left); dsty = abs(_ci.ptScreenPos.y - _rect.top); if (GetIconInfo(icon, &ii)) { POINT pos; DrawIconEx(hdc, dstx, dsty, icon, 0, 0, 0, NULL, DI_NORMAL); DeleteObject(ii.hbmColor); DeleteObject(ii.hbmMask); } DestroyIcon(icon); } int record_desktop_gdi::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 { hdc_screen = GetWindowDC(NULL); 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); //must have CAPTUREBLT falg,otherwise some layered window can not be captured 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; } memset(&_ci, 0, sizeof(CURSORINFO)); _ci.cbSize = sizeof(CURSORINFO); 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 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; } #if 0 //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); #endif error = AE_NO; } while (0); if(hbm_mem) DeleteObject(hbm_mem); if(hdc_mem) DeleteObject(hdc_mem); if(hdc_screen) ReleaseDC(NULL, hdc_screen); return AE_NO; } void record_desktop_gdi::do_sleep(int64_t dur, int64_t pre, int64_t now) { int64_t delay = now - pre; dur = delay > dur ? max(0, dur - (delay - dur)) : (dur + dur - delay); //al_debug("%lld", delay); if(dur) av_usleep(dur); } void record_desktop_gdi::record_func() { AVFrame *frame = av_frame_alloc(); int64_t pre_pts = 0; int64_t dur = AV_TIME_BASE / _fps; int ret = AE_NO; while (_running) { ret = do_record(); if (ret != AE_NO) { if (_on_error) _on_error(ret); break; } frame->pts = av_gettime_relative(); frame->pkt_dts = frame->pts; frame->width = _width; frame->height = _height; frame->format = AV_PIX_FMT_BGRA; frame->pict_type = AV_PICTURE_TYPE_I; frame->pkt_size = _width * _height * 4; av_image_fill_arrays(frame->data, frame->linesize, _buffer, AV_PIX_FMT_BGRA, _width, _height, 1 ); if (_on_data) _on_data(frame); do_sleep(dur, pre_pts, frame->pts); pre_pts = frame->pts; } av_frame_free(&frame); } }