win-wasapi.cpp 41 KB

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