plugin_win_dd_producer.cxx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. /* Copyright (C) 2015 Mamadou DIOP
  2. * Copyright (C) 2015 Doubango Telecom <http://www.doubango.org>
  3. *
  4. * This file is part of Open Source Doubango Framework.
  5. *
  6. * DOUBANGO is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * DOUBANGO is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with DOUBANGO.
  18. */
  19. // Microsoft Duplication Desktop producer for Win8+: https://msdn.microsoft.com/en-us/library/windows/desktop/hh404487(v=VS.85).aspx
  20. // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
  21. // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
  22. // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
  23. // PARTICULAR PURPOSE.
  24. //
  25. // Copyright (c) Microsoft Corporation. All rights reserved
  26. #include "plugin_win_dd_config.h"
  27. #include "tinymedia/tmedia_defaults.h"
  28. #include "tinymedia/tmedia_producer.h"
  29. #include "tsk_time.h"
  30. #include "tsk_string.h"
  31. #include "tsk_thread.h"
  32. #include "tsk_safeobj.h"
  33. #include "tsk_debug.h"
  34. #include "internals/DisplayManager.h"
  35. #include "internals/DuplicationManager.h"
  36. #include "internals/OutputManager.h"
  37. #include "internals/ThreadManager.h"
  38. #include <Windows.h>
  39. #define DD_DEBUG_INFO(FMT, ...) TSK_DEBUG_INFO("[DESKTOP DUPLICATION] " FMT, ##__VA_ARGS__)
  40. #define DD_DEBUG_WARN(FMT, ...) TSK_DEBUG_WARN("[DESKTOP DUPLICATION] " FMT, ##__VA_ARGS__)
  41. #define DD_DEBUG_ERROR(FMT, ...) TSK_DEBUG_ERROR("[DESKTOP DUPLICATION] " FMT, ##__VA_ARGS__)
  42. #define DD_DEBUG_FATAL(FMT, ...) TSK_DEBUG_FATAL("[DESKTOP DUPLICATION] " FMT, ##__VA_ARGS__)
  43. #define DD_CHECK_HR(x) { HRESULT __hr__ = (x); if (FAILED(__hr__)) { DD_DEBUG_ERROR("Operation Failed (%08x)", __hr__); goto bail; } }
  44. #if !defined(DD_DDPROC_THREAD_TIMEOUT)
  45. # define DD_DDPROC_THREAD_TIMEOUT 1500
  46. #endif
  47. //
  48. // plugin_win_dd_producer_t
  49. //
  50. typedef struct plugin_win_dd_producer_s {
  51. TMEDIA_DECLARE_PRODUCER;
  52. bool bStarted, bPrepared, bMuted, bWindowHooked, bThreadTerminationDelayed;
  53. tsk_thread_handle_t* ppTread[1];
  54. OUTPUTMANAGER *pOutMgr;
  55. THREADMANAGER *pThreadMgr;
  56. // Window handles
  57. HWND hwndPreview;
  58. WNDPROC wndPreviewProc;
  59. HWND hwndSrc;
  60. // Synchronization
  61. HANDLE hlUnexpectedErrorEvent;
  62. HANDLE hlExpectedErrorEvent;
  63. HANDLE hlOcclutionEvent;
  64. HANDLE hlTerminateThreadsEvent;
  65. HCURSOR hcCursor;
  66. }
  67. plugin_win_dd_producer_t;
  68. // Forward declarations
  69. static int _plugin_win_dd_producer_unprepare(plugin_win_dd_producer_t* pSelf, bool bCleanup = false);
  70. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
  71. static HRESULT HookWindow(struct plugin_win_dd_producer_s *pSelf, HWND hWnd);
  72. static HRESULT UnhookWindow(struct plugin_win_dd_producer_s *pSelf);
  73. DWORD WINAPI DDProc(_In_ void* Param);
  74. void DisplayMsg(_In_ LPCWSTR Str, _In_ LPCWSTR Title, HRESULT hr);
  75. _Post_satisfies_(return != DUPL_RETURN_SUCCESS)
  76. DUPL_RETURN ProcessFailure(_In_opt_ ID3D11Device* Device, _In_ LPCWSTR Str, _In_ LPCWSTR Title, HRESULT hr, _In_opt_z_ HRESULT* ExpectedErrors);
  77. static void* TSK_STDCALL DDThread(void *pArg);
  78. //
  79. // Class for progressive waits
  80. //
  81. typedef struct {
  82. UINT WaitTime;
  83. UINT WaitCount;
  84. } WAIT_BAND;
  85. #define WAIT_BAND_COUNT 3
  86. #define WAIT_BAND_STOP 0
  87. class DYNAMIC_WAIT
  88. {
  89. public:
  90. DYNAMIC_WAIT();
  91. ~DYNAMIC_WAIT();
  92. void Wait();
  93. private:
  94. static const WAIT_BAND m_WaitBands[WAIT_BAND_COUNT];
  95. // Period in seconds that a new wait call is considered part of the same wait sequence
  96. static const UINT m_WaitSequenceTimeInSeconds = 2;
  97. UINT m_CurrentWaitBandIdx;
  98. UINT m_WaitCountInCurrentBand;
  99. LARGE_INTEGER m_QPCFrequency;
  100. LARGE_INTEGER m_LastWakeUpTime;
  101. BOOL m_QPCValid;
  102. };
  103. const WAIT_BAND DYNAMIC_WAIT::m_WaitBands[WAIT_BAND_COUNT] = {
  104. { 250, 20 },
  105. { 2000, 60 },
  106. { 5000, WAIT_BAND_STOP } // Never move past this band
  107. };
  108. /* ============ Video DD Producer Interface ================= */
  109. static int plugin_win_dd_producer_set(tmedia_producer_t *p_self, const tmedia_param_t* pc_param)
  110. {
  111. int ret = -1;
  112. plugin_win_dd_producer_t* p_dd = (plugin_win_dd_producer_t*)p_self;
  113. if (!p_dd || !pc_param) {
  114. DD_DEBUG_ERROR("Invalid parameter");
  115. return -1;
  116. }
  117. if (pc_param->value_type == tmedia_pvt_int64) {
  118. if (tsk_striequals(pc_param->key, "local-hwnd") || tsk_striequals(pc_param->key, "preview-hwnd")) {
  119. HWND hwnd = (HWND)*((int64_t*)pc_param->value);
  120. ret = SUCCEEDED(HookWindow(p_dd, hwnd)) ? 0 : -1;
  121. }
  122. else if (tsk_striequals(pc_param->key, "src-hwnd")) {
  123. p_dd->hwndSrc = (HWND)*((int64_t*)pc_param->value);
  124. ret = 0;
  125. }
  126. }
  127. else if (pc_param->value_type == tmedia_pvt_int32) {
  128. if (tsk_striequals(pc_param->key, "mute")) {
  129. p_dd->bMuted = (TSK_TO_INT32((uint8_t*)pc_param->value) != 0);
  130. ret = 0;
  131. }
  132. }
  133. return ret;
  134. }
  135. static int plugin_win_dd_producer_prepare(tmedia_producer_t* self, const tmedia_codec_t* codec)
  136. {
  137. plugin_win_dd_producer_t* pSelf = (plugin_win_dd_producer_t*)self;
  138. HRESULT hr = S_OK;
  139. if (!pSelf || !codec && codec->plugin) {
  140. DD_DEBUG_ERROR("Invalid parameter");
  141. DD_CHECK_HR(hr = E_UNEXPECTED);
  142. }
  143. if (pSelf->bPrepared) {
  144. DD_DEBUG_WARN("DD video producer already prepared");
  145. DD_CHECK_HR(hr = E_UNEXPECTED);
  146. }
  147. if (pSelf->bThreadTerminationDelayed) {
  148. DD_DEBUG_INFO("Thread termination was delayed ...cleanup now");
  149. if (_plugin_win_dd_producer_unprepare(pSelf, true/*cleanup?*/) != 0) {
  150. DD_CHECK_HR(hr = E_UNEXPECTED);
  151. }
  152. }
  153. TMEDIA_PRODUCER(pSelf)->video.fps = TMEDIA_CODEC_VIDEO(codec)->out.fps;
  154. TMEDIA_PRODUCER(pSelf)->video.width = TMEDIA_CODEC_VIDEO(codec)->out.width;
  155. TMEDIA_PRODUCER(pSelf)->video.height = TMEDIA_CODEC_VIDEO(codec)->out.height;
  156. TMEDIA_PRODUCER(pSelf)->encoder.codec_id = tmedia_codec_id_none; // means RAW frames as input
  157. DD_DEBUG_INFO("DD video producer: fps=%d, width=%d, height=%d",
  158. TMEDIA_PRODUCER(pSelf)->video.fps,
  159. TMEDIA_PRODUCER(pSelf)->video.width,
  160. TMEDIA_PRODUCER(pSelf)->video.height);
  161. // Event used by the threads to signal an unexpected error and we want to quit the app
  162. if (!pSelf->hlUnexpectedErrorEvent && !(pSelf->hlUnexpectedErrorEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) {
  163. ProcessFailure(nullptr, L"UnexpectedErrorEvent creation failed", L"Error", E_UNEXPECTED);
  164. DD_CHECK_HR(hr = E_UNEXPECTED);
  165. }
  166. // Event for when a thread encounters an expected error
  167. if (!pSelf->hlExpectedErrorEvent && !(pSelf->hlExpectedErrorEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) {
  168. ProcessFailure(nullptr, L"ExpectedErrorEvent creation failed", L"Error", E_UNEXPECTED);
  169. DD_CHECK_HR(hr = E_UNEXPECTED);
  170. }
  171. // Event for Occlution
  172. if (!pSelf->hlOcclutionEvent && !(pSelf->hlOcclutionEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) {
  173. ProcessFailure(nullptr, L"OcclutionEvent creation failed", L"Error", E_UNEXPECTED);
  174. DD_CHECK_HR(hr = E_UNEXPECTED);
  175. }
  176. // Event to tell spawned threads to quit
  177. if (!pSelf->hlTerminateThreadsEvent && !(pSelf->hlTerminateThreadsEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr))) {
  178. ProcessFailure(nullptr, L"TerminateThreadsEvent creation failed", L"Error", E_UNEXPECTED);
  179. DD_CHECK_HR(hr = E_UNEXPECTED);
  180. }
  181. // Load simple cursor
  182. if (!pSelf->hcCursor && !(pSelf->hcCursor = LoadCursor(nullptr, IDC_ARROW))) {
  183. ProcessFailure(nullptr, L"Cursor load failed", L"Error", E_UNEXPECTED);
  184. DD_CHECK_HR(hr = E_UNEXPECTED);
  185. }
  186. if (!pSelf->pOutMgr && !(pSelf->pOutMgr = new OUTPUTMANAGER())) {
  187. ProcessFailure(nullptr, L"Out manager allocation failed", L"Error", E_OUTOFMEMORY);
  188. DD_CHECK_HR(hr = E_OUTOFMEMORY);
  189. }
  190. if (!pSelf->pThreadMgr && !(pSelf->pThreadMgr = new THREADMANAGER())) {
  191. ProcessFailure(nullptr, L"Thread managed allocation failed", L"Error", E_OUTOFMEMORY);
  192. DD_CHECK_HR(hr = E_OUTOFMEMORY);
  193. }
  194. bail:
  195. pSelf->bPrepared = SUCCEEDED(hr);
  196. return SUCCEEDED(hr) ? 0 : -1;
  197. }
  198. static int plugin_win_dd_producer_start(tmedia_producer_t* self)
  199. {
  200. plugin_win_dd_producer_t* pSelf = (plugin_win_dd_producer_t*)self;
  201. HRESULT hr = S_OK;
  202. if (!pSelf) {
  203. DD_DEBUG_ERROR("Invalid parameter");
  204. return -1;
  205. }
  206. if (pSelf->bStarted) {
  207. DD_DEBUG_INFO("Producer already started");
  208. goto bail;
  209. }
  210. if (!pSelf->bPrepared) {
  211. DD_DEBUG_ERROR("Producer not prepared");
  212. DD_CHECK_HR(hr = E_UNEXPECTED);
  213. }
  214. DD_CHECK_HR(hr = HookWindow(pSelf, pSelf->hwndPreview));
  215. // Start asynchronous watcher thread
  216. pSelf->bStarted = true;
  217. int ret = tsk_thread_create(&pSelf->ppTread[0], DDThread, pSelf);
  218. if (ret != 0) {
  219. TSK_DEBUG_ERROR("Failed to create thread");
  220. pSelf->bStarted = false;
  221. if (pSelf->ppTread[0]) {
  222. tsk_thread_join(&pSelf->ppTread[0]);
  223. }
  224. DD_CHECK_HR(hr = E_UNEXPECTED);
  225. }
  226. bail:
  227. if (FAILED(hr)) {
  228. UnhookWindow(pSelf);
  229. return -1;
  230. }
  231. pSelf->bStarted = true;
  232. return 0;
  233. }
  234. static int plugin_win_dd_producer_pause(tmedia_producer_t* self)
  235. {
  236. plugin_win_dd_producer_t* pSelf = (plugin_win_dd_producer_t*)self;
  237. if (!pSelf) {
  238. DD_DEBUG_ERROR("Invalid parameter");
  239. return -1;
  240. }
  241. if (!pSelf->bStarted) {
  242. DD_DEBUG_INFO("MF video producer not started");
  243. }
  244. return 0;
  245. }
  246. static int plugin_win_dd_producer_stop(tmedia_producer_t* self)
  247. {
  248. plugin_win_dd_producer_t* pSelf = (plugin_win_dd_producer_t*)self;
  249. if (!pSelf) {
  250. DD_DEBUG_ERROR("Invalid parameter");
  251. return -1;
  252. }
  253. pSelf->bStarted = false;
  254. UnhookWindow(pSelf);
  255. if (pSelf->hlTerminateThreadsEvent) {
  256. SetEvent(pSelf->hlTerminateThreadsEvent);
  257. }
  258. if (pSelf->ppTread[0]) {
  259. tsk_thread_join(&pSelf->ppTread[0]);
  260. }
  261. // next start() will be called after prepare()
  262. int ret = _plugin_win_dd_producer_unprepare(pSelf);
  263. return ret;
  264. }
  265. static int _plugin_win_dd_producer_unprepare(plugin_win_dd_producer_t* pSelf, bool bCleanup /*= false*/)
  266. {
  267. HRESULT hr = S_OK;
  268. if (!pSelf) {
  269. DD_DEBUG_ERROR("Invalid parameter");
  270. return -1;
  271. }
  272. if (pSelf->bStarted) {
  273. DD_CHECK_HR(hr = E_UNEXPECTED);
  274. }
  275. pSelf->bThreadTerminationDelayed = false;
  276. // Thread manager must be destroyed before the events and output manager
  277. if (pSelf->pThreadMgr) {
  278. // if we are cleaning the producer then all threads must exit only when all threads are destroyed
  279. // https://code.google.com/p/sincity/issues/detail?id=7
  280. if (pSelf->pThreadMgr->WaitForThreadTermination(bCleanup ? INFINITE : DD_DDPROC_THREAD_TIMEOUT) == true) {
  281. delete pSelf->pThreadMgr;
  282. pSelf->pThreadMgr = nullptr;
  283. }
  284. else {
  285. // Thread wait timedout
  286. DD_DEBUG_WARN("DDProc thread termination delayed");
  287. pSelf->bThreadTerminationDelayed = true;
  288. }
  289. }
  290. if (!pSelf->bThreadTerminationDelayed) {
  291. if (pSelf->hlUnexpectedErrorEvent) {
  292. CloseHandle(pSelf->hlUnexpectedErrorEvent);
  293. pSelf->hlUnexpectedErrorEvent = nullptr;
  294. }
  295. if (pSelf->hlExpectedErrorEvent) {
  296. CloseHandle(pSelf->hlExpectedErrorEvent);
  297. pSelf->hlExpectedErrorEvent = nullptr;
  298. }
  299. if (pSelf->hlOcclutionEvent) {
  300. CloseHandle(pSelf->hlOcclutionEvent);
  301. pSelf->hlOcclutionEvent = nullptr;
  302. }
  303. if (pSelf->hlTerminateThreadsEvent) {
  304. CloseHandle(pSelf->hlTerminateThreadsEvent);
  305. pSelf->hlTerminateThreadsEvent = nullptr;
  306. }
  307. if (pSelf->hcCursor) {
  308. DestroyCursor(pSelf->hcCursor);
  309. pSelf->hcCursor = nullptr;
  310. }
  311. if (pSelf->pOutMgr) {
  312. delete pSelf->pOutMgr;
  313. pSelf->pOutMgr = nullptr;
  314. }
  315. }
  316. pSelf->bPrepared = false;
  317. bail:
  318. return 0;
  319. }
  320. static HRESULT HookWindow(struct plugin_win_dd_producer_s *pSelf, HWND hWnd)
  321. {
  322. HRESULT hr = S_OK;
  323. DD_CHECK_HR(hr = UnhookWindow(pSelf));
  324. if ((pSelf->hwndPreview = hWnd)) {
  325. pSelf->wndPreviewProc = (WNDPROC)SetWindowLongPtr(pSelf->hwndPreview, GWLP_WNDPROC, (LONG_PTR)WndProc);
  326. if (!pSelf->wndPreviewProc) {
  327. DD_DEBUG_ERROR("HookWindowLongPtr() failed with errcode=%d", GetLastError());
  328. DD_CHECK_HR(hr = E_FAIL);
  329. }
  330. SetProp(pSelf->hwndPreview, L"Self", pSelf);
  331. pSelf->bWindowHooked = true;
  332. }
  333. bail:
  334. return S_OK;
  335. }
  336. static HRESULT UnhookWindow(struct plugin_win_dd_producer_s *pSelf)
  337. {
  338. if (pSelf->hwndPreview && pSelf->wndPreviewProc) {
  339. SetWindowLongPtr(pSelf->hwndPreview, GWLP_WNDPROC, (LONG_PTR)pSelf->wndPreviewProc);
  340. pSelf->wndPreviewProc = NULL;
  341. }
  342. if (pSelf->hwndPreview) {
  343. ::InvalidateRect(pSelf->hwndPreview, NULL, FALSE);
  344. }
  345. pSelf->bWindowHooked = false;
  346. return S_OK;
  347. }
  348. //
  349. // Windows Desktop Duplication producer object definition
  350. //
  351. /* constructor */
  352. static tsk_object_t* plugin_win_dd_producer_ctor(tsk_object_t * self, va_list * app)
  353. {
  354. plugin_win_dd_producer_t *pSelf = (plugin_win_dd_producer_t *)self;
  355. if (pSelf) {
  356. /* init base */
  357. tmedia_producer_init(TMEDIA_PRODUCER(pSelf));
  358. /* init self with default values*/
  359. TMEDIA_PRODUCER(pSelf)->encoder.codec_id = tmedia_codec_id_none; // means RAW frames as input
  360. TMEDIA_PRODUCER(pSelf)->video.chroma = tmedia_chroma_rgb32;
  361. TMEDIA_PRODUCER(pSelf)->video.fps = 15;
  362. TMEDIA_PRODUCER(pSelf)->video.width = 352;
  363. TMEDIA_PRODUCER(pSelf)->video.height = 288;
  364. DD_DEBUG_INFO("Create Microsoft Desktop Duplication producer");
  365. }
  366. return self;
  367. }
  368. /* destructor */
  369. static tsk_object_t* plugin_win_dd_producer_dtor(tsk_object_t * self)
  370. {
  371. plugin_win_dd_producer_t *pSelf = (plugin_win_dd_producer_t *)self;
  372. if (pSelf) {
  373. /* stop */
  374. if (pSelf->bStarted) {
  375. plugin_win_dd_producer_stop(TMEDIA_PRODUCER(pSelf));
  376. }
  377. /* deinit base */
  378. tmedia_producer_deinit(TMEDIA_PRODUCER(pSelf));
  379. /* deinit self */
  380. _plugin_win_dd_producer_unprepare(pSelf, true/*cleanup*/);
  381. DD_DEBUG_INFO("*** WinDD producer destroyed ***");
  382. }
  383. return self;
  384. }
  385. /* object definition */
  386. static const tsk_object_def_t plugin_win_dd_producer_def_s = {
  387. sizeof(plugin_win_dd_producer_t),
  388. plugin_win_dd_producer_ctor,
  389. plugin_win_dd_producer_dtor,
  390. tsk_null,
  391. };
  392. /* plugin definition*/
  393. static const tmedia_producer_plugin_def_t plugin_win_dd_producer_plugin_def_s = {
  394. &plugin_win_dd_producer_def_s,
  395. tmedia_bfcp_video,
  396. "Microsoft Windows Desktop Duplication producer (Video)",
  397. plugin_win_dd_producer_set,
  398. plugin_win_dd_producer_prepare,
  399. plugin_win_dd_producer_start,
  400. plugin_win_dd_producer_pause,
  401. plugin_win_dd_producer_stop
  402. };
  403. const tmedia_producer_plugin_def_t *plugin_win_dd_producer_plugin_def_t = &plugin_win_dd_producer_plugin_def_s;
  404. // Below are lists of errors expect from Dxgi API calls when a transition event like mode change, PnpStop, PnpStart
  405. // desktop switch, TDR or session disconnect/reconnect. In all these cases we want the application to clean up the threads that process
  406. // the desktop updates and attempt to recreate them.
  407. // If we get an error that is not on the appropriate list then we exit the application
  408. // These are the errors we expect from general Dxgi API due to a transition
  409. HRESULT SystemTransitionsExpectedErrors[] = {
  410. DXGI_ERROR_DEVICE_REMOVED,
  411. DXGI_ERROR_ACCESS_LOST,
  412. static_cast<HRESULT>(WAIT_ABANDONED),
  413. S_OK // Terminate list with zero valued HRESULT
  414. };
  415. // These are the errors we expect from IDXGIOutput1::DuplicateOutput due to a transition
  416. HRESULT CreateDuplicationExpectedErrors[] = {
  417. DXGI_ERROR_DEVICE_REMOVED,
  418. static_cast<HRESULT>(E_ACCESSDENIED),
  419. DXGI_ERROR_UNSUPPORTED,
  420. DXGI_ERROR_SESSION_DISCONNECTED,
  421. S_OK // Terminate list with zero valued HRESULT
  422. };
  423. // These are the errors we expect from IDXGIOutputDuplication methods due to a transition
  424. HRESULT FrameInfoExpectedErrors[] = {
  425. DXGI_ERROR_DEVICE_REMOVED,
  426. DXGI_ERROR_ACCESS_LOST,
  427. S_OK // Terminate list with zero valued HRESULT
  428. };
  429. // These are the errors we expect from IDXGIAdapter::EnumOutputs methods due to outputs becoming stale during a transition
  430. HRESULT EnumOutputsExpectedErrors[] = {
  431. DXGI_ERROR_NOT_FOUND,
  432. S_OK // Terminate list with zero valued HRESULT
  433. };
  434. _Post_satisfies_(return != DUPL_RETURN_SUCCESS)
  435. DUPL_RETURN ProcessFailure(_In_opt_ ID3D11Device* Device, _In_ LPCWSTR Str, _In_ LPCWSTR Title, HRESULT hr, _In_opt_z_ HRESULT* ExpectedErrors)
  436. {
  437. HRESULT TranslatedHr;
  438. // On an error check if the DX device is lost
  439. if (Device) {
  440. HRESULT DeviceRemovedReason = Device->GetDeviceRemovedReason();
  441. switch (DeviceRemovedReason) {
  442. case DXGI_ERROR_DEVICE_REMOVED:
  443. case DXGI_ERROR_DEVICE_RESET:
  444. case static_cast<HRESULT>(E_OUTOFMEMORY) : {
  445. // Our device has been stopped due to an external event on the GPU so map them all to
  446. // device removed and continue processing the condition
  447. TranslatedHr = DXGI_ERROR_DEVICE_REMOVED;
  448. break;
  449. }
  450. case S_OK: {
  451. // Device is not removed so use original error
  452. TranslatedHr = hr;
  453. break;
  454. }
  455. default: {
  456. // Device is removed but not a error we want to remap
  457. TranslatedHr = DeviceRemovedReason;
  458. }
  459. }
  460. }
  461. else {
  462. TranslatedHr = hr;
  463. }
  464. // Check if this error was expected or not
  465. if (ExpectedErrors) {
  466. HRESULT* CurrentResult = ExpectedErrors;
  467. while (*CurrentResult != S_OK) {
  468. if (*(CurrentResult++) == TranslatedHr) {
  469. return DUPL_RETURN_ERROR_EXPECTED;
  470. }
  471. }
  472. }
  473. // Error was not expected so display the message box
  474. DisplayMsg(Str, Title, TranslatedHr);
  475. return DUPL_RETURN_ERROR_UNEXPECTED;
  476. }
  477. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  478. {
  479. switch (message) {
  480. case WM_DESTROY: {
  481. PostQuitMessage(0);
  482. break;
  483. }
  484. case WM_SIZE: {
  485. // Tell output manager that window size has changed
  486. plugin_win_dd_producer_t* pSelf = static_cast<plugin_win_dd_producer_t*>(GetProp(hWnd, L"Self"));
  487. if (pSelf && pSelf->pOutMgr) {
  488. pSelf->pOutMgr->WindowResize();
  489. }
  490. break;
  491. }
  492. case OCCLUSION_STATUS_MSG: {
  493. plugin_win_dd_producer_t* pSelf = static_cast<plugin_win_dd_producer_t*>(GetProp(hWnd, L"Self"));
  494. if (pSelf && pSelf->hlOcclutionEvent) {
  495. SetEvent(pSelf->hlOcclutionEvent);
  496. }
  497. break;
  498. }
  499. default:
  500. return DefWindowProc(hWnd, message, wParam, lParam);
  501. }
  502. return 0;
  503. }
  504. //
  505. // Entry point for new duplication threads
  506. //
  507. DWORD WINAPI DDProc(_In_ void* Param)
  508. {
  509. DD_DEBUG_INFO("DDProc (producer) - ENTER");
  510. // Classes
  511. DISPLAYMANAGER DispMgr;
  512. DUPLICATIONMANAGER DuplMgr;
  513. // D3D objects
  514. ID3D11Texture2D* SharedSurf = nullptr;
  515. IDXGIKeyedMutex* KeyMutex = nullptr;
  516. // Data passed in from thread creation
  517. THREAD_DATA* TData = reinterpret_cast<THREAD_DATA*>(Param);
  518. // Get desktop
  519. DUPL_RETURN Ret;
  520. HDESK CurrentDesktop = nullptr;
  521. CurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
  522. if (!CurrentDesktop) {
  523. // We do not have access to the desktop so request a retry
  524. SetEvent(TData->ExpectedErrorEvent);
  525. Ret = DUPL_RETURN_ERROR_EXPECTED;
  526. goto Exit;
  527. }
  528. // Attach desktop to this thread
  529. bool DesktopAttached = SetThreadDesktop(CurrentDesktop) != 0;
  530. CloseDesktop(CurrentDesktop);
  531. CurrentDesktop = nullptr;
  532. if (!DesktopAttached) {
  533. // We do not have access to the desktop so request a retry
  534. Ret = DUPL_RETURN_ERROR_EXPECTED;
  535. goto Exit;
  536. }
  537. // New display manager
  538. DispMgr.InitD3D(&TData->DxRes);
  539. // FPS manager
  540. uint64_t TimeNow, TimeLastFrame = 0;
  541. const uint64_t TimeFrameDuration = 1000 / TData->Producer->video.fps;
  542. // Obtain handle to sync shared Surface
  543. HRESULT hr = TData->DxRes.Device->OpenSharedResource(TData->TexSharedHandle, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&SharedSurf));
  544. if (FAILED(hr)) {
  545. Ret = ProcessFailure(TData->DxRes.Device, L"Opening shared texture failed", L"Error", hr, SystemTransitionsExpectedErrors);
  546. goto Exit;
  547. }
  548. hr = SharedSurf->QueryInterface(__uuidof(IDXGIKeyedMutex), reinterpret_cast<void**>(&KeyMutex));
  549. if (FAILED(hr)) {
  550. Ret = ProcessFailure(nullptr, L"Failed to get keyed mutex interface in spawned thread", L"Error", hr);
  551. goto Exit;
  552. }
  553. // Make duplication manager
  554. Ret = DuplMgr.InitDupl(TData->DxRes.Device, TData->DxRes.Context ,TData->Output);
  555. if (Ret != DUPL_RETURN_SUCCESS) {
  556. goto Exit;
  557. }
  558. // Get output description
  559. DXGI_OUTPUT_DESC DesktopDesc;
  560. RtlZeroMemory(&DesktopDesc, sizeof(DXGI_OUTPUT_DESC));
  561. DuplMgr.GetOutputDesc(&DesktopDesc);
  562. // Main duplication loop
  563. bool WaitToProcessCurrentFrame = false;
  564. FRAME_DATA CurrentData;
  565. while (TData->Producer->is_started && (WaitForSingleObjectEx(TData->TerminateThreadsEvent, 0, FALSE) == WAIT_TIMEOUT)) {
  566. if (!WaitToProcessCurrentFrame) {
  567. // Get new frame from desktop duplication
  568. bool TimeOut;
  569. Ret = DuplMgr.GetFrame(&CurrentData, &TimeOut);
  570. if (Ret != DUPL_RETURN_SUCCESS) {
  571. // An error occurred getting the next frame drop out of loop which
  572. // will check if it was expected or not
  573. break;
  574. }
  575. // Check for timeout
  576. if (TimeOut) {
  577. // No new frame at the moment
  578. continue;
  579. }
  580. }
  581. // We have a new frame so try and process it
  582. // Try to acquire keyed mutex in order to access shared surface
  583. hr = KeyMutex->AcquireSync(0, 1000);
  584. if (hr == static_cast<HRESULT>(WAIT_TIMEOUT)) {
  585. // Can't use shared surface right now, try again later
  586. WaitToProcessCurrentFrame = true;
  587. continue;
  588. }
  589. else if (FAILED(hr)) {
  590. // Generic unknown failure
  591. Ret = ProcessFailure(TData->DxRes.Device, L"Unexpected error acquiring KeyMutex", L"Error", hr, SystemTransitionsExpectedErrors);
  592. DuplMgr.DoneWithFrame();
  593. break;
  594. }
  595. // We can now process the current frame
  596. WaitToProcessCurrentFrame = false;
  597. // Get mouse info
  598. Ret = DuplMgr.GetMouse(TData->PtrInfo, &(CurrentData.FrameInfo), TData->OffsetX, TData->OffsetY);
  599. if (Ret != DUPL_RETURN_SUCCESS) {
  600. DuplMgr.DoneWithFrame();
  601. KeyMutex->ReleaseSync(1);
  602. break;
  603. }
  604. // Process new frame
  605. Ret = DispMgr.ProcessFrame(&CurrentData, SharedSurf, TData->OffsetX, TData->OffsetY, &DesktopDesc);
  606. if (Ret != DUPL_RETURN_SUCCESS) {
  607. DuplMgr.DoneWithFrame();
  608. KeyMutex->ReleaseSync(1);
  609. break;
  610. }
  611. // Release acquired keyed mutex
  612. hr = KeyMutex->ReleaseSync(1);
  613. if (FAILED(hr)) {
  614. Ret = ProcessFailure(TData->DxRes.Device, L"Unexpected error releasing the keyed mutex", L"Error", hr, SystemTransitionsExpectedErrors);
  615. DuplMgr.DoneWithFrame();
  616. break;
  617. }
  618. // Send Frame Over the Network
  619. TimeNow = tsk_time_now();
  620. if ((TimeNow - TimeLastFrame) > TimeFrameDuration) {
  621. if (!((const plugin_win_dd_producer_t*)TData->Producer)->bMuted) {
  622. hr = DuplMgr.SendData(const_cast<struct tmedia_producer_s*>(TData->Producer), &CurrentData);
  623. }
  624. if (SUCCEEDED(hr)) {
  625. TimeLastFrame = TimeNow;
  626. }
  627. }
  628. #if 0
  629. else {
  630. DD_DEBUG_INFO("Skip frame");
  631. }
  632. #endif
  633. // Release frame back to desktop duplication
  634. Ret = DuplMgr.DoneWithFrame();
  635. if (Ret != DUPL_RETURN_SUCCESS) {
  636. break;
  637. }
  638. }
  639. Exit:
  640. if (Ret != DUPL_RETURN_SUCCESS) {
  641. if (Ret == DUPL_RETURN_ERROR_EXPECTED) {
  642. // The system is in a transition state so request the duplication be restarted
  643. SetEvent(TData->ExpectedErrorEvent);
  644. }
  645. else {
  646. // Unexpected error so exit the application
  647. SetEvent(TData->UnexpectedErrorEvent);
  648. }
  649. }
  650. if (SharedSurf) {
  651. SharedSurf->Release();
  652. SharedSurf = nullptr;
  653. }
  654. if (KeyMutex) {
  655. KeyMutex->Release();
  656. KeyMutex = nullptr;
  657. }
  658. DD_DEBUG_INFO("DDProc (producer) - EXIT");
  659. return 0;
  660. }
  661. // Run session async thread
  662. static void* TSK_STDCALL DDThread(void *pArg)
  663. {
  664. plugin_win_dd_producer_t *pSelf = (plugin_win_dd_producer_t *)pArg;
  665. HRESULT hr = S_OK;
  666. INT SingleOutput = -1;
  667. RECT DeskBounds = {};
  668. UINT OutputCount = 1;
  669. bool FirstTime = true;
  670. bool Occluded = true;
  671. bool PreviewChanged = false;
  672. DYNAMIC_WAIT DynamicWait;
  673. HWND hwndPreview = NULL;
  674. DD_DEBUG_INFO("DDThread (producer) - ENTER");
  675. while (pSelf->bStarted) {
  676. DUPL_RETURN Ret = DUPL_RETURN_SUCCESS;
  677. // Check if Preview window changed
  678. PreviewChanged = (hwndPreview != pSelf->hwndPreview);
  679. if (WaitForSingleObjectEx(pSelf->hlOcclutionEvent, 0, FALSE) == WAIT_OBJECT_0) {
  680. Occluded = false;
  681. }
  682. if (WaitForSingleObjectEx(pSelf->hlUnexpectedErrorEvent, 0, FALSE) == WAIT_OBJECT_0) {
  683. // Unexpected error occurred so exit the application
  684. DD_CHECK_HR(hr = E_UNEXPECTED);
  685. }
  686. else if (FirstTime || PreviewChanged || WaitForSingleObjectEx(pSelf->hlExpectedErrorEvent, 0, FALSE) == WAIT_OBJECT_0) {
  687. if (PreviewChanged) {
  688. hwndPreview = pSelf->hwndPreview;
  689. }
  690. if (!FirstTime) {
  691. // Terminate other threads
  692. SetEvent(pSelf->hlTerminateThreadsEvent);
  693. pSelf->pThreadMgr->WaitForThreadTermination();
  694. ResetEvent(pSelf->hlTerminateThreadsEvent);
  695. ResetEvent(pSelf->hlExpectedErrorEvent);
  696. // Clean up
  697. pSelf->pThreadMgr->Clean();
  698. pSelf->pOutMgr->CleanRefs();
  699. // As we have encountered an error due to a system transition we wait before trying again, using this dynamic wait
  700. // the wait periods will get progressively long to avoid wasting too much system resource if this state lasts a long time
  701. DynamicWait.Wait();
  702. }
  703. else {
  704. // First time through the loop so nothing to clean up
  705. FirstTime = false;
  706. }
  707. // Re-initialize
  708. Ret = pSelf->pOutMgr->InitOutput(hwndPreview, SingleOutput, &OutputCount, &DeskBounds);
  709. if (Ret == DUPL_RETURN_SUCCESS) {
  710. HANDLE SharedHandle = pSelf->pOutMgr->GetSharedHandle();
  711. if (SharedHandle) {
  712. Ret = pSelf->pThreadMgr->Initialize(SingleOutput, OutputCount, pSelf->hlUnexpectedErrorEvent, pSelf->hlExpectedErrorEvent, pSelf->hlTerminateThreadsEvent, SharedHandle, TMEDIA_PRODUCER(pSelf), &DeskBounds);
  713. }
  714. else {
  715. DisplayMsg(L"Failed to get handle of shared surface", L"Error", S_OK);
  716. Ret = DUPL_RETURN_ERROR_UNEXPECTED;
  717. }
  718. }
  719. // We start off in occluded state and we should immediate get a occlusion status window message
  720. Occluded = true;
  721. }
  722. else {
  723. // Nothing else to do, so try to present to write out to window if not occluded
  724. if (!Occluded || !pSelf->bWindowHooked) {
  725. Ret = pSelf->pOutMgr->UpdateApplicationWindow(pSelf->pThreadMgr->GetPointerInfo(), &Occluded);
  726. }
  727. }
  728. // Check if for errors
  729. if (Ret != DUPL_RETURN_SUCCESS) {
  730. if (Ret == DUPL_RETURN_ERROR_EXPECTED) {
  731. // Some type of system transition is occurring so retry
  732. SetEvent(pSelf->hlExpectedErrorEvent);
  733. }
  734. else {
  735. // Unexpected error so exit
  736. DD_CHECK_HR(hr = E_UNEXPECTED);
  737. break;
  738. }
  739. }
  740. }
  741. bail:
  742. DD_DEBUG_INFO("DDThread (producer) - BAIL");
  743. #if 0 // Done by unprepare()
  744. // Make sure all other threads have exited
  745. if (SetEvent(pSelf->hlTerminateThreadsEvent)) {
  746. ThreadMgr.WaitForThreadTermination();
  747. }
  748. // Clean up
  749. CloseHandle(pSelf->hlUnexpectedErrorEvent);
  750. pSelf->hlUnexpectedErrorEvent = NULL;
  751. CloseHandle(pSelf->hlExpectedErrorEvent);
  752. pSelf->hlExpectedErrorEvent = NULL;
  753. CloseHandle(pSelf->hlTerminateThreadsEvent);
  754. pSelf->hlTerminateThreadsEvent = NULL;
  755. #endif
  756. DD_DEBUG_INFO("DDThread (producer) - EXIT");
  757. return NULL;
  758. }
  759. //
  760. // Displays a message
  761. //
  762. void DisplayMsg(_In_ LPCWSTR Str, _In_ LPCWSTR Title, HRESULT hr)
  763. {
  764. const UINT StringLen = (UINT)(wcslen(Str) + sizeof(" with HRESULT 0x########."));
  765. wchar_t* OutStr = new wchar_t[StringLen];
  766. if (!OutStr) {
  767. return;
  768. }
  769. INT LenWritten = swprintf_s(OutStr, StringLen, L"%s with 0x%X.", Str, hr);
  770. if (LenWritten != -1) {
  771. DD_DEBUG_ERROR("%ls: %ls", Title, OutStr);
  772. }
  773. delete[] OutStr;
  774. }
  775. DYNAMIC_WAIT::DYNAMIC_WAIT() : m_CurrentWaitBandIdx(0), m_WaitCountInCurrentBand(0)
  776. {
  777. m_QPCValid = QueryPerformanceFrequency(&m_QPCFrequency);
  778. m_LastWakeUpTime.QuadPart = 0L;
  779. }
  780. DYNAMIC_WAIT::~DYNAMIC_WAIT()
  781. {
  782. }
  783. void DYNAMIC_WAIT::Wait()
  784. {
  785. LARGE_INTEGER CurrentQPC = { 0 };
  786. // Is this wait being called with the period that we consider it to be part of the same wait sequence
  787. QueryPerformanceCounter(&CurrentQPC);
  788. if (m_QPCValid && (CurrentQPC.QuadPart <= (m_LastWakeUpTime.QuadPart + (m_QPCFrequency.QuadPart * m_WaitSequenceTimeInSeconds)))) {
  789. // We are still in the same wait sequence, lets check if we should move to the next band
  790. if ((m_WaitBands[m_CurrentWaitBandIdx].WaitCount != WAIT_BAND_STOP) && (m_WaitCountInCurrentBand > m_WaitBands[m_CurrentWaitBandIdx].WaitCount)) {
  791. m_CurrentWaitBandIdx++;
  792. m_WaitCountInCurrentBand = 0;
  793. }
  794. }
  795. else {
  796. // Either we could not get the current time or we are starting a new wait sequence
  797. m_WaitCountInCurrentBand = 0;
  798. m_CurrentWaitBandIdx = 0;
  799. }
  800. // Sleep for the required period of time
  801. Sleep(m_WaitBands[m_CurrentWaitBandIdx].WaitTime);
  802. // Record the time we woke up so we can detect wait sequences
  803. QueryPerformanceCounter(&m_LastWakeUpTime);
  804. m_WaitCountInCurrentBand++;
  805. }