1
0

win-wasapi.cpp 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156
  1. #include "enum-wasapi.hpp"
  2. #include <obs-module.h>
  3. #include <obs.h>
  4. #include <util/platform.h>
  5. #include <util/windows/HRError.hpp>
  6. #include <util/windows/ComPtr.hpp>
  7. #include <util/windows/WinHandle.hpp>
  8. #include <util/windows/CoTaskMemPtr.hpp>
  9. #include <util/windows/win-version.h>
  10. #include <util/threading.h>
  11. #include <util/util_uint64.h>
  12. #include <atomic>
  13. #include <cinttypes>
  14. #include <avrt.h>
  15. #include <RTWorkQ.h>
  16. using namespace std;
  17. #define OPT_DEVICE_ID "device_id"
  18. #define OPT_USE_DEVICE_TIMING "use_device_timing"
  19. static void GetWASAPIDefaults(obs_data_t *settings);
  20. #define OBS_KSAUDIO_SPEAKER_4POINT1 \
  21. (KSAUDIO_SPEAKER_SURROUND | SPEAKER_LOW_FREQUENCY)
  22. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqUnlockWorkQueue)(DWORD);
  23. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqLockSharedWorkQueue)(PCWSTR usageClass,
  24. LONG basePriority,
  25. DWORD *taskId,
  26. DWORD *id);
  27. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqCreateAsyncResult)(IUnknown *,
  28. IRtwqAsyncCallback *,
  29. IUnknown *,
  30. IRtwqAsyncResult **);
  31. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqPutWorkItem)(DWORD, LONG,
  32. IRtwqAsyncResult *);
  33. typedef HRESULT(STDAPICALLTYPE *PFN_RtwqPutWaitingWorkItem)(HANDLE, LONG,
  34. IRtwqAsyncResult *,
  35. RTWQWORKITEM_KEY *);
  36. class ARtwqAsyncCallback : public IRtwqAsyncCallback {
  37. protected:
  38. ARtwqAsyncCallback(void *source) : source(source) {}
  39. public:
  40. STDMETHOD_(ULONG, AddRef)() { return ++refCount; }
  41. STDMETHOD_(ULONG, Release)() { return --refCount; }
  42. STDMETHOD(QueryInterface)(REFIID riid, void **ppvObject)
  43. {
  44. HRESULT hr = E_NOINTERFACE;
  45. if (riid == __uuidof(IRtwqAsyncCallback) ||
  46. riid == __uuidof(IUnknown)) {
  47. *ppvObject = this;
  48. AddRef();
  49. hr = S_OK;
  50. } else {
  51. *ppvObject = NULL;
  52. }
  53. return hr;
  54. }
  55. STDMETHOD(GetParameters)
  56. (DWORD *pdwFlags, DWORD *pdwQueue)
  57. {
  58. *pdwFlags = 0;
  59. *pdwQueue = queue_id;
  60. return S_OK;
  61. }
  62. STDMETHOD(Invoke)
  63. (IRtwqAsyncResult *) override = 0;
  64. DWORD GetQueueId() const { return queue_id; }
  65. void SetQueueId(DWORD id) { queue_id = id; }
  66. protected:
  67. std::atomic<ULONG> refCount = 1;
  68. void *source;
  69. DWORD queue_id = 0;
  70. };
  71. class WASAPISource {
  72. ComPtr<IMMNotificationClient> notify;
  73. ComPtr<IMMDeviceEnumerator> enumerator;
  74. ComPtr<IAudioClient> client;
  75. ComPtr<IAudioCaptureClient> capture;
  76. obs_source_t *source;
  77. wstring default_id;
  78. string device_id;
  79. string device_name;
  80. PFN_RtwqUnlockWorkQueue rtwq_unlock_work_queue = NULL;
  81. PFN_RtwqLockSharedWorkQueue rtwq_lock_shared_work_queue = NULL;
  82. PFN_RtwqCreateAsyncResult rtwq_create_async_result = NULL;
  83. PFN_RtwqPutWorkItem rtwq_put_work_item = NULL;
  84. PFN_RtwqPutWaitingWorkItem rtwq_put_waiting_work_item = NULL;
  85. bool rtwq_supported = false;
  86. const bool isInputDevice;
  87. std::atomic<bool> useDeviceTiming = false;
  88. std::atomic<bool> isDefaultDevice = false;
  89. bool previouslyFailed = false;
  90. WinHandle reconnectThread;
  91. class CallbackStartCapture : public ARtwqAsyncCallback {
  92. public:
  93. CallbackStartCapture(WASAPISource *source)
  94. : ARtwqAsyncCallback(source)
  95. {
  96. }
  97. STDMETHOD(Invoke)
  98. (IRtwqAsyncResult *) override
  99. {
  100. ((WASAPISource *)source)->OnStartCapture();
  101. return S_OK;
  102. }
  103. } startCapture;
  104. ComPtr<IRtwqAsyncResult> startCaptureAsyncResult;
  105. class CallbackSampleReady : public ARtwqAsyncCallback {
  106. public:
  107. CallbackSampleReady(WASAPISource *source)
  108. : ARtwqAsyncCallback(source)
  109. {
  110. }
  111. STDMETHOD(Invoke)
  112. (IRtwqAsyncResult *) override
  113. {
  114. ((WASAPISource *)source)->OnSampleReady();
  115. return S_OK;
  116. }
  117. } sampleReady;
  118. ComPtr<IRtwqAsyncResult> sampleReadyAsyncResult;
  119. class CallbackRestart : public ARtwqAsyncCallback {
  120. public:
  121. CallbackRestart(WASAPISource *source)
  122. : ARtwqAsyncCallback(source)
  123. {
  124. }
  125. STDMETHOD(Invoke)
  126. (IRtwqAsyncResult *) override
  127. {
  128. ((WASAPISource *)source)->OnRestart();
  129. return S_OK;
  130. }
  131. } restart;
  132. ComPtr<IRtwqAsyncResult> restartAsyncResult;
  133. WinHandle captureThread;
  134. WinHandle idleSignal;
  135. WinHandle stopSignal;
  136. WinHandle receiveSignal;
  137. WinHandle restartSignal;
  138. WinHandle exitSignal;
  139. WinHandle initSignal;
  140. DWORD reconnectDuration = 0;
  141. WinHandle reconnectSignal;
  142. speaker_layout speakers;
  143. audio_format format;
  144. uint32_t sampleRate;
  145. static DWORD WINAPI ReconnectThread(LPVOID param);
  146. static DWORD WINAPI CaptureThread(LPVOID param);
  147. bool ProcessCaptureData();
  148. void Start();
  149. void Stop();
  150. static ComPtr<IMMDevice> InitDevice(IMMDeviceEnumerator *enumerator,
  151. bool isDefaultDevice,
  152. bool isInputDevice,
  153. const string device_id);
  154. static ComPtr<IAudioClient> InitClient(IMMDevice *device,
  155. bool isInputDevice,
  156. enum speaker_layout &speakers,
  157. enum audio_format &format,
  158. uint32_t &sampleRate);
  159. static void InitFormat(const WAVEFORMATEX *wfex,
  160. enum speaker_layout &speakers,
  161. enum audio_format &format, uint32_t &sampleRate);
  162. static void ClearBuffer(IMMDevice *device);
  163. static ComPtr<IAudioCaptureClient> InitCapture(IAudioClient *client,
  164. HANDLE receiveSignal);
  165. void Initialize();
  166. bool TryInitialize();
  167. void UpdateSettings(obs_data_t *settings);
  168. public:
  169. WASAPISource(obs_data_t *settings, obs_source_t *source_, bool input);
  170. ~WASAPISource();
  171. void Update(obs_data_t *settings);
  172. void SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id);
  173. void OnStartCapture();
  174. void OnSampleReady();
  175. void OnRestart();
  176. };
  177. class WASAPINotify : public IMMNotificationClient {
  178. long refs = 0; /* auto-incremented to 1 by ComPtr */
  179. WASAPISource *source;
  180. public:
  181. WASAPINotify(WASAPISource *source_) : source(source_) {}
  182. STDMETHODIMP_(ULONG) AddRef()
  183. {
  184. return (ULONG)os_atomic_inc_long(&refs);
  185. }
  186. STDMETHODIMP_(ULONG) STDMETHODCALLTYPE Release()
  187. {
  188. long val = os_atomic_dec_long(&refs);
  189. if (val == 0)
  190. delete this;
  191. return (ULONG)val;
  192. }
  193. STDMETHODIMP QueryInterface(REFIID riid, void **ptr)
  194. {
  195. if (riid == IID_IUnknown) {
  196. *ptr = (IUnknown *)this;
  197. } else if (riid == __uuidof(IMMNotificationClient)) {
  198. *ptr = (IMMNotificationClient *)this;
  199. } else {
  200. *ptr = nullptr;
  201. return E_NOINTERFACE;
  202. }
  203. os_atomic_inc_long(&refs);
  204. return S_OK;
  205. }
  206. STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role,
  207. LPCWSTR id)
  208. {
  209. source->SetDefaultDevice(flow, role, id);
  210. return S_OK;
  211. }
  212. STDMETHODIMP OnDeviceAdded(LPCWSTR) { return S_OK; }
  213. STDMETHODIMP OnDeviceRemoved(LPCWSTR) { return S_OK; }
  214. STDMETHODIMP OnDeviceStateChanged(LPCWSTR, DWORD) { return S_OK; }
  215. STDMETHODIMP OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY)
  216. {
  217. return S_OK;
  218. }
  219. };
  220. WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_,
  221. bool input)
  222. : source(source_),
  223. isInputDevice(input),
  224. startCapture(this),
  225. sampleReady(this),
  226. restart(this)
  227. {
  228. UpdateSettings(settings);
  229. idleSignal = CreateEvent(nullptr, true, false, nullptr);
  230. if (!idleSignal.Valid())
  231. throw "Could not create idle signal";
  232. stopSignal = CreateEvent(nullptr, true, false, nullptr);
  233. if (!stopSignal.Valid())
  234. throw "Could not create stop signal";
  235. receiveSignal = CreateEvent(nullptr, false, false, nullptr);
  236. if (!receiveSignal.Valid())
  237. throw "Could not create receive signal";
  238. restartSignal = CreateEvent(nullptr, true, false, nullptr);
  239. if (!restartSignal.Valid())
  240. throw "Could not create restart signal";
  241. exitSignal = CreateEvent(nullptr, true, false, nullptr);
  242. if (!exitSignal.Valid())
  243. throw "Could not create exit signal";
  244. initSignal = CreateEvent(nullptr, false, false, nullptr);
  245. if (!initSignal.Valid())
  246. throw "Could not create init signal";
  247. reconnectSignal = CreateEvent(nullptr, false, false, nullptr);
  248. if (!reconnectSignal.Valid())
  249. throw "Could not create reconnect signal";
  250. reconnectThread = CreateThread(
  251. nullptr, 0, WASAPISource::ReconnectThread, this, 0, nullptr);
  252. if (!reconnectThread.Valid())
  253. throw "Failed to create reconnect thread";
  254. notify = new WASAPINotify(this);
  255. if (!notify)
  256. throw "Could not create WASAPINotify";
  257. HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
  258. CLSCTX_ALL,
  259. IID_PPV_ARGS(enumerator.Assign()));
  260. if (FAILED(hr))
  261. throw HRError("Failed to create enumerator", hr);
  262. hr = enumerator->RegisterEndpointNotificationCallback(notify);
  263. if (FAILED(hr))
  264. throw HRError("Failed to register endpoint callback", hr);
  265. /* OBS will already load DLL on startup if it exists */
  266. const HMODULE rtwq_module = GetModuleHandle(L"RTWorkQ.dll");
  267. // while RTWQ was introduced in Win 8.1, it silently fails
  268. // to capture Desktop Audio for some reason. Disable for now.
  269. if (get_win_ver_int() >= _WIN32_WINNT_WIN10)
  270. rtwq_supported = rtwq_module != NULL;
  271. if (rtwq_supported) {
  272. rtwq_unlock_work_queue =
  273. (PFN_RtwqUnlockWorkQueue)GetProcAddress(
  274. rtwq_module, "RtwqUnlockWorkQueue");
  275. rtwq_lock_shared_work_queue =
  276. (PFN_RtwqLockSharedWorkQueue)GetProcAddress(
  277. rtwq_module, "RtwqLockSharedWorkQueue");
  278. rtwq_create_async_result =
  279. (PFN_RtwqCreateAsyncResult)GetProcAddress(
  280. rtwq_module, "RtwqCreateAsyncResult");
  281. rtwq_put_work_item = (PFN_RtwqPutWorkItem)GetProcAddress(
  282. rtwq_module, "RtwqPutWorkItem");
  283. rtwq_put_waiting_work_item =
  284. (PFN_RtwqPutWaitingWorkItem)GetProcAddress(
  285. rtwq_module, "RtwqPutWaitingWorkItem");
  286. try {
  287. hr = rtwq_create_async_result(nullptr, &startCapture,
  288. nullptr,
  289. &startCaptureAsyncResult);
  290. if (FAILED(hr)) {
  291. throw HRError(
  292. "Could not create startCaptureAsyncResult",
  293. hr);
  294. }
  295. hr = rtwq_create_async_result(nullptr, &sampleReady,
  296. nullptr,
  297. &sampleReadyAsyncResult);
  298. if (FAILED(hr)) {
  299. throw HRError(
  300. "Could not create sampleReadyAsyncResult",
  301. hr);
  302. }
  303. hr = rtwq_create_async_result(nullptr, &restart,
  304. nullptr,
  305. &restartAsyncResult);
  306. if (FAILED(hr)) {
  307. throw HRError(
  308. "Could not create restartAsyncResult",
  309. hr);
  310. }
  311. DWORD taskId = 0;
  312. DWORD id = 0;
  313. hr = rtwq_lock_shared_work_queue(L"Capture", 0, &taskId,
  314. &id);
  315. if (FAILED(hr)) {
  316. throw HRError("RtwqLockSharedWorkQueue failed",
  317. hr);
  318. }
  319. startCapture.SetQueueId(id);
  320. sampleReady.SetQueueId(id);
  321. restart.SetQueueId(id);
  322. } catch (HRError &err) {
  323. blog(LOG_ERROR, "RTWQ setup failed: %s (0x%08X)",
  324. err.str, err.hr);
  325. rtwq_supported = false;
  326. }
  327. }
  328. if (!rtwq_supported) {
  329. captureThread = CreateThread(nullptr, 0,
  330. WASAPISource::CaptureThread, this,
  331. 0, nullptr);
  332. if (!captureThread.Valid()) {
  333. enumerator->UnregisterEndpointNotificationCallback(
  334. notify);
  335. throw "Failed to create capture thread";
  336. }
  337. }
  338. Start();
  339. }
  340. void WASAPISource::Start()
  341. {
  342. if (rtwq_supported) {
  343. rtwq_put_work_item(startCapture.GetQueueId(), 0,
  344. startCaptureAsyncResult);
  345. } else {
  346. SetEvent(initSignal);
  347. }
  348. }
  349. void WASAPISource::Stop()
  350. {
  351. SetEvent(stopSignal);
  352. blog(LOG_INFO, "WASAPI: Device '%s' Terminated", device_name.c_str());
  353. if (rtwq_supported)
  354. SetEvent(receiveSignal);
  355. WaitForSingleObject(idleSignal, INFINITE);
  356. SetEvent(exitSignal);
  357. WaitForSingleObject(reconnectThread, INFINITE);
  358. if (rtwq_supported)
  359. rtwq_unlock_work_queue(sampleReady.GetQueueId());
  360. else
  361. WaitForSingleObject(captureThread, INFINITE);
  362. }
  363. WASAPISource::~WASAPISource()
  364. {
  365. enumerator->UnregisterEndpointNotificationCallback(notify);
  366. Stop();
  367. }
  368. void WASAPISource::UpdateSettings(obs_data_t *settings)
  369. {
  370. device_id = obs_data_get_string(settings, OPT_DEVICE_ID);
  371. useDeviceTiming = obs_data_get_bool(settings, OPT_USE_DEVICE_TIMING);
  372. isDefaultDevice = _strcmpi(device_id.c_str(), "default") == 0;
  373. blog(LOG_INFO,
  374. "[win-wasapi: '%s'] update settings:\n"
  375. "\tdevice id: %s\n"
  376. "\tuse device timing: %d",
  377. obs_source_get_name(source), device_id.c_str(),
  378. (int)useDeviceTiming);
  379. }
  380. void WASAPISource::Update(obs_data_t *settings)
  381. {
  382. const string newDevice = obs_data_get_string(settings, OPT_DEVICE_ID);
  383. const bool restart = newDevice.compare(device_id) != 0;
  384. UpdateSettings(settings);
  385. if (restart)
  386. SetEvent(restartSignal);
  387. }
  388. ComPtr<IMMDevice> WASAPISource::InitDevice(IMMDeviceEnumerator *enumerator,
  389. bool isDefaultDevice,
  390. bool isInputDevice,
  391. const string device_id)
  392. {
  393. ComPtr<IMMDevice> device;
  394. if (isDefaultDevice) {
  395. HRESULT res = enumerator->GetDefaultAudioEndpoint(
  396. isInputDevice ? eCapture : eRender,
  397. isInputDevice ? eCommunications : eConsole,
  398. device.Assign());
  399. if (FAILED(res))
  400. throw HRError("Failed GetDefaultAudioEndpoint", res);
  401. } else {
  402. wchar_t *w_id;
  403. os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
  404. if (!w_id)
  405. throw "Failed to widen device id string";
  406. const HRESULT res =
  407. enumerator->GetDevice(w_id, device.Assign());
  408. bfree(w_id);
  409. if (FAILED(res))
  410. throw HRError("Failed to enumerate device", res);
  411. }
  412. return device;
  413. }
  414. #define BUFFER_TIME_100NS (5 * 10000000)
  415. ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device,
  416. bool isInputDevice,
  417. enum speaker_layout &speakers,
  418. enum audio_format &format,
  419. uint32_t &sampleRate)
  420. {
  421. ComPtr<IAudioClient> client;
  422. HRESULT res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
  423. nullptr, (void **)client.Assign());
  424. if (FAILED(res))
  425. throw HRError("Failed to activate client context", res);
  426. CoTaskMemPtr<WAVEFORMATEX> wfex;
  427. res = client->GetMixFormat(&wfex);
  428. if (FAILED(res))
  429. throw HRError("Failed to get mix format", res);
  430. InitFormat(wfex, speakers, format, sampleRate);
  431. DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
  432. if (!isInputDevice)
  433. flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
  434. res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags,
  435. BUFFER_TIME_100NS, 0, wfex, nullptr);
  436. if (FAILED(res))
  437. throw HRError("Failed to initialize audio client", res);
  438. return client;
  439. }
  440. void WASAPISource::ClearBuffer(IMMDevice *device)
  441. {
  442. CoTaskMemPtr<WAVEFORMATEX> wfex;
  443. HRESULT res;
  444. LPBYTE buffer;
  445. UINT32 frames;
  446. ComPtr<IAudioClient> client;
  447. res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
  448. (void **)client.Assign());
  449. if (FAILED(res))
  450. throw HRError("Failed to activate client context", res);
  451. res = client->GetMixFormat(&wfex);
  452. if (FAILED(res))
  453. throw HRError("Failed to get mix format", res);
  454. res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, BUFFER_TIME_100NS,
  455. 0, wfex, nullptr);
  456. if (FAILED(res))
  457. throw HRError("Failed to initialize audio client", res);
  458. /* Silent loopback fix. Prevents audio stream from stopping and */
  459. /* messing up timestamps and other weird glitches during silence */
  460. /* by playing a silent sample all over again. */
  461. res = client->GetBufferSize(&frames);
  462. if (FAILED(res))
  463. throw HRError("Failed to get buffer size", res);
  464. ComPtr<IAudioRenderClient> render;
  465. res = client->GetService(IID_PPV_ARGS(render.Assign()));
  466. if (FAILED(res))
  467. throw HRError("Failed to get render client", res);
  468. res = render->GetBuffer(frames, &buffer);
  469. if (FAILED(res))
  470. throw HRError("Failed to get buffer", res);
  471. memset(buffer, 0, (size_t)frames * (size_t)wfex->nBlockAlign);
  472. render->ReleaseBuffer(frames, 0);
  473. }
  474. static speaker_layout ConvertSpeakerLayout(DWORD layout, WORD channels)
  475. {
  476. switch (layout) {
  477. case KSAUDIO_SPEAKER_2POINT1:
  478. return SPEAKERS_2POINT1;
  479. case KSAUDIO_SPEAKER_SURROUND:
  480. return SPEAKERS_4POINT0;
  481. case OBS_KSAUDIO_SPEAKER_4POINT1:
  482. return SPEAKERS_4POINT1;
  483. case KSAUDIO_SPEAKER_5POINT1_SURROUND:
  484. return SPEAKERS_5POINT1;
  485. case KSAUDIO_SPEAKER_7POINT1_SURROUND:
  486. return SPEAKERS_7POINT1;
  487. }
  488. return (speaker_layout)channels;
  489. }
  490. void WASAPISource::InitFormat(const WAVEFORMATEX *wfex,
  491. enum speaker_layout &speakers,
  492. enum audio_format &format, uint32_t &sampleRate)
  493. {
  494. DWORD layout = 0;
  495. if (wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
  496. WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE *)wfex;
  497. layout = ext->dwChannelMask;
  498. }
  499. /* WASAPI is always float */
  500. speakers = ConvertSpeakerLayout(layout, wfex->nChannels);
  501. format = AUDIO_FORMAT_FLOAT;
  502. sampleRate = wfex->nSamplesPerSec;
  503. }
  504. ComPtr<IAudioCaptureClient> WASAPISource::InitCapture(IAudioClient *client,
  505. HANDLE receiveSignal)
  506. {
  507. ComPtr<IAudioCaptureClient> capture;
  508. HRESULT res = client->GetService(IID_PPV_ARGS(capture.Assign()));
  509. if (FAILED(res))
  510. throw HRError("Failed to create capture context", res);
  511. res = client->SetEventHandle(receiveSignal);
  512. if (FAILED(res))
  513. throw HRError("Failed to set event handle", res);
  514. res = client->Start();
  515. if (FAILED(res))
  516. throw HRError("Failed to start capture client", res);
  517. return capture;
  518. }
  519. void WASAPISource::Initialize()
  520. {
  521. ComPtr<IMMDevice> device = InitDevice(enumerator, isDefaultDevice,
  522. isInputDevice, device_id);
  523. device_name = GetDeviceName(device);
  524. ResetEvent(receiveSignal);
  525. ComPtr<IAudioClient> temp_client =
  526. InitClient(device, isInputDevice, speakers, format, sampleRate);
  527. if (!isInputDevice)
  528. ClearBuffer(device);
  529. ComPtr<IAudioCaptureClient> temp_capture =
  530. InitCapture(temp_client, receiveSignal);
  531. client = std::move(temp_client);
  532. capture = std::move(temp_capture);
  533. if (rtwq_supported) {
  534. HRESULT hr = rtwq_put_waiting_work_item(
  535. receiveSignal, 0, sampleReadyAsyncResult, nullptr);
  536. if (FAILED(hr)) {
  537. capture.Clear();
  538. client.Clear();
  539. throw HRError("RtwqPutWaitingWorkItem failed", hr);
  540. }
  541. hr = rtwq_put_waiting_work_item(restartSignal, 0,
  542. restartAsyncResult, nullptr);
  543. if (FAILED(hr)) {
  544. capture.Clear();
  545. client.Clear();
  546. throw HRError("RtwqPutWaitingWorkItem failed", hr);
  547. }
  548. }
  549. blog(LOG_INFO, "WASAPI: Device '%s' [%" PRIu32 " Hz] initialized",
  550. device_name.c_str(), sampleRate);
  551. }
  552. bool WASAPISource::TryInitialize()
  553. {
  554. bool success = false;
  555. try {
  556. Initialize();
  557. success = true;
  558. } catch (HRError &error) {
  559. if (!previouslyFailed) {
  560. blog(LOG_WARNING,
  561. "[WASAPISource::TryInitialize]:[%s] %s: %lX",
  562. device_name.empty() ? device_id.c_str()
  563. : device_name.c_str(),
  564. error.str, error.hr);
  565. }
  566. } catch (const char *error) {
  567. if (!previouslyFailed) {
  568. blog(LOG_WARNING,
  569. "[WASAPISource::TryInitialize]:[%s] %s",
  570. device_name.empty() ? device_id.c_str()
  571. : device_name.c_str(),
  572. error);
  573. }
  574. }
  575. previouslyFailed = !success;
  576. return success;
  577. }
  578. DWORD WINAPI WASAPISource::ReconnectThread(LPVOID param)
  579. {
  580. os_set_thread_name("win-wasapi: reconnect thread");
  581. WASAPISource *source = (WASAPISource *)param;
  582. const HANDLE sigs[] = {
  583. source->exitSignal,
  584. source->reconnectSignal,
  585. };
  586. bool exit = false;
  587. while (!exit) {
  588. const DWORD ret = WaitForMultipleObjects(_countof(sigs), sigs,
  589. false, INFINITE);
  590. switch (ret) {
  591. case WAIT_OBJECT_0:
  592. exit = true;
  593. break;
  594. default:
  595. assert(ret == (WAIT_OBJECT_0 + 1));
  596. if (source->reconnectDuration > 0) {
  597. WaitForSingleObject(source->stopSignal,
  598. source->reconnectDuration);
  599. }
  600. source->Start();
  601. }
  602. }
  603. return 0;
  604. }
  605. bool WASAPISource::ProcessCaptureData()
  606. {
  607. HRESULT res;
  608. LPBYTE buffer;
  609. UINT32 frames;
  610. DWORD flags;
  611. UINT64 pos, ts;
  612. UINT captureSize = 0;
  613. while (true) {
  614. res = capture->GetNextPacketSize(&captureSize);
  615. if (FAILED(res)) {
  616. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  617. blog(LOG_WARNING,
  618. "[WASAPISource::ProcessCaptureData]"
  619. " capture->GetNextPacketSize"
  620. " failed: %lX",
  621. res);
  622. return false;
  623. }
  624. if (!captureSize)
  625. break;
  626. res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);
  627. if (FAILED(res)) {
  628. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  629. blog(LOG_WARNING,
  630. "[WASAPISource::ProcessCaptureData]"
  631. " capture->GetBuffer"
  632. " failed: %lX",
  633. res);
  634. return false;
  635. }
  636. obs_source_audio data = {};
  637. data.data[0] = (const uint8_t *)buffer;
  638. data.frames = (uint32_t)frames;
  639. data.speakers = speakers;
  640. data.samples_per_sec = sampleRate;
  641. data.format = format;
  642. data.timestamp = useDeviceTiming ? ts * 100 : os_gettime_ns();
  643. if (!useDeviceTiming)
  644. data.timestamp -= util_mul_div64(frames, 1000000000ULL,
  645. sampleRate);
  646. obs_source_output_audio(source, &data);
  647. capture->ReleaseBuffer(frames);
  648. }
  649. return true;
  650. }
  651. #define RECONNECT_INTERVAL 3000
  652. DWORD WINAPI WASAPISource::CaptureThread(LPVOID param)
  653. {
  654. os_set_thread_name("win-wasapi: capture thread");
  655. const HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
  656. const bool com_initialized = SUCCEEDED(hr);
  657. if (!com_initialized) {
  658. blog(LOG_ERROR,
  659. "[WASAPISource::CaptureThread]"
  660. " CoInitializeEx failed: 0x%08X",
  661. hr);
  662. }
  663. DWORD unused = 0;
  664. const HANDLE handle = AvSetMmThreadCharacteristics(L"Audio", &unused);
  665. WASAPISource *source = (WASAPISource *)param;
  666. const HANDLE inactive_sigs[] = {
  667. source->exitSignal,
  668. source->stopSignal,
  669. source->initSignal,
  670. };
  671. const HANDLE active_sigs[] = {
  672. source->exitSignal,
  673. source->stopSignal,
  674. source->receiveSignal,
  675. source->restartSignal,
  676. };
  677. DWORD sig_count = _countof(inactive_sigs);
  678. const HANDLE *sigs = inactive_sigs;
  679. bool exit = false;
  680. while (!exit) {
  681. bool idle = false;
  682. bool stop = false;
  683. bool reconnect = false;
  684. do {
  685. /* Windows 7 does not seem to wake up for LOOPBACK */
  686. const DWORD dwMilliseconds = ((sigs == active_sigs) &&
  687. !source->isInputDevice)
  688. ? 10
  689. : INFINITE;
  690. const DWORD ret = WaitForMultipleObjects(
  691. sig_count, sigs, false, dwMilliseconds);
  692. switch (ret) {
  693. case WAIT_OBJECT_0: {
  694. exit = true;
  695. stop = true;
  696. idle = true;
  697. break;
  698. }
  699. case WAIT_OBJECT_0 + 1:
  700. stop = true;
  701. idle = true;
  702. break;
  703. case WAIT_OBJECT_0 + 2:
  704. case WAIT_TIMEOUT:
  705. if (sigs == inactive_sigs) {
  706. assert(ret != WAIT_TIMEOUT);
  707. if (source->TryInitialize()) {
  708. sig_count =
  709. _countof(active_sigs);
  710. sigs = active_sigs;
  711. } else {
  712. blog(LOG_INFO,
  713. "WASAPI: Device '%s' failed to start",
  714. source->device_id.c_str());
  715. stop = true;
  716. reconnect = true;
  717. source->reconnectDuration =
  718. RECONNECT_INTERVAL;
  719. }
  720. } else {
  721. stop = !source->ProcessCaptureData();
  722. if (stop) {
  723. blog(LOG_INFO,
  724. "Device '%s' invalidated. Retrying",
  725. source->device_name
  726. .c_str());
  727. stop = true;
  728. reconnect = true;
  729. source->reconnectDuration =
  730. RECONNECT_INTERVAL;
  731. }
  732. }
  733. break;
  734. default:
  735. assert(sigs == active_sigs);
  736. assert(ret == WAIT_OBJECT_0 + 3);
  737. stop = true;
  738. reconnect = true;
  739. source->reconnectDuration = 0;
  740. ResetEvent(source->restartSignal);
  741. }
  742. } while (!stop);
  743. sig_count = _countof(inactive_sigs);
  744. sigs = inactive_sigs;
  745. if (source->client) {
  746. source->client->Stop();
  747. source->capture.Clear();
  748. source->client.Clear();
  749. }
  750. if (idle) {
  751. SetEvent(source->idleSignal);
  752. } else if (reconnect) {
  753. blog(LOG_INFO, "Device '%s' invalidated. Retrying",
  754. source->device_name.c_str());
  755. SetEvent(source->reconnectSignal);
  756. }
  757. }
  758. if (handle)
  759. AvRevertMmThreadCharacteristics(handle);
  760. if (com_initialized)
  761. CoUninitialize();
  762. return 0;
  763. }
  764. void WASAPISource::SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id)
  765. {
  766. if (!isDefaultDevice)
  767. return;
  768. const EDataFlow expectedFlow = isInputDevice ? eCapture : eRender;
  769. const ERole expectedRole = isInputDevice ? eCommunications : eConsole;
  770. if (flow != expectedFlow || role != expectedRole)
  771. return;
  772. if (id) {
  773. if (default_id.compare(id) == 0)
  774. return;
  775. default_id = id;
  776. } else {
  777. if (default_id.empty())
  778. return;
  779. default_id.clear();
  780. }
  781. blog(LOG_INFO, "WASAPI: Default %s device changed",
  782. isInputDevice ? "input" : "output");
  783. SetEvent(restartSignal);
  784. }
  785. void WASAPISource::OnStartCapture()
  786. {
  787. const DWORD ret = WaitForSingleObject(stopSignal, 0);
  788. switch (ret) {
  789. case WAIT_OBJECT_0:
  790. SetEvent(idleSignal);
  791. break;
  792. default:
  793. assert(ret == WAIT_TIMEOUT);
  794. if (!TryInitialize()) {
  795. blog(LOG_INFO, "WASAPI: Device '%s' failed to start",
  796. device_id.c_str());
  797. reconnectDuration = RECONNECT_INTERVAL;
  798. SetEvent(reconnectSignal);
  799. }
  800. }
  801. }
  802. void WASAPISource::OnSampleReady()
  803. {
  804. bool stop = false;
  805. bool reconnect = false;
  806. if (!ProcessCaptureData()) {
  807. stop = true;
  808. reconnect = true;
  809. reconnectDuration = RECONNECT_INTERVAL;
  810. }
  811. if (WaitForSingleObject(restartSignal, 0) == WAIT_OBJECT_0) {
  812. stop = true;
  813. reconnect = true;
  814. reconnectDuration = 0;
  815. ResetEvent(restartSignal);
  816. rtwq_put_waiting_work_item(restartSignal, 0, restartAsyncResult,
  817. nullptr);
  818. }
  819. if (WaitForSingleObject(stopSignal, 0) == WAIT_OBJECT_0) {
  820. stop = true;
  821. reconnect = false;
  822. }
  823. if (!stop) {
  824. if (FAILED(rtwq_put_waiting_work_item(receiveSignal, 0,
  825. sampleReadyAsyncResult,
  826. nullptr))) {
  827. blog(LOG_ERROR,
  828. "Could not requeue sample receive work");
  829. stop = true;
  830. reconnect = true;
  831. reconnectDuration = RECONNECT_INTERVAL;
  832. }
  833. }
  834. if (stop) {
  835. client->Stop();
  836. capture.Clear();
  837. client.Clear();
  838. if (reconnect) {
  839. blog(LOG_INFO, "Device '%s' invalidated. Retrying",
  840. device_name.c_str());
  841. SetEvent(reconnectSignal);
  842. } else {
  843. SetEvent(idleSignal);
  844. }
  845. }
  846. }
  847. void WASAPISource::OnRestart()
  848. {
  849. SetEvent(receiveSignal);
  850. }
  851. /* ------------------------------------------------------------------------- */
  852. static const char *GetWASAPIInputName(void *)
  853. {
  854. return obs_module_text("AudioInput");
  855. }
  856. static const char *GetWASAPIOutputName(void *)
  857. {
  858. return obs_module_text("AudioOutput");
  859. }
  860. static void GetWASAPIDefaultsInput(obs_data_t *settings)
  861. {
  862. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  863. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, false);
  864. }
  865. static void GetWASAPIDefaultsOutput(obs_data_t *settings)
  866. {
  867. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  868. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, true);
  869. }
  870. static void *CreateWASAPISource(obs_data_t *settings, obs_source_t *source,
  871. bool input)
  872. {
  873. try {
  874. return new WASAPISource(settings, source, input);
  875. } catch (const char *error) {
  876. blog(LOG_ERROR, "[CreateWASAPISource] %s", error);
  877. }
  878. return nullptr;
  879. }
  880. static void *CreateWASAPIInput(obs_data_t *settings, obs_source_t *source)
  881. {
  882. return CreateWASAPISource(settings, source, true);
  883. }
  884. static void *CreateWASAPIOutput(obs_data_t *settings, obs_source_t *source)
  885. {
  886. return CreateWASAPISource(settings, source, false);
  887. }
  888. static void DestroyWASAPISource(void *obj)
  889. {
  890. delete static_cast<WASAPISource *>(obj);
  891. }
  892. static void UpdateWASAPISource(void *obj, obs_data_t *settings)
  893. {
  894. static_cast<WASAPISource *>(obj)->Update(settings);
  895. }
  896. static obs_properties_t *GetWASAPIProperties(bool input)
  897. {
  898. obs_properties_t *props = obs_properties_create();
  899. vector<AudioDeviceInfo> devices;
  900. obs_property_t *device_prop = obs_properties_add_list(
  901. props, OPT_DEVICE_ID, obs_module_text("Device"),
  902. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  903. GetWASAPIAudioDevices(devices, input);
  904. if (devices.size())
  905. obs_property_list_add_string(
  906. device_prop, obs_module_text("Default"), "default");
  907. for (size_t i = 0; i < devices.size(); i++) {
  908. AudioDeviceInfo &device = devices[i];
  909. obs_property_list_add_string(device_prop, device.name.c_str(),
  910. device.id.c_str());
  911. }
  912. obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING,
  913. obs_module_text("UseDeviceTiming"));
  914. return props;
  915. }
  916. static obs_properties_t *GetWASAPIPropertiesInput(void *)
  917. {
  918. return GetWASAPIProperties(true);
  919. }
  920. static obs_properties_t *GetWASAPIPropertiesOutput(void *)
  921. {
  922. return GetWASAPIProperties(false);
  923. }
  924. void RegisterWASAPIInput()
  925. {
  926. obs_source_info info = {};
  927. info.id = "wasapi_input_capture";
  928. info.type = OBS_SOURCE_TYPE_INPUT;
  929. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE;
  930. info.get_name = GetWASAPIInputName;
  931. info.create = CreateWASAPIInput;
  932. info.destroy = DestroyWASAPISource;
  933. info.update = UpdateWASAPISource;
  934. info.get_defaults = GetWASAPIDefaultsInput;
  935. info.get_properties = GetWASAPIPropertiesInput;
  936. info.icon_type = OBS_ICON_TYPE_AUDIO_INPUT;
  937. obs_register_source(&info);
  938. }
  939. void RegisterWASAPIOutput()
  940. {
  941. obs_source_info info = {};
  942. info.id = "wasapi_output_capture";
  943. info.type = OBS_SOURCE_TYPE_INPUT;
  944. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE |
  945. OBS_SOURCE_DO_NOT_SELF_MONITOR;
  946. info.get_name = GetWASAPIOutputName;
  947. info.create = CreateWASAPIOutput;
  948. info.destroy = DestroyWASAPISource;
  949. info.update = UpdateWASAPISource;
  950. info.get_defaults = GetWASAPIDefaultsOutput;
  951. info.get_properties = GetWASAPIPropertiesOutput;
  952. info.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT;
  953. obs_register_source(&info);
  954. }