record_desktop_mag.cpp 14 KB

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