1
0

win-wasapi.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593
  1. #include "wasapi-notify.hpp"
  2. #include "enum-wasapi.hpp"
  3. #include <obs-module.h>
  4. #include <obs.h>
  5. #include <util/dstr.h>
  6. #include <util/platform.h>
  7. #include <util/windows/HRError.hpp>
  8. #include <util/windows/ComPtr.hpp>
  9. #include <util/windows/WinHandle.hpp>
  10. #include <util/windows/CoTaskMemPtr.hpp>
  11. #include <util/windows/win-version.h>
  12. #include <util/windows/window-helpers.h>
  13. #include <util/threading.h>
  14. #include <util/util_uint64.h>
  15. #include <atomic>
  16. #include <cinttypes>
  17. #include <audioclientactivationparams.h>
  18. #include <avrt.h>
  19. #include <RTWorkQ.h>
  20. #include <wrl/implements.h>
  21. using namespace std;
  22. #define OPT_DEVICE_ID "device_id"
  23. #define OPT_USE_DEVICE_TIMING "use_device_timing"
  24. #define OPT_WINDOW "window"
  25. #define OPT_PRIORITY "priority"
  26. WASAPINotify *GetNotify();
  27. static void GetWASAPIDefaults(obs_data_t *settings);
  28. #define OBS_KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_SURROUND | SPEAKER_LOW_FREQUENCY)
  29. typedef HRESULT(STDAPICALLTYPE *PFN_ActivateAudioInterfaceAsync)(LPCWSTR, REFIID, PROPVARIANT *,
  30. IActivateAudioInterfaceCompletionHandler *,
  31. IActivateAudioInterfaceAsyncOperation **);
  32. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqUnlockWorkQueue)(DWORD);
  33. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqLockSharedWorkQueue)(PCWSTR usageClass, LONG basePriority, DWORD *taskId,
  34. DWORD *id);
  35. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqCreateAsyncResult)(IUnknown *, IRtwqAsyncCallback *, IUnknown *,
  36. IRtwqAsyncResult **);
  37. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqPutWorkItem)(DWORD, LONG, IRtwqAsyncResult *);
  38. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqPutWaitingWorkItem)(HANDLE, LONG, IRtwqAsyncResult *, RTWQWORKITEM_KEY *);
  39. class WASAPIActivateAudioInterfaceCompletionHandler
  40. : public Microsoft::WRL::RuntimeClass<Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
  41. Microsoft::WRL::FtmBase, IActivateAudioInterfaceCompletionHandler> {
  42. IUnknown *unknown;
  43. HRESULT activationResult;
  44. WinHandle activationSignal;
  45. public:
  46. WASAPIActivateAudioInterfaceCompletionHandler();
  47. HRESULT GetActivateResult(IAudioClient **client);
  48. private:
  49. virtual HRESULT STDMETHODCALLTYPE
  50. ActivateCompleted(IActivateAudioInterfaceAsyncOperation *activateOperation) override final;
  51. };
  52. WASAPIActivateAudioInterfaceCompletionHandler::WASAPIActivateAudioInterfaceCompletionHandler()
  53. {
  54. activationSignal = CreateEvent(nullptr, false, false, nullptr);
  55. if (!activationSignal.Valid())
  56. throw "Could not create receive signal";
  57. }
  58. HRESULT
  59. WASAPIActivateAudioInterfaceCompletionHandler::GetActivateResult(IAudioClient **client)
  60. {
  61. WaitForSingleObject(activationSignal, INFINITE);
  62. *client = static_cast<IAudioClient *>(unknown);
  63. return activationResult;
  64. }
  65. HRESULT
  66. WASAPIActivateAudioInterfaceCompletionHandler::ActivateCompleted(
  67. IActivateAudioInterfaceAsyncOperation *activateOperation)
  68. {
  69. HRESULT hr, hr_activate;
  70. hr = activateOperation->GetActivateResult(&hr_activate, &unknown);
  71. hr = SUCCEEDED(hr) ? hr_activate : hr;
  72. activationResult = hr;
  73. SetEvent(activationSignal);
  74. return hr;
  75. }
  76. enum class SourceType {
  77. Input,
  78. DeviceOutput,
  79. ProcessOutput,
  80. };
  81. class ARtwqAsyncCallback : public IRtwqAsyncCallback {
  82. protected:
  83. ARtwqAsyncCallback(void *source) : source(source) {}
  84. public:
  85. STDMETHOD_(ULONG, AddRef)() { return ++refCount; }
  86. STDMETHOD_(ULONG, Release)() { return --refCount; }
  87. STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject)
  88. {
  89. HRESULT hr = E_NOINTERFACE;
  90. if (riid == __uuidof(IRtwqAsyncCallback) || riid == __uuidof(IUnknown)) {
  91. *ppvObject = this;
  92. AddRef();
  93. hr = S_OK;
  94. } else {
  95. *ppvObject = NULL;
  96. }
  97. return hr;
  98. }
  99. STDMETHOD(GetParameters)
  100. (DWORD *pdwFlags, DWORD *pdwQueue)
  101. {
  102. *pdwFlags = 0;
  103. *pdwQueue = queue_id;
  104. return S_OK;
  105. }
  106. STDMETHOD(Invoke)
  107. (IRtwqAsyncResult *) override = 0;
  108. DWORD GetQueueId() const { return queue_id; }
  109. void SetQueueId(DWORD id) { queue_id = id; }
  110. protected:
  111. std::atomic<ULONG> refCount = 1;
  112. void *source;
  113. DWORD queue_id = 0;
  114. };
  115. class WASAPISource {
  116. ComPtr<IMMDeviceEnumerator> enumerator;
  117. ComPtr<IAudioClient> client;
  118. ComPtr<IAudioCaptureClient> capture;
  119. obs_source_t *source;
  120. obs_weak_source_t *reroute_target = nullptr;
  121. wstring default_id;
  122. string device_id;
  123. string device_name;
  124. WinModule mmdevapi_module;
  125. PFN_ActivateAudioInterfaceAsync activate_audio_interface_async = NULL;
  126. PFN_RtwqUnlockWorkQueue rtwq_unlock_work_queue = NULL;
  127. PFN_RtwqLockSharedWorkQueue rtwq_lock_shared_work_queue = NULL;
  128. PFN_RtwqCreateAsyncResult rtwq_create_async_result = NULL;
  129. PFN_RtwqPutWorkItem rtwq_put_work_item = NULL;
  130. PFN_RtwqPutWaitingWorkItem rtwq_put_waiting_work_item = NULL;
  131. bool rtwq_supported = false;
  132. window_priority priority;
  133. string window_class;
  134. string title;
  135. string executable;
  136. HWND hwnd = NULL;
  137. DWORD process_id = 0;
  138. const SourceType sourceType;
  139. std::atomic<bool> useDeviceTiming = false;
  140. std::atomic<bool> isDefaultDevice = false;
  141. std::atomic<bool> sawBadTimestamp = false;
  142. bool hooked = false;
  143. bool previouslyFailed = false;
  144. WinHandle reconnectThread = NULL;
  145. class CallbackStartCapture : public ARtwqAsyncCallback {
  146. public:
  147. CallbackStartCapture(WASAPISource *source) : ARtwqAsyncCallback(source) {}
  148. STDMETHOD(Invoke)
  149. (IRtwqAsyncResult *) override
  150. {
  151. ((WASAPISource *)source)->OnStartCapture();
  152. return S_OK;
  153. }
  154. } startCapture;
  155. ComPtr<IRtwqAsyncResult> startCaptureAsyncResult;
  156. class CallbackSampleReady : public ARtwqAsyncCallback {
  157. public:
  158. CallbackSampleReady(WASAPISource *source) : ARtwqAsyncCallback(source) {}
  159. STDMETHOD(Invoke)
  160. (IRtwqAsyncResult *) override
  161. {
  162. ((WASAPISource *)source)->OnSampleReady();
  163. return S_OK;
  164. }
  165. } sampleReady;
  166. ComPtr<IRtwqAsyncResult> sampleReadyAsyncResult;
  167. class CallbackRestart : public ARtwqAsyncCallback {
  168. public:
  169. CallbackRestart(WASAPISource *source) : ARtwqAsyncCallback(source) {}
  170. STDMETHOD(Invoke)
  171. (IRtwqAsyncResult *) override
  172. {
  173. ((WASAPISource *)source)->OnRestart();
  174. return S_OK;
  175. }
  176. } restart;
  177. ComPtr<IRtwqAsyncResult> restartAsyncResult;
  178. WinHandle captureThread;
  179. WinHandle idleSignal;
  180. WinHandle stopSignal;
  181. WinHandle receiveSignal;
  182. WinHandle restartSignal;
  183. WinHandle reconnectExitSignal;
  184. WinHandle exitSignal;
  185. WinHandle initSignal;
  186. DWORD reconnectDuration = 0;
  187. WinHandle reconnectSignal;
  188. speaker_layout speakers;
  189. audio_format format;
  190. uint32_t sampleRate;
  191. vector<BYTE> silence;
  192. static DWORD WINAPI ReconnectThread(LPVOID param);
  193. static DWORD WINAPI CaptureThread(LPVOID param);
  194. bool ProcessCaptureData();
  195. void Start();
  196. void Stop();
  197. static ComPtr<IMMDevice> InitDevice(IMMDeviceEnumerator *enumerator, bool isDefaultDevice, SourceType type,
  198. const string device_id);
  199. static ComPtr<IAudioClient> InitClient(IMMDevice *device, SourceType type, DWORD process_id,
  200. PFN_ActivateAudioInterfaceAsync activate_audio_interface_async,
  201. speaker_layout &speakers, audio_format &format, uint32_t &sampleRate);
  202. static void InitFormat(const WAVEFORMATEX *wfex, enum speaker_layout &speakers, enum audio_format &format,
  203. uint32_t &sampleRate);
  204. static void ClearBuffer(IMMDevice *device);
  205. static ComPtr<IAudioCaptureClient> InitCapture(IAudioClient *client, HANDLE receiveSignal);
  206. void Initialize();
  207. bool TryInitialize();
  208. struct UpdateParams {
  209. string device_id;
  210. bool useDeviceTiming;
  211. bool isDefaultDevice;
  212. window_priority priority;
  213. string window_class;
  214. string title;
  215. string executable;
  216. };
  217. UpdateParams BuildUpdateParams(obs_data_t *settings);
  218. void UpdateSettings(UpdateParams &&params);
  219. void LogSettings();
  220. public:
  221. WASAPISource(obs_data_t *settings, obs_source_t *source_, SourceType type);
  222. ~WASAPISource();
  223. void Update(obs_data_t *settings);
  224. void OnWindowChanged(obs_data_t *settings);
  225. void Activate();
  226. void Deactivate();
  227. void SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id);
  228. void OnStartCapture();
  229. void OnSampleReady();
  230. void OnRestart();
  231. bool GetHooked();
  232. HWND GetHwnd();
  233. void SetRerouteTarget(obs_source_t *target)
  234. {
  235. obs_weak_source_release(reroute_target);
  236. reroute_target = obs_source_get_weak_source(target);
  237. }
  238. };
  239. WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_, SourceType type)
  240. : source(source_),
  241. sourceType(type),
  242. startCapture(this),
  243. sampleReady(this),
  244. restart(this)
  245. {
  246. mmdevapi_module = LoadLibrary(L"Mmdevapi");
  247. if (mmdevapi_module) {
  248. activate_audio_interface_async =
  249. (PFN_ActivateAudioInterfaceAsync)GetProcAddress(mmdevapi_module, "ActivateAudioInterfaceAsync");
  250. }
  251. UpdateSettings(BuildUpdateParams(settings));
  252. LogSettings();
  253. idleSignal = CreateEvent(nullptr, true, false, nullptr);
  254. if (!idleSignal.Valid())
  255. throw "Could not create idle signal";
  256. stopSignal = CreateEvent(nullptr, true, false, nullptr);
  257. if (!stopSignal.Valid())
  258. throw "Could not create stop signal";
  259. receiveSignal = CreateEvent(nullptr, false, false, nullptr);
  260. if (!receiveSignal.Valid())
  261. throw "Could not create receive signal";
  262. restartSignal = CreateEvent(nullptr, true, false, nullptr);
  263. if (!restartSignal.Valid())
  264. throw "Could not create restart signal";
  265. reconnectExitSignal = CreateEvent(nullptr, true, false, nullptr);
  266. if (!reconnectExitSignal.Valid())
  267. throw "Could not create reconnect exit signal";
  268. exitSignal = CreateEvent(nullptr, true, false, nullptr);
  269. if (!exitSignal.Valid())
  270. throw "Could not create exit signal";
  271. initSignal = CreateEvent(nullptr, false, false, nullptr);
  272. if (!initSignal.Valid())
  273. throw "Could not create init signal";
  274. reconnectSignal = CreateEvent(nullptr, false, false, nullptr);
  275. if (!reconnectSignal.Valid())
  276. throw "Could not create reconnect signal";
  277. HRESULT hr =
  278. CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, IID_PPV_ARGS(enumerator.Assign()));
  279. if (FAILED(hr))
  280. throw HRError("Failed to create enumerator", hr);
  281. /* OBS will already load DLL on startup if it exists */
  282. const HMODULE rtwq_module = GetModuleHandle(L"RTWorkQ.dll");
  283. // while RTWQ was introduced in Win 8.1, it silently fails
  284. // to capture Desktop Audio for some reason. Disable for now.
  285. struct win_version_info win1703 = {};
  286. win1703.major = 10;
  287. win1703.minor = 0;
  288. win1703.build = 15063;
  289. win1703.revis = 0;
  290. struct win_version_info ver;
  291. get_win_ver(&ver);
  292. if (win_version_compare(&ver, &win1703) >= 0)
  293. rtwq_supported = rtwq_module != NULL;
  294. if (rtwq_supported) {
  295. rtwq_unlock_work_queue = (PFN_RtwqUnlockWorkQueue)GetProcAddress(rtwq_module, "RtwqUnlockWorkQueue");
  296. rtwq_lock_shared_work_queue =
  297. (PFN_RtwqLockSharedWorkQueue)GetProcAddress(rtwq_module, "RtwqLockSharedWorkQueue");
  298. rtwq_create_async_result =
  299. (PFN_RtwqCreateAsyncResult)GetProcAddress(rtwq_module, "RtwqCreateAsyncResult");
  300. rtwq_put_work_item = (PFN_RtwqPutWorkItem)GetProcAddress(rtwq_module, "RtwqPutWorkItem");
  301. rtwq_put_waiting_work_item =
  302. (PFN_RtwqPutWaitingWorkItem)GetProcAddress(rtwq_module, "RtwqPutWaitingWorkItem");
  303. try {
  304. hr = rtwq_create_async_result(nullptr, &startCapture, nullptr, &startCaptureAsyncResult);
  305. if (FAILED(hr)) {
  306. throw HRError("Could not create startCaptureAsyncResult", hr);
  307. }
  308. hr = rtwq_create_async_result(nullptr, &sampleReady, nullptr, &sampleReadyAsyncResult);
  309. if (FAILED(hr)) {
  310. throw HRError("Could not create sampleReadyAsyncResult", hr);
  311. }
  312. hr = rtwq_create_async_result(nullptr, &restart, nullptr, &restartAsyncResult);
  313. if (FAILED(hr)) {
  314. throw HRError("Could not create restartAsyncResult", hr);
  315. }
  316. DWORD taskId = 0;
  317. DWORD id = 0;
  318. hr = rtwq_lock_shared_work_queue(L"Capture", 0, &taskId, &id);
  319. if (FAILED(hr)) {
  320. throw HRError("RtwqLockSharedWorkQueue failed", hr);
  321. }
  322. startCapture.SetQueueId(id);
  323. sampleReady.SetQueueId(id);
  324. restart.SetQueueId(id);
  325. } catch (HRError &err) {
  326. blog(LOG_ERROR, "RTWQ setup failed: %s (0x%08X)", err.str, err.hr);
  327. rtwq_supported = false;
  328. }
  329. }
  330. if (!rtwq_supported) {
  331. captureThread = CreateThread(nullptr, 0, WASAPISource::CaptureThread, this, 0, nullptr);
  332. if (!captureThread.Valid()) {
  333. throw "Failed to create capture thread";
  334. }
  335. }
  336. auto notify = GetNotify();
  337. if (notify) {
  338. notify->AddDefaultDeviceChangedCallback(this, std::bind(&WASAPISource::SetDefaultDevice, this,
  339. std::placeholders::_1, std::placeholders::_2,
  340. std::placeholders::_3));
  341. }
  342. Start();
  343. }
  344. void WASAPISource::Start()
  345. {
  346. if (rtwq_supported) {
  347. rtwq_put_work_item(startCapture.GetQueueId(), 0, startCaptureAsyncResult);
  348. } else {
  349. SetEvent(initSignal);
  350. }
  351. }
  352. void WASAPISource::Stop()
  353. {
  354. SetEvent(stopSignal);
  355. blog(LOG_INFO, "WASAPI: Device '%s' Terminated", device_name.c_str());
  356. if (rtwq_supported)
  357. SetEvent(receiveSignal);
  358. if (reconnectThread.Valid()) {
  359. WaitForSingleObject(idleSignal, INFINITE);
  360. } else {
  361. const HANDLE sigs[] = {reconnectSignal, idleSignal};
  362. WaitForMultipleObjects(_countof(sigs), sigs, false, INFINITE);
  363. }
  364. SetEvent(exitSignal);
  365. if (reconnectThread.Valid()) {
  366. SetEvent(reconnectExitSignal);
  367. WaitForSingleObject(reconnectThread, INFINITE);
  368. }
  369. if (rtwq_supported)
  370. rtwq_unlock_work_queue(sampleReady.GetQueueId());
  371. else
  372. WaitForSingleObject(captureThread, INFINITE);
  373. obs_weak_source_release(reroute_target);
  374. }
  375. WASAPISource::~WASAPISource()
  376. {
  377. auto notify = GetNotify();
  378. if (notify) {
  379. notify->RemoveDefaultDeviceChangedCallback(this);
  380. }
  381. // If the device is also used for monitoring, a cleanup is needed.
  382. if (sourceType == SourceType::DeviceOutput)
  383. obs_source_audio_output_capture_device_changed(source, NULL);
  384. Stop();
  385. }
  386. WASAPISource::UpdateParams WASAPISource::BuildUpdateParams(obs_data_t *settings)
  387. {
  388. WASAPISource::UpdateParams params;
  389. params.device_id = obs_data_get_string(settings, OPT_DEVICE_ID);
  390. params.useDeviceTiming = obs_data_get_bool(settings, OPT_USE_DEVICE_TIMING);
  391. params.isDefaultDevice = _strcmpi(params.device_id.c_str(), "default") == 0;
  392. params.priority = (window_priority)obs_data_get_int(settings, "priority");
  393. params.window_class.clear();
  394. params.title.clear();
  395. params.executable.clear();
  396. if (sourceType != SourceType::Input) {
  397. const char *const window = obs_data_get_string(settings, OPT_WINDOW);
  398. char *window_class = nullptr;
  399. char *title = nullptr;
  400. char *executable = nullptr;
  401. ms_build_window_strings(window, &window_class, &title, &executable);
  402. if (window_class) {
  403. params.window_class = window_class;
  404. bfree(window_class);
  405. }
  406. if (title) {
  407. params.title = title;
  408. bfree(title);
  409. }
  410. if (executable) {
  411. params.executable = executable;
  412. bfree(executable);
  413. }
  414. }
  415. return params;
  416. }
  417. void WASAPISource::UpdateSettings(UpdateParams &&params)
  418. {
  419. // Signal to deduplication logic in case the device is also used for monitoring.
  420. if (device_id != params.device_id && sourceType == SourceType::DeviceOutput)
  421. obs_source_audio_output_capture_device_changed(source, params.device_id.c_str());
  422. device_id = std::move(params.device_id);
  423. useDeviceTiming = params.useDeviceTiming;
  424. isDefaultDevice = params.isDefaultDevice;
  425. priority = params.priority;
  426. window_class = std::move(params.window_class);
  427. title = std::move(params.title);
  428. executable = std::move(params.executable);
  429. }
  430. void WASAPISource::LogSettings()
  431. {
  432. if (sourceType == SourceType::ProcessOutput) {
  433. blog(LOG_INFO,
  434. "[win-wasapi: '%s'] update settings:\n"
  435. "\texecutable: %s\n"
  436. "\ttitle: %s\n"
  437. "\tclass: %s\n"
  438. "\tpriority: %d",
  439. obs_source_get_name(source), executable.c_str(), title.c_str(), window_class.c_str(),
  440. (int)priority);
  441. } else {
  442. blog(LOG_INFO,
  443. "[win-wasapi: '%s'] update settings:\n"
  444. "\tdevice id: %s\n"
  445. "\tuse device timing: %d",
  446. obs_source_get_name(source), device_id.c_str(), (int)useDeviceTiming);
  447. }
  448. }
  449. void WASAPISource::Update(obs_data_t *settings)
  450. {
  451. UpdateParams params = BuildUpdateParams(settings);
  452. const bool restart = (sourceType == SourceType::ProcessOutput)
  453. ? ((priority != params.priority) || (window_class != params.window_class) ||
  454. (title != params.title) || (executable != params.executable))
  455. : (device_id.compare(params.device_id) != 0);
  456. UpdateSettings(std::move(params));
  457. LogSettings();
  458. if (restart)
  459. SetEvent(restartSignal);
  460. }
  461. void WASAPISource::OnWindowChanged(obs_data_t *settings)
  462. {
  463. UpdateParams params = BuildUpdateParams(settings);
  464. const bool restart = (sourceType == SourceType::ProcessOutput)
  465. ? ((priority != params.priority) || (window_class != params.window_class) ||
  466. (title != params.title) || (executable != params.executable))
  467. : (device_id.compare(params.device_id) != 0);
  468. UpdateSettings(std::move(params));
  469. if (restart)
  470. SetEvent(restartSignal);
  471. }
  472. void WASAPISource::Activate()
  473. {
  474. if (!reconnectThread.Valid()) {
  475. ResetEvent(reconnectExitSignal);
  476. reconnectThread = CreateThread(nullptr, 0, WASAPISource::ReconnectThread, this, 0, nullptr);
  477. }
  478. }
  479. void WASAPISource::Deactivate()
  480. {
  481. if (reconnectThread.Valid()) {
  482. SetEvent(reconnectExitSignal);
  483. WaitForSingleObject(reconnectThread, INFINITE);
  484. reconnectThread = NULL;
  485. }
  486. }
  487. ComPtr<IMMDevice> WASAPISource::InitDevice(IMMDeviceEnumerator *enumerator, bool isDefaultDevice, SourceType type,
  488. const string device_id)
  489. {
  490. ComPtr<IMMDevice> device;
  491. if (isDefaultDevice) {
  492. const bool input = type == SourceType::Input;
  493. HRESULT res = enumerator->GetDefaultAudioEndpoint(input ? eCapture : eRender,
  494. input ? eCommunications : eConsole, device.Assign());
  495. if (FAILED(res))
  496. throw HRError("Failed GetDefaultAudioEndpoint", res);
  497. } else {
  498. wchar_t *w_id;
  499. os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
  500. if (!w_id)
  501. throw "Failed to widen device id string";
  502. const HRESULT res = enumerator->GetDevice(w_id, device.Assign());
  503. bfree(w_id);
  504. if (FAILED(res))
  505. throw HRError("Failed to enumerate device", res);
  506. }
  507. return device;
  508. }
  509. #define BUFFER_TIME_100NS (5 * 10000000)
  510. static DWORD GetSpeakerChannelMask(speaker_layout layout)
  511. {
  512. switch (layout) {
  513. case SPEAKERS_STEREO:
  514. return KSAUDIO_SPEAKER_STEREO;
  515. case SPEAKERS_2POINT1:
  516. return KSAUDIO_SPEAKER_2POINT1;
  517. case SPEAKERS_4POINT0:
  518. return KSAUDIO_SPEAKER_SURROUND;
  519. case SPEAKERS_4POINT1:
  520. return OBS_KSAUDIO_SPEAKER_4POINT1;
  521. case SPEAKERS_5POINT1:
  522. return KSAUDIO_SPEAKER_5POINT1_SURROUND;
  523. case SPEAKERS_7POINT1:
  524. return KSAUDIO_SPEAKER_7POINT1_SURROUND;
  525. }
  526. return (DWORD)layout;
  527. }
  528. ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device, SourceType type, DWORD process_id,
  529. PFN_ActivateAudioInterfaceAsync activate_audio_interface_async,
  530. speaker_layout &speakers, audio_format &format, uint32_t &samples_per_sec)
  531. {
  532. WAVEFORMATEXTENSIBLE wfextensible;
  533. CoTaskMemPtr<WAVEFORMATEX> wfex;
  534. const WAVEFORMATEX *pFormat;
  535. HRESULT res;
  536. ComPtr<IAudioClient> client;
  537. if (type == SourceType::ProcessOutput) {
  538. if (activate_audio_interface_async == NULL)
  539. throw "ActivateAudioInterfaceAsync is not available";
  540. struct obs_audio_info oai;
  541. obs_get_audio_info(&oai);
  542. const WORD nChannels = (WORD)get_audio_channels(oai.speakers);
  543. const DWORD nSamplesPerSec = oai.samples_per_sec;
  544. constexpr WORD wBitsPerSample = 32;
  545. const WORD nBlockAlign = nChannels * wBitsPerSample / 8;
  546. WAVEFORMATEX &wf = wfextensible.Format;
  547. wf.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
  548. wf.nChannels = nChannels;
  549. wf.nSamplesPerSec = nSamplesPerSec;
  550. wf.nAvgBytesPerSec = nSamplesPerSec * nBlockAlign;
  551. wf.nBlockAlign = nBlockAlign;
  552. wf.wBitsPerSample = wBitsPerSample;
  553. wf.cbSize = sizeof(wfextensible) - sizeof(wf);
  554. wfextensible.Samples.wValidBitsPerSample = wBitsPerSample;
  555. wfextensible.dwChannelMask = GetSpeakerChannelMask(oai.speakers);
  556. wfextensible.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
  557. AUDIOCLIENT_ACTIVATION_PARAMS audioclientActivationParams;
  558. audioclientActivationParams.ActivationType = AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK;
  559. audioclientActivationParams.ProcessLoopbackParams.TargetProcessId = process_id;
  560. audioclientActivationParams.ProcessLoopbackParams.ProcessLoopbackMode =
  561. PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE;
  562. PROPVARIANT activateParams{};
  563. activateParams.vt = VT_BLOB;
  564. activateParams.blob.cbSize = sizeof(audioclientActivationParams);
  565. activateParams.blob.pBlobData = reinterpret_cast<BYTE *>(&audioclientActivationParams);
  566. {
  567. Microsoft::WRL::ComPtr<WASAPIActivateAudioInterfaceCompletionHandler> handler =
  568. Microsoft::WRL::Make<WASAPIActivateAudioInterfaceCompletionHandler>();
  569. ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
  570. res = activate_audio_interface_async(VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK,
  571. __uuidof(IAudioClient), &activateParams, handler.Get(),
  572. &asyncOp);
  573. if (FAILED(res))
  574. throw HRError("Failed to get activate audio client", res);
  575. res = handler->GetActivateResult(client.Assign());
  576. if (FAILED(res))
  577. throw HRError("Async activation failed", res);
  578. }
  579. pFormat = &wf;
  580. } else {
  581. res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **)client.Assign());
  582. if (FAILED(res))
  583. throw HRError("Failed to activate client context", res);
  584. res = client->GetMixFormat(&wfex);
  585. if (FAILED(res))
  586. throw HRError("Failed to get mix format", res);
  587. pFormat = wfex.Get();
  588. }
  589. InitFormat(pFormat, speakers, format, samples_per_sec);
  590. DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
  591. if (type != SourceType::Input)
  592. flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
  593. res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, BUFFER_TIME_100NS, 0, pFormat, nullptr);
  594. if (FAILED(res))
  595. throw HRError("Failed to initialize audio client", res);
  596. return client;
  597. }
  598. void WASAPISource::ClearBuffer(IMMDevice *device)
  599. {
  600. CoTaskMemPtr<WAVEFORMATEX> wfex;
  601. HRESULT res;
  602. LPBYTE buffer;
  603. UINT32 frames;
  604. ComPtr<IAudioClient> client;
  605. res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, (void **)client.Assign());
  606. if (FAILED(res))
  607. throw HRError("Failed to activate client context", res);
  608. res = client->GetMixFormat(&wfex);
  609. if (FAILED(res))
  610. throw HRError("Failed to get mix format", res);
  611. res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, BUFFER_TIME_100NS, 0, wfex, nullptr);
  612. if (FAILED(res))
  613. throw HRError("Failed to initialize audio client", res);
  614. /* Silent loopback fix. Prevents audio stream from stopping and */
  615. /* messing up timestamps and other weird glitches during silence */
  616. /* by playing a silent sample all over again. */
  617. res = client->GetBufferSize(&frames);
  618. if (FAILED(res))
  619. throw HRError("Failed to get buffer size", res);
  620. ComPtr<IAudioRenderClient> render;
  621. res = client->GetService(IID_PPV_ARGS(render.Assign()));
  622. if (FAILED(res))
  623. throw HRError("Failed to get render client", res);
  624. res = render->GetBuffer(frames, &buffer);
  625. if (FAILED(res))
  626. throw HRError("Failed to get buffer", res);
  627. memset(buffer, 0, (size_t)frames * (size_t)wfex->nBlockAlign);
  628. render->ReleaseBuffer(frames, 0);
  629. }
  630. static speaker_layout ConvertSpeakerLayout(DWORD layout, WORD channels)
  631. {
  632. switch (layout) {
  633. case KSAUDIO_SPEAKER_2POINT1:
  634. return SPEAKERS_2POINT1;
  635. case KSAUDIO_SPEAKER_SURROUND:
  636. return SPEAKERS_4POINT0;
  637. case OBS_KSAUDIO_SPEAKER_4POINT1:
  638. return SPEAKERS_4POINT1;
  639. case KSAUDIO_SPEAKER_5POINT1_SURROUND:
  640. return SPEAKERS_5POINT1;
  641. case KSAUDIO_SPEAKER_7POINT1_SURROUND:
  642. return SPEAKERS_7POINT1;
  643. }
  644. return (speaker_layout)channels;
  645. }
  646. void WASAPISource::InitFormat(const WAVEFORMATEX *wfex, enum speaker_layout &speakers, enum audio_format &format,
  647. uint32_t &sampleRate)
  648. {
  649. DWORD layout = 0;
  650. if (wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
  651. WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE *)wfex;
  652. layout = ext->dwChannelMask;
  653. }
  654. /* WASAPI is always float */
  655. speakers = ConvertSpeakerLayout(layout, wfex->nChannels);
  656. format = AUDIO_FORMAT_FLOAT;
  657. sampleRate = wfex->nSamplesPerSec;
  658. }
  659. ComPtr<IAudioCaptureClient> WASAPISource::InitCapture(IAudioClient *client, HANDLE receiveSignal)
  660. {
  661. ComPtr<IAudioCaptureClient> capture;
  662. HRESULT res = client->GetService(IID_PPV_ARGS(capture.Assign()));
  663. if (FAILED(res))
  664. throw HRError("Failed to create capture context", res);
  665. res = client->SetEventHandle(receiveSignal);
  666. if (FAILED(res))
  667. throw HRError("Failed to set event handle", res);
  668. res = client->Start();
  669. if (FAILED(res))
  670. throw HRError("Failed to start capture client", res);
  671. return capture;
  672. }
  673. void WASAPISource::Initialize()
  674. {
  675. ComPtr<IMMDevice> device;
  676. if (sourceType == SourceType::ProcessOutput) {
  677. device_name = "[VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK]";
  678. hwnd = ms_find_window(INCLUDE_MINIMIZED, priority, window_class.c_str(), title.c_str(),
  679. executable.c_str());
  680. if (!hwnd)
  681. throw "Failed to find window";
  682. DWORD dwProcessId = 0;
  683. if (!GetWindowThreadProcessId(hwnd, &dwProcessId)) {
  684. hwnd = NULL;
  685. throw "Failed to get process id of window";
  686. }
  687. process_id = dwProcessId;
  688. } else {
  689. device = InitDevice(enumerator, isDefaultDevice, sourceType, device_id);
  690. device_name = GetDeviceName(device);
  691. }
  692. ResetEvent(receiveSignal);
  693. ComPtr<IAudioClient> temp_client = InitClient(device, sourceType, process_id, activate_audio_interface_async,
  694. speakers, format, sampleRate);
  695. if (sourceType == SourceType::DeviceOutput)
  696. ClearBuffer(device);
  697. ComPtr<IAudioCaptureClient> temp_capture = InitCapture(temp_client, receiveSignal);
  698. client = std::move(temp_client);
  699. capture = std::move(temp_capture);
  700. if (rtwq_supported) {
  701. HRESULT hr = rtwq_put_waiting_work_item(receiveSignal, 0, sampleReadyAsyncResult, nullptr);
  702. if (FAILED(hr)) {
  703. capture.Clear();
  704. client.Clear();
  705. throw HRError("RtwqPutWaitingWorkItem failed", hr);
  706. }
  707. hr = rtwq_put_waiting_work_item(restartSignal, 0, restartAsyncResult, nullptr);
  708. if (FAILED(hr)) {
  709. capture.Clear();
  710. client.Clear();
  711. throw HRError("RtwqPutWaitingWorkItem failed", hr);
  712. }
  713. }
  714. blog(LOG_INFO, "WASAPI: Device '%s' [%" PRIu32 " Hz] initialized (source: %s)", device_name.c_str(), sampleRate,
  715. obs_source_get_name(source));
  716. if (sourceType == SourceType::ProcessOutput && !hooked) {
  717. hooked = true;
  718. signal_handler_t *sh = obs_source_get_signal_handler(source);
  719. calldata_t data = {0};
  720. struct dstr title = {0};
  721. struct dstr window_class = {0};
  722. struct dstr executable = {0};
  723. ms_get_window_title(&title, hwnd);
  724. ms_get_window_class(&window_class, hwnd);
  725. ms_get_window_exe(&executable, hwnd);
  726. calldata_set_ptr(&data, "source", source);
  727. calldata_set_string(&data, "title", title.array);
  728. calldata_set_string(&data, "class", window_class.array);
  729. calldata_set_string(&data, "executable", executable.array);
  730. signal_handler_signal(sh, "hooked", &data);
  731. dstr_free(&title);
  732. dstr_free(&window_class);
  733. dstr_free(&executable);
  734. calldata_free(&data);
  735. }
  736. }
  737. bool WASAPISource::TryInitialize()
  738. {
  739. bool success = false;
  740. try {
  741. Initialize();
  742. success = true;
  743. } catch (HRError &error) {
  744. if (!previouslyFailed) {
  745. blog(LOG_WARNING, "[WASAPISource::TryInitialize]:[%s] %s: %lX",
  746. device_name.empty() ? device_id.c_str() : device_name.c_str(), error.str, error.hr);
  747. }
  748. } catch (const char *error) {
  749. if (!previouslyFailed) {
  750. blog(LOG_WARNING, "[WASAPISource::TryInitialize]:[%s] %s",
  751. device_name.empty() ? device_id.c_str() : device_name.c_str(), error);
  752. }
  753. }
  754. previouslyFailed = !success;
  755. return success;
  756. }
  757. DWORD WINAPI WASAPISource::ReconnectThread(LPVOID param)
  758. {
  759. os_set_thread_name("win-wasapi: reconnect thread");
  760. WASAPISource *source = (WASAPISource *)param;
  761. const HANDLE sigs[] = {
  762. source->reconnectExitSignal,
  763. source->reconnectSignal,
  764. };
  765. const HANDLE reconnect_sigs[] = {
  766. source->reconnectExitSignal,
  767. source->stopSignal,
  768. };
  769. bool exit = false;
  770. while (!exit) {
  771. const DWORD ret = WaitForMultipleObjects(_countof(sigs), sigs, false, INFINITE);
  772. switch (ret) {
  773. case WAIT_OBJECT_0:
  774. exit = true;
  775. break;
  776. default:
  777. assert(ret == (WAIT_OBJECT_0 + 1));
  778. if (source->reconnectDuration > 0) {
  779. WaitForMultipleObjects(_countof(reconnect_sigs), reconnect_sigs, false,
  780. source->reconnectDuration);
  781. }
  782. source->Start();
  783. }
  784. }
  785. return 0;
  786. }
  787. bool WASAPISource::ProcessCaptureData()
  788. {
  789. HRESULT res;
  790. LPBYTE buffer;
  791. UINT32 frames;
  792. DWORD flags;
  793. UINT64 pos, ts;
  794. UINT captureSize = 0;
  795. while (true) {
  796. if ((sourceType == SourceType::ProcessOutput) && !IsWindow(hwnd)) {
  797. blog(LOG_WARNING, "[WASAPISource::ProcessCaptureData] window disappeared");
  798. return false;
  799. }
  800. res = capture->GetNextPacketSize(&captureSize);
  801. if (FAILED(res)) {
  802. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  803. blog(LOG_WARNING,
  804. "[WASAPISource::ProcessCaptureData]"
  805. " capture->GetNextPacketSize"
  806. " failed: %lX",
  807. res);
  808. return false;
  809. }
  810. if (!captureSize)
  811. break;
  812. res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);
  813. if (FAILED(res)) {
  814. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  815. blog(LOG_WARNING,
  816. "[WASAPISource::ProcessCaptureData]"
  817. " capture->GetBuffer"
  818. " failed: %lX",
  819. res);
  820. return false;
  821. }
  822. if (!sawBadTimestamp && flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR) {
  823. blog(LOG_WARNING, "[WASAPISource::ProcessCaptureData]"
  824. " Timestamp error!");
  825. sawBadTimestamp = true;
  826. }
  827. if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
  828. /* buffer size = frame size * number of frames
  829. * frame size = channels * sample size
  830. * sample size = 4 bytes (always float per InitFormat) */
  831. uint32_t requiredBufSize = get_audio_channels(speakers) * frames * 4;
  832. if (silence.size() < requiredBufSize)
  833. silence.resize(requiredBufSize);
  834. buffer = silence.data();
  835. }
  836. obs_source_audio data = {};
  837. data.data[0] = buffer;
  838. data.frames = frames;
  839. data.speakers = speakers;
  840. data.samples_per_sec = sampleRate;
  841. data.format = format;
  842. if (sourceType == SourceType::ProcessOutput) {
  843. data.timestamp = ts * 100;
  844. } else {
  845. data.timestamp = useDeviceTiming ? ts * 100 : os_gettime_ns();
  846. if (!useDeviceTiming)
  847. data.timestamp -= util_mul_div64(frames, UINT64_C(1000000000), sampleRate);
  848. }
  849. if (reroute_target) {
  850. obs_source_t *target = obs_weak_source_get_source(reroute_target);
  851. if (target) {
  852. obs_source_output_audio(target, &data);
  853. obs_source_release(target);
  854. }
  855. } else {
  856. obs_source_output_audio(source, &data);
  857. }
  858. capture->ReleaseBuffer(frames);
  859. }
  860. return true;
  861. }
  862. #define RECONNECT_INTERVAL 3000
  863. DWORD WINAPI WASAPISource::CaptureThread(LPVOID param)
  864. {
  865. os_set_thread_name("win-wasapi: capture thread");
  866. const HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
  867. const bool com_initialized = SUCCEEDED(hr);
  868. if (!com_initialized) {
  869. blog(LOG_ERROR,
  870. "[WASAPISource::CaptureThread]"
  871. " CoInitializeEx failed: 0x%08X",
  872. hr);
  873. }
  874. DWORD unused = 0;
  875. const HANDLE handle = AvSetMmThreadCharacteristics(L"Audio", &unused);
  876. WASAPISource *source = (WASAPISource *)param;
  877. const HANDLE inactive_sigs[] = {
  878. source->exitSignal,
  879. source->stopSignal,
  880. source->initSignal,
  881. };
  882. const HANDLE active_sigs[] = {
  883. source->exitSignal,
  884. source->stopSignal,
  885. source->receiveSignal,
  886. source->restartSignal,
  887. };
  888. DWORD sig_count = _countof(inactive_sigs);
  889. const HANDLE *sigs = inactive_sigs;
  890. bool exit = false;
  891. while (!exit) {
  892. bool idle = false;
  893. bool stop = false;
  894. bool reconnect = false;
  895. do {
  896. /* Windows 7 does not seem to wake up for LOOPBACK */
  897. const DWORD dwMilliseconds =
  898. ((sigs == active_sigs) && (source->sourceType != SourceType::Input)) ? 10 : INFINITE;
  899. const DWORD ret = WaitForMultipleObjects(sig_count, sigs, false, dwMilliseconds);
  900. switch (ret) {
  901. case WAIT_OBJECT_0: {
  902. exit = true;
  903. stop = true;
  904. idle = true;
  905. break;
  906. }
  907. case WAIT_OBJECT_0 + 1:
  908. stop = true;
  909. idle = true;
  910. break;
  911. case WAIT_OBJECT_0 + 2:
  912. case WAIT_TIMEOUT:
  913. if (sigs == inactive_sigs) {
  914. assert(ret != WAIT_TIMEOUT);
  915. if (source->TryInitialize()) {
  916. sig_count = _countof(active_sigs);
  917. sigs = active_sigs;
  918. } else {
  919. if (source->reconnectDuration == 0) {
  920. blog(LOG_INFO,
  921. "WASAPI: Device '%s' failed to start (source: %s)",
  922. source->device_id.c_str(),
  923. obs_source_get_name(source->source));
  924. }
  925. stop = true;
  926. reconnect = true;
  927. source->reconnectDuration = RECONNECT_INTERVAL;
  928. }
  929. } else {
  930. stop = !source->ProcessCaptureData();
  931. if (stop) {
  932. blog(LOG_INFO, "Device '%s' invalidated. Retrying (source: %s)",
  933. source->device_name.c_str(), obs_source_get_name(source->source));
  934. if (source->sourceType == SourceType::ProcessOutput && source->hooked) {
  935. source->hooked = false;
  936. signal_handler_t *sh =
  937. obs_source_get_signal_handler(source->source);
  938. calldata_t data = {0};
  939. calldata_set_ptr(&data, "source", source->source);
  940. signal_handler_signal(sh, "unhooked", &data);
  941. calldata_free(&data);
  942. }
  943. stop = true;
  944. reconnect = true;
  945. source->reconnectDuration = RECONNECT_INTERVAL;
  946. }
  947. }
  948. break;
  949. default:
  950. assert(sigs == active_sigs);
  951. assert(ret == WAIT_OBJECT_0 + 3);
  952. stop = true;
  953. reconnect = true;
  954. source->reconnectDuration = 0;
  955. ResetEvent(source->restartSignal);
  956. }
  957. } while (!stop);
  958. sig_count = _countof(inactive_sigs);
  959. sigs = inactive_sigs;
  960. if (source->client) {
  961. source->client->Stop();
  962. source->capture.Clear();
  963. source->client.Clear();
  964. }
  965. if (idle) {
  966. SetEvent(source->idleSignal);
  967. } else if (reconnect) {
  968. blog(LOG_INFO, "Device '%s' invalidated. Retrying (source: %s)", source->device_name.c_str(),
  969. obs_source_get_name(source->source));
  970. if (source->sourceType == SourceType::ProcessOutput && source->hooked) {
  971. source->hooked = false;
  972. signal_handler_t *sh = obs_source_get_signal_handler(source->source);
  973. calldata_t data = {0};
  974. calldata_set_ptr(&data, "source", source->source);
  975. signal_handler_signal(sh, "unhooked", &data);
  976. calldata_free(&data);
  977. }
  978. SetEvent(source->reconnectSignal);
  979. }
  980. }
  981. if (handle)
  982. AvRevertMmThreadCharacteristics(handle);
  983. if (com_initialized)
  984. CoUninitialize();
  985. return 0;
  986. }
  987. void WASAPISource::SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id)
  988. {
  989. if (!isDefaultDevice)
  990. return;
  991. const bool input = sourceType == SourceType::Input;
  992. const EDataFlow expectedFlow = input ? eCapture : eRender;
  993. const ERole expectedRole = input ? eCommunications : eConsole;
  994. if (flow != expectedFlow || role != expectedRole)
  995. return;
  996. if (id) {
  997. if (default_id.compare(id) == 0)
  998. return;
  999. default_id = id;
  1000. } else {
  1001. if (default_id.empty())
  1002. return;
  1003. default_id.clear();
  1004. }
  1005. blog(LOG_INFO, "WASAPI: Default %s device changed", input ? "input" : "output");
  1006. SetEvent(restartSignal);
  1007. }
  1008. void WASAPISource::OnStartCapture()
  1009. {
  1010. const DWORD ret = WaitForSingleObject(stopSignal, 0);
  1011. switch (ret) {
  1012. case WAIT_OBJECT_0:
  1013. SetEvent(idleSignal);
  1014. break;
  1015. default:
  1016. assert(ret == WAIT_TIMEOUT);
  1017. if (!TryInitialize()) {
  1018. if (reconnectDuration == 0) {
  1019. blog(LOG_INFO, "WASAPI: Device '%s' failed to start (source: %s)", device_id.c_str(),
  1020. obs_source_get_name(source));
  1021. }
  1022. reconnectDuration = RECONNECT_INTERVAL;
  1023. SetEvent(reconnectSignal);
  1024. }
  1025. }
  1026. }
  1027. void WASAPISource::OnSampleReady()
  1028. {
  1029. bool stop = false;
  1030. bool reconnect = false;
  1031. if (!ProcessCaptureData()) {
  1032. stop = true;
  1033. reconnect = true;
  1034. reconnectDuration = RECONNECT_INTERVAL;
  1035. }
  1036. if (WaitForSingleObject(restartSignal, 0) == WAIT_OBJECT_0) {
  1037. stop = true;
  1038. reconnect = true;
  1039. reconnectDuration = 0;
  1040. ResetEvent(restartSignal);
  1041. rtwq_put_waiting_work_item(restartSignal, 0, restartAsyncResult, nullptr);
  1042. }
  1043. if (WaitForSingleObject(stopSignal, 0) == WAIT_OBJECT_0) {
  1044. stop = true;
  1045. reconnect = false;
  1046. }
  1047. if (!stop) {
  1048. if (FAILED(rtwq_put_waiting_work_item(receiveSignal, 0, sampleReadyAsyncResult, nullptr))) {
  1049. blog(LOG_ERROR, "Could not requeue sample receive work");
  1050. stop = true;
  1051. reconnect = true;
  1052. reconnectDuration = RECONNECT_INTERVAL;
  1053. }
  1054. }
  1055. if (stop) {
  1056. client->Stop();
  1057. capture.Clear();
  1058. client.Clear();
  1059. if (reconnect) {
  1060. blog(LOG_INFO, "Device '%s' invalidated. Retrying (source: %s)", device_name.c_str(),
  1061. obs_source_get_name(source));
  1062. SetEvent(reconnectSignal);
  1063. if (sourceType == SourceType::ProcessOutput && hooked) {
  1064. hooked = false;
  1065. signal_handler_t *sh = obs_source_get_signal_handler(source);
  1066. calldata_t data = {0};
  1067. calldata_set_ptr(&data, "source", source);
  1068. signal_handler_signal(sh, "unhooked", &data);
  1069. calldata_free(&data);
  1070. }
  1071. } else {
  1072. SetEvent(idleSignal);
  1073. }
  1074. }
  1075. }
  1076. void WASAPISource::OnRestart()
  1077. {
  1078. SetEvent(receiveSignal);
  1079. }
  1080. bool WASAPISource::GetHooked()
  1081. {
  1082. return hooked;
  1083. }
  1084. HWND WASAPISource::GetHwnd()
  1085. {
  1086. return hwnd;
  1087. }
  1088. /* ------------------------------------------------------------------------- */
  1089. static const char *GetWASAPIInputName(void *)
  1090. {
  1091. return obs_module_text("AudioInput");
  1092. }
  1093. static const char *GetWASAPIDeviceOutputName(void *)
  1094. {
  1095. return obs_module_text("AudioOutput");
  1096. }
  1097. static const char *GetWASAPIProcessOutputName(void *)
  1098. {
  1099. return obs_module_text("ApplicationAudioCapture");
  1100. }
  1101. static void GetWASAPIDefaultsInput(obs_data_t *settings)
  1102. {
  1103. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  1104. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, false);
  1105. }
  1106. static void GetWASAPIDefaultsDeviceOutput(obs_data_t *settings)
  1107. {
  1108. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  1109. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, true);
  1110. }
  1111. static void GetWASAPIDefaultsProcessOutput(obs_data_t *) {}
  1112. static void wasapi_get_hooked(void *data, calldata_t *cd)
  1113. {
  1114. WASAPISource *wasapi_source = reinterpret_cast<WASAPISource *>(data);
  1115. if (!wasapi_source)
  1116. return;
  1117. bool hooked = wasapi_source->GetHooked();
  1118. HWND hwnd = wasapi_source->GetHwnd();
  1119. if (hooked && hwnd) {
  1120. calldata_set_bool(cd, "hooked", true);
  1121. struct dstr title = {0};
  1122. struct dstr window_class = {0};
  1123. struct dstr executable = {0};
  1124. ms_get_window_title(&title, hwnd);
  1125. ms_get_window_class(&window_class, hwnd);
  1126. ms_get_window_exe(&executable, hwnd);
  1127. calldata_set_string(cd, "title", title.array);
  1128. calldata_set_string(cd, "class", window_class.array);
  1129. calldata_set_string(cd, "executable", executable.array);
  1130. dstr_free(&title);
  1131. dstr_free(&window_class);
  1132. dstr_free(&executable);
  1133. } else {
  1134. calldata_set_bool(cd, "hooked", false);
  1135. calldata_set_string(cd, "title", "");
  1136. calldata_set_string(cd, "class", "");
  1137. calldata_set_string(cd, "executable", "");
  1138. }
  1139. }
  1140. static void wasapi_reroute_audio(void *data, calldata_t *cd)
  1141. {
  1142. auto wasapi_source = static_cast<WASAPISource *>(data);
  1143. if (!wasapi_source)
  1144. return;
  1145. obs_source_t *target = nullptr;
  1146. calldata_get_ptr(cd, "target", &target);
  1147. wasapi_source->SetRerouteTarget(target);
  1148. }
  1149. static void *CreateWASAPISource(obs_data_t *settings, obs_source_t *source, SourceType type)
  1150. {
  1151. try {
  1152. if (type != SourceType::ProcessOutput) {
  1153. return new WASAPISource(settings, source, type);
  1154. } else {
  1155. WASAPISource *wasapi_source = new WASAPISource(settings, source, type);
  1156. if (wasapi_source) {
  1157. signal_handler_t *sh = obs_source_get_signal_handler(source);
  1158. signal_handler_add(sh, "void unhooked(ptr source)");
  1159. signal_handler_add(
  1160. sh, "void hooked(ptr source, string title, string class, string executable)");
  1161. proc_handler_t *ph = obs_source_get_proc_handler(source);
  1162. proc_handler_add(
  1163. ph,
  1164. "void get_hooked(out bool hooked, out string title, out string class, out string executable)",
  1165. wasapi_get_hooked, wasapi_source);
  1166. proc_handler_add(ph, "void reroute_audio(in ptr target)", wasapi_reroute_audio,
  1167. wasapi_source);
  1168. }
  1169. return wasapi_source;
  1170. }
  1171. } catch (const char *error) {
  1172. blog(LOG_ERROR, "[CreateWASAPISource] %s", error);
  1173. }
  1174. return nullptr;
  1175. }
  1176. static void *CreateWASAPIInput(obs_data_t *settings, obs_source_t *source)
  1177. {
  1178. return CreateWASAPISource(settings, source, SourceType::Input);
  1179. }
  1180. static void *CreateWASAPIDeviceOutput(obs_data_t *settings, obs_source_t *source)
  1181. {
  1182. return CreateWASAPISource(settings, source, SourceType::DeviceOutput);
  1183. }
  1184. static void *CreateWASAPIProcessOutput(obs_data_t *settings, obs_source_t *source)
  1185. {
  1186. return CreateWASAPISource(settings, source, SourceType::ProcessOutput);
  1187. }
  1188. static void DestroyWASAPISource(void *obj)
  1189. {
  1190. delete static_cast<WASAPISource *>(obj);
  1191. }
  1192. static void UpdateWASAPISource(void *obj, obs_data_t *settings)
  1193. {
  1194. static_cast<WASAPISource *>(obj)->Update(settings);
  1195. }
  1196. static void ActivateWASAPISource(void *obj)
  1197. {
  1198. static_cast<WASAPISource *>(obj)->Activate();
  1199. }
  1200. static void DeactivateWASAPISource(void *obj)
  1201. {
  1202. static_cast<WASAPISource *>(obj)->Deactivate();
  1203. }
  1204. static bool UpdateWASAPIMethod(obs_properties_t *props, obs_property_t *, obs_data_t *settings)
  1205. {
  1206. WASAPISource *source = (WASAPISource *)obs_properties_get_param(props);
  1207. if (!source)
  1208. return false;
  1209. source->Update(settings);
  1210. return true;
  1211. }
  1212. static obs_properties_t *GetWASAPIPropertiesInput(void *)
  1213. {
  1214. obs_properties_t *props = obs_properties_create();
  1215. vector<AudioDeviceInfo> devices;
  1216. obs_property_t *device_prop = obs_properties_add_list(props, OPT_DEVICE_ID, obs_module_text("Device"),
  1217. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1218. GetWASAPIAudioDevices(devices, true);
  1219. if (devices.size())
  1220. obs_property_list_add_string(device_prop, obs_module_text("Default"), "default");
  1221. for (size_t i = 0; i < devices.size(); i++) {
  1222. AudioDeviceInfo &device = devices[i];
  1223. obs_property_list_add_string(device_prop, device.name.c_str(), device.id.c_str());
  1224. }
  1225. obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming"));
  1226. return props;
  1227. }
  1228. static obs_properties_t *GetWASAPIPropertiesDeviceOutput(void *)
  1229. {
  1230. obs_properties_t *props = obs_properties_create();
  1231. vector<AudioDeviceInfo> devices;
  1232. obs_property_t *device_prop = obs_properties_add_list(props, OPT_DEVICE_ID, obs_module_text("Device"),
  1233. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1234. GetWASAPIAudioDevices(devices, false);
  1235. if (devices.size())
  1236. obs_property_list_add_string(device_prop, obs_module_text("Default"), "default");
  1237. for (size_t i = 0; i < devices.size(); i++) {
  1238. AudioDeviceInfo &device = devices[i];
  1239. obs_property_list_add_string(device_prop, device.name.c_str(), device.id.c_str());
  1240. }
  1241. obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING, obs_module_text("UseDeviceTiming"));
  1242. return props;
  1243. }
  1244. static bool wasapi_window_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  1245. {
  1246. WASAPISource *source = (WASAPISource *)obs_properties_get_param(props);
  1247. if (!source)
  1248. return false;
  1249. source->OnWindowChanged(settings);
  1250. ms_check_window_property_setting(props, p, settings, "window", 0);
  1251. return true;
  1252. }
  1253. static obs_properties_t *GetWASAPIPropertiesProcessOutput(void *data)
  1254. {
  1255. obs_properties_t *props = obs_properties_create();
  1256. obs_properties_set_param(props, data, NULL);
  1257. obs_property_t *const window_prop = obs_properties_add_list(props, OPT_WINDOW, obs_module_text("Window"),
  1258. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  1259. ms_fill_window_list(window_prop, INCLUDE_MINIMIZED, nullptr);
  1260. obs_property_set_modified_callback(window_prop, wasapi_window_changed);
  1261. obs_property_t *const priority_prop = obs_properties_add_list(props, OPT_PRIORITY, obs_module_text("Priority"),
  1262. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  1263. obs_property_list_add_int(priority_prop, obs_module_text("Priority.Title"), WINDOW_PRIORITY_TITLE);
  1264. obs_property_list_add_int(priority_prop, obs_module_text("Priority.Class"), WINDOW_PRIORITY_CLASS);
  1265. obs_property_list_add_int(priority_prop, obs_module_text("Priority.Exe"), WINDOW_PRIORITY_EXE);
  1266. return props;
  1267. }
  1268. void RegisterWASAPIInput()
  1269. {
  1270. obs_source_info info = {};
  1271. info.id = "wasapi_input_capture";
  1272. info.type = OBS_SOURCE_TYPE_INPUT;
  1273. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE;
  1274. info.get_name = GetWASAPIInputName;
  1275. info.create = CreateWASAPIInput;
  1276. info.destroy = DestroyWASAPISource;
  1277. info.update = UpdateWASAPISource;
  1278. info.activate = ActivateWASAPISource;
  1279. info.deactivate = DeactivateWASAPISource;
  1280. info.get_defaults = GetWASAPIDefaultsInput;
  1281. info.get_properties = GetWASAPIPropertiesInput;
  1282. info.icon_type = OBS_ICON_TYPE_AUDIO_INPUT;
  1283. obs_register_source(&info);
  1284. }
  1285. void RegisterWASAPIDeviceOutput()
  1286. {
  1287. obs_source_info info = {};
  1288. info.id = "wasapi_output_capture";
  1289. info.type = OBS_SOURCE_TYPE_INPUT;
  1290. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_DO_NOT_SELF_MONITOR;
  1291. info.get_name = GetWASAPIDeviceOutputName;
  1292. info.create = CreateWASAPIDeviceOutput;
  1293. info.destroy = DestroyWASAPISource;
  1294. info.update = UpdateWASAPISource;
  1295. info.activate = ActivateWASAPISource;
  1296. info.deactivate = DeactivateWASAPISource;
  1297. info.get_defaults = GetWASAPIDefaultsDeviceOutput;
  1298. info.get_properties = GetWASAPIPropertiesDeviceOutput;
  1299. info.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT;
  1300. obs_register_source(&info);
  1301. }
  1302. void RegisterWASAPIProcessOutput()
  1303. {
  1304. obs_source_info info = {};
  1305. info.id = "wasapi_process_output_capture";
  1306. info.type = OBS_SOURCE_TYPE_INPUT;
  1307. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE | OBS_SOURCE_DO_NOT_SELF_MONITOR;
  1308. info.get_name = GetWASAPIProcessOutputName;
  1309. info.create = CreateWASAPIProcessOutput;
  1310. info.destroy = DestroyWASAPISource;
  1311. info.update = UpdateWASAPISource;
  1312. info.activate = ActivateWASAPISource;
  1313. info.deactivate = DeactivateWASAPISource;
  1314. info.get_defaults = GetWASAPIDefaultsProcessOutput;
  1315. info.get_properties = GetWASAPIPropertiesProcessOutput;
  1316. info.icon_type = OBS_ICON_TYPE_PROCESS_AUDIO_OUTPUT;
  1317. obs_register_source(&info);
  1318. }