record_desktop_mag.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. #include "record_desktop_mag.h"
  2. #include "system_lib.h"
  3. #include "error_define.h"
  4. #include "log_helper.h"
  5. namespace am {
  6. namespace {
  7. // kMagnifierWindowClass has to be "Magnifier" according to the Magnification
  8. // API. The other strings can be anything.
  9. static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost";
  10. static wchar_t kHostWindowName[] = L"MagnifierHost";
  11. static wchar_t kMagnifierWindowClass[] = L"Magnifier";
  12. static wchar_t kMagnifierWindowName[] = L"MagnifierWindow";
  13. DWORD GetTlsIndex() {
  14. static const DWORD tls_index = TlsAlloc();
  15. return tls_index;
  16. }
  17. } // namespace
  18. BOOL __stdcall record_desktop_mag::on_mag_scaling_callback(
  19. HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata,
  20. MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty) {
  21. record_desktop_mag *owner =
  22. reinterpret_cast<record_desktop_mag *>(TlsGetValue(GetTlsIndex()));
  23. TlsSetValue(GetTlsIndex(), nullptr);
  24. owner->on_mag_data(srcdata, srcheader);
  25. return TRUE;
  26. }
  27. record_desktop_mag::record_desktop_mag() {}
  28. record_desktop_mag::~record_desktop_mag() {}
  29. int record_desktop_mag::init(const RECORD_DESKTOP_RECT &rect, const int fps) {
  30. if (_inited == true) {
  31. return AE_NO;
  32. }
  33. _fps = fps;
  34. _rect = rect;
  35. _width = rect.right - rect.left;
  36. _height = rect.bottom - rect.top;
  37. _start_time = av_gettime_relative();
  38. _time_base = {1, AV_TIME_BASE};
  39. _pixel_fmt = AV_PIX_FMT_BGRA;
  40. _inited = true;
  41. return AE_NO;
  42. }
  43. int record_desktop_mag::start() {
  44. if (_running == true) {
  45. al_warn("record desktop gdi is already running");
  46. return AE_NO;
  47. }
  48. if (_inited == false) {
  49. return AE_NEED_INIT;
  50. }
  51. _running = true;
  52. _thread = std::thread(std::bind(&record_desktop_mag::record_func, this));
  53. return AE_NO;
  54. }
  55. int record_desktop_mag::pause() {
  56. _paused = true;
  57. return AE_NO;
  58. }
  59. int record_desktop_mag::resume() {
  60. _paused = false;
  61. return AE_NO;
  62. }
  63. int record_desktop_mag::stop() {
  64. _running = false;
  65. if (_thread.joinable())
  66. _thread.join();
  67. return AE_NO;
  68. }
  69. void record_desktop_mag::clean_up() {
  70. // DestroyWindow must be called before MagUninitialize. _magnifier_window is
  71. // destroyed automatically when _host_window is destroyed.
  72. if (_host_window)
  73. DestroyWindow(_host_window);
  74. if (_magnifier_initialized)
  75. _mag_uninitialize_func();
  76. if (_mag_lib_handle)
  77. free_system_library(_mag_lib_handle);
  78. if (_desktop_dc)
  79. ReleaseDC(NULL, _desktop_dc);
  80. _inited = false;
  81. }
  82. void record_desktop_mag::record_func() {
  83. int64_t pre_pts = 0;
  84. int64_t dur = AV_TIME_BASE / _fps;
  85. int ret = AE_NO;
  86. // must call this in a new thread, otherwise SetWindowPos will stuck before
  87. // capture
  88. if (!do_mag_initialize()) {
  89. al_info("Failed to initialize ScreenCapturerWinMagnifier.");
  90. if (_on_error)
  91. _on_error(AE_NEED_INIT);
  92. return;
  93. }
  94. while (_running) {
  95. ret = do_mag_record();
  96. if (ret != AE_NO) {
  97. if (_on_error)
  98. _on_error(ret);
  99. break;
  100. }
  101. do_sleep(dur, pre_pts, _current_pts);
  102. pre_pts = _current_pts;
  103. }
  104. }
  105. void record_desktop_mag::do_sleep(int64_t dur, int64_t pre, int64_t now) {
  106. int64_t delay = now - pre;
  107. dur = delay > dur ? max(0, dur - (delay - dur)) : (dur + dur - delay);
  108. // al_debug("%lld", delay);
  109. if (dur)
  110. av_usleep(dur);
  111. }
  112. bool record_desktop_mag::do_mag_initialize() {
  113. #if 0 // we can handle crash
  114. if (GetSystemMetrics(SM_CMONITORS) != 1) {
  115. // Do not try to use the magnifier in multi-screen setup (where the API
  116. // crashes sometimes).
  117. al_info("Magnifier capturer cannot work on multi-screen system.");
  118. return false;
  119. }
  120. #endif
  121. _desktop_dc = GetDC(nullptr);
  122. _mag_lib_handle = load_system_library("Magnification.dll");
  123. if (!_mag_lib_handle)
  124. return false;
  125. // Initialize Magnification API function pointers.
  126. _mag_initialize_func = reinterpret_cast<MagInitializeFunc>(
  127. GetProcAddress(_mag_lib_handle, "MagInitialize"));
  128. _mag_uninitialize_func = reinterpret_cast<MagUninitializeFunc>(
  129. GetProcAddress(_mag_lib_handle, "MagUninitialize"));
  130. _mag_set_window_source_func = reinterpret_cast<MagSetWindowSourceFunc>(
  131. GetProcAddress(_mag_lib_handle, "MagSetWindowSource"));
  132. _mag_set_window_filter_list_func =
  133. reinterpret_cast<MagSetWindowFilterListFunc>(
  134. GetProcAddress(_mag_lib_handle, "MagSetWindowFilterList"));
  135. _mag_set_image_scaling_callback_func =
  136. reinterpret_cast<MagSetImageScalingCallbackFunc>(
  137. GetProcAddress(_mag_lib_handle, "MagSetImageScalingCallback"));
  138. if (!_mag_initialize_func || !_mag_uninitialize_func ||
  139. !_mag_set_window_source_func || !_mag_set_window_filter_list_func ||
  140. !_mag_set_image_scaling_callback_func) {
  141. al_info(
  142. "Failed to initialize ScreenCapturerWinMagnifier: library functions "
  143. "missing.");
  144. return false;
  145. }
  146. BOOL result = _mag_initialize_func();
  147. if (!result) {
  148. al_info("Failed to initialize ScreenCapturerWinMagnifier: error from "
  149. "MagInitialize %ld",
  150. GetLastError());
  151. return false;
  152. }
  153. HMODULE hInstance = nullptr;
  154. result =
  155. GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
  156. GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
  157. reinterpret_cast<char *>(&DefWindowProc), &hInstance);
  158. if (!result) {
  159. _mag_uninitialize_func();
  160. al_info("Failed to initialize ScreenCapturerWinMagnifier: "
  161. "error from GetModulehandleExA %ld",
  162. GetLastError());
  163. return false;
  164. }
  165. // Register the host window class. See the MSDN documentation of the
  166. // Magnification API for more infomation.
  167. WNDCLASSEXW wcex = {};
  168. wcex.cbSize = sizeof(WNDCLASSEX);
  169. wcex.lpfnWndProc = &DefWindowProc;
  170. wcex.hInstance = hInstance;
  171. wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
  172. wcex.lpszClassName = kMagnifierHostClass;
  173. // Ignore the error which may happen when the class is already registered.
  174. RegisterClassExW(&wcex);
  175. // Create the host window.
  176. _host_window =
  177. CreateWindowExW(WS_EX_LAYERED, kMagnifierHostClass, kHostWindowName, 0, 0,
  178. 0, 0, 0, nullptr, nullptr, hInstance, nullptr);
  179. if (!_host_window) {
  180. _mag_uninitialize_func();
  181. al_info("Failed to initialize ScreenCapturerWinMagnifier: "
  182. "error from creating host window %ld",
  183. GetLastError());
  184. return false;
  185. }
  186. // Create the magnifier control.
  187. _magnifier_window = CreateWindowW(kMagnifierWindowClass, kMagnifierWindowName,
  188. WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
  189. _host_window, nullptr, hInstance, nullptr);
  190. if (!_magnifier_window) {
  191. _mag_uninitialize_func();
  192. al_info("Failed to initialize ScreenCapturerWinMagnifier: "
  193. "error from creating magnifier window %ld",
  194. GetLastError());
  195. return false;
  196. }
  197. // Hide the host window.
  198. ShowWindow(_host_window, SW_HIDE);
  199. // Set the scaling callback to receive captured image.
  200. result = _mag_set_image_scaling_callback_func(
  201. _magnifier_window, &record_desktop_mag::on_mag_scaling_callback);
  202. if (!result) {
  203. _mag_uninitialize_func();
  204. al_info("Failed to initialize ScreenCapturerWinMagnifier: "
  205. "error from MagSetImageScalingCallback %ld",
  206. GetLastError());
  207. return false;
  208. }
  209. if (_excluded_window) {
  210. result = _mag_set_window_filter_list_func(
  211. _magnifier_window, MW_FILTERMODE_EXCLUDE, 1, &_excluded_window);
  212. if (!result) {
  213. _mag_uninitialize_func();
  214. al_warn("Failed to initialize ScreenCapturerWinMagnifier: "
  215. "error from MagSetWindowFilterList %ld",
  216. GetLastError());
  217. return false;
  218. }
  219. }
  220. _magnifier_initialized = true;
  221. return true;
  222. }
  223. int record_desktop_mag::do_mag_record() {
  224. if (!_magnifier_initialized) {
  225. al_error("Magnifier initialization failed.");
  226. return AE_NEED_INIT;
  227. }
  228. auto capture_image = [&](const RECORD_DESKTOP_RECT &rect) {
  229. // Set the magnifier control to cover the captured rect. The content of the
  230. // magnifier control will be the captured image.
  231. BOOL result =
  232. SetWindowPos(_magnifier_window, NULL, rect.left, rect.top,
  233. rect.right - rect.left, rect.bottom - rect.top, 0);
  234. if (!result) {
  235. al_error("Failed to call SetWindowPos: %ld. Rect = {%d, %d, %d, %d}",
  236. GetLastError(), rect.left, rect.top, rect.right, rect.bottom);
  237. return false;
  238. }
  239. _magnifier_capture_succeeded = false;
  240. RECT native_rect = {rect.left, rect.top, rect.right, rect.bottom};
  241. TlsSetValue(GetTlsIndex(), this);
  242. // on_mag_data will be called via on_mag_scaling_callback and fill in the
  243. // frame before _mag_set_window_source_func returns.
  244. DWORD exception = 0;
  245. result =
  246. seh_mag_set_window_source(_magnifier_window, native_rect, exception);
  247. if (!result) {
  248. al_error("Failed to call MagSetWindowSource: %ld Exception: %ld. Rect = {%d, %d, %d, %d}",
  249. GetLastError(), exception, rect.left, rect.top, rect.right,
  250. rect.bottom);
  251. return false;
  252. }
  253. return _magnifier_capture_succeeded;
  254. };
  255. #if 0
  256. // Switch to the desktop receiving user input if different from the current
  257. // one.
  258. std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
  259. if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
  260. // Release GDI resources otherwise SetThreadDesktop will fail.
  261. if (_desktop_dc) {
  262. ReleaseDC(NULL, _desktop_dc);
  263. _desktop_dc = NULL;
  264. }
  265. // If SetThreadDesktop() fails, the thread is still assigned a desktop.
  266. // So we can continue capture screen bits, just from the wrong desktop.
  267. desktop_.SetThreadDesktop(input_desktop.release());
  268. }
  269. DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
  270. #endif
  271. // capture_image may fail in some situations, e.g. windows8 metro mode. So
  272. // defer to the fallback capturer if magnifier capturer did not work.
  273. if (!capture_image(_rect)) {
  274. al_error("Magnifier capturer failed to capture a frame.");
  275. return AE_ERROR;
  276. }
  277. return AE_NO;
  278. }
  279. void record_desktop_mag::set_exclude(HWND excluded_window) {
  280. _excluded_window = excluded_window;
  281. if (_excluded_window && _magnifier_initialized) {
  282. _mag_set_window_filter_list_func(_magnifier_window, MW_FILTERMODE_EXCLUDE,
  283. 1, &_excluded_window);
  284. }
  285. }
  286. void record_desktop_mag::on_mag_data(void *data, const MAGIMAGEHEADER &header) {
  287. const int kBytesPerPixel = 4;
  288. int captured_bytes_per_pixel = header.cbSize / header.width / header.height;
  289. if (header.format != GUID_WICPixelFormat32bppRGBA ||
  290. header.width != static_cast<UINT>(_width) ||
  291. header.height != static_cast<UINT>(_height) ||
  292. header.stride != static_cast<UINT>(kBytesPerPixel * _width) ||
  293. captured_bytes_per_pixel != kBytesPerPixel) {
  294. al_warn("Output format does not match the captured format: width = %d, "
  295. "height = %d, stride= %d, bpp = %d, pixel format RGBA ? %d.",
  296. header.width, header.height, header.stride,
  297. captured_bytes_per_pixel,
  298. (header.format == GUID_WICPixelFormat32bppRGBA));
  299. return;
  300. }
  301. _current_pts = av_gettime_relative();
  302. AVFrame *frame = av_frame_alloc();
  303. frame->pts = _current_pts;
  304. frame->pkt_dts = frame->pts;
  305. frame->width = _width;
  306. frame->height = _height;
  307. frame->format = AV_PIX_FMT_BGRA;
  308. frame->pict_type = AV_PICTURE_TYPE_I;
  309. frame->pkt_size = _width * _height * 4;
  310. av_image_fill_arrays(frame->data, frame->linesize,
  311. reinterpret_cast<uint8_t *>(data), AV_PIX_FMT_BGRA,
  312. _width, _height, 1);
  313. if (_on_data)
  314. _on_data(frame);
  315. av_frame_free(&frame);
  316. _magnifier_capture_succeeded = true;
  317. }
  318. bool record_desktop_mag::seh_mag_set_window_source(HWND hwnd, RECT rect,
  319. DWORD &exception) {
  320. if (!_mag_set_window_source_func)
  321. return false;
  322. __try {
  323. return _mag_set_window_source_func(hwnd, rect);
  324. } __except (EXCEPTION_EXECUTE_HANDLER) {
  325. exception = ::GetExceptionCode();
  326. return false;
  327. }
  328. return false;
  329. }
  330. } // namespace am