win-wasapi.cpp 45 KB

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