win-wasapi.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  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/threading.h>
  10. #include <util/util_uint64.h>
  11. #include <atomic>
  12. #include <cinttypes>
  13. using namespace std;
  14. #define OPT_DEVICE_ID "device_id"
  15. #define OPT_USE_DEVICE_TIMING "use_device_timing"
  16. static void GetWASAPIDefaults(obs_data_t *settings);
  17. #define OBS_KSAUDIO_SPEAKER_4POINT1 \
  18. (KSAUDIO_SPEAKER_SURROUND | SPEAKER_LOW_FREQUENCY)
  19. class WASAPISource {
  20. ComPtr<IMMNotificationClient> notify;
  21. ComPtr<IMMDeviceEnumerator> enumerator;
  22. ComPtr<IAudioClient> client;
  23. ComPtr<IAudioCaptureClient> capture;
  24. obs_source_t *source;
  25. wstring default_id;
  26. string device_id;
  27. string device_name;
  28. uint64_t lastNotifyTime = 0;
  29. bool isInputDevice;
  30. std::atomic<bool> useDeviceTiming = false;
  31. bool isDefaultDevice = false;
  32. bool previouslyFailed = false;
  33. WinHandle reconnectThread;
  34. WinHandle captureThread;
  35. WinHandle idleSignal;
  36. WinHandle stopSignal;
  37. WinHandle receiveSignal;
  38. WinHandle restartSignal;
  39. WinHandle exitSignal;
  40. WinHandle initSignal;
  41. DWORD reconnectDuration = 0;
  42. WinHandle reconnectSignal;
  43. speaker_layout speakers;
  44. audio_format format;
  45. uint32_t sampleRate;
  46. static DWORD WINAPI ReconnectThread(LPVOID param);
  47. static DWORD WINAPI CaptureThread(LPVOID param);
  48. bool ProcessCaptureData();
  49. void Start();
  50. void Stop();
  51. static ComPtr<IMMDevice> InitDevice(IMMDeviceEnumerator *enumerator,
  52. bool isDefaultDevice,
  53. bool isInputDevice,
  54. const string device_id,
  55. wstring &default_id);
  56. static ComPtr<IAudioClient> InitClient(IMMDevice *device,
  57. bool isInputDevice,
  58. enum speaker_layout &speakers,
  59. enum audio_format &format,
  60. uint32_t &sampleRate);
  61. static void InitFormat(const WAVEFORMATEX *wfex,
  62. enum speaker_layout &speakers,
  63. enum audio_format &format, uint32_t &sampleRate);
  64. static void ClearBuffer(IMMDevice *device);
  65. static ComPtr<IAudioCaptureClient> InitCapture(IAudioClient *client,
  66. HANDLE receiveSignal);
  67. void Initialize();
  68. bool TryInitialize();
  69. void UpdateSettings(obs_data_t *settings);
  70. public:
  71. WASAPISource(obs_data_t *settings, obs_source_t *source_, bool input);
  72. ~WASAPISource();
  73. void Update(obs_data_t *settings);
  74. void SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id);
  75. };
  76. class WASAPINotify : public IMMNotificationClient {
  77. long refs = 0; /* auto-incremented to 1 by ComPtr */
  78. WASAPISource *source;
  79. public:
  80. WASAPINotify(WASAPISource *source_) : source(source_) {}
  81. STDMETHODIMP_(ULONG) AddRef()
  82. {
  83. return (ULONG)os_atomic_inc_long(&refs);
  84. }
  85. STDMETHODIMP_(ULONG) STDMETHODCALLTYPE Release()
  86. {
  87. long val = os_atomic_dec_long(&refs);
  88. if (val == 0)
  89. delete this;
  90. return (ULONG)val;
  91. }
  92. STDMETHODIMP QueryInterface(REFIID riid, void **ptr)
  93. {
  94. if (riid == IID_IUnknown) {
  95. *ptr = (IUnknown *)this;
  96. } else if (riid == __uuidof(IMMNotificationClient)) {
  97. *ptr = (IMMNotificationClient *)this;
  98. } else {
  99. *ptr = nullptr;
  100. return E_NOINTERFACE;
  101. }
  102. os_atomic_inc_long(&refs);
  103. return S_OK;
  104. }
  105. STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role,
  106. LPCWSTR id)
  107. {
  108. source->SetDefaultDevice(flow, role, id);
  109. return S_OK;
  110. }
  111. STDMETHODIMP OnDeviceAdded(LPCWSTR) { return S_OK; }
  112. STDMETHODIMP OnDeviceRemoved(LPCWSTR) { return S_OK; }
  113. STDMETHODIMP OnDeviceStateChanged(LPCWSTR, DWORD) { return S_OK; }
  114. STDMETHODIMP OnPropertyValueChanged(LPCWSTR, const PROPERTYKEY)
  115. {
  116. return S_OK;
  117. }
  118. };
  119. WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_,
  120. bool input)
  121. : source(source_), isInputDevice(input)
  122. {
  123. UpdateSettings(settings);
  124. idleSignal = CreateEvent(nullptr, true, false, nullptr);
  125. if (!idleSignal.Valid())
  126. throw "Could not create idle signal";
  127. stopSignal = CreateEvent(nullptr, true, false, nullptr);
  128. if (!stopSignal.Valid())
  129. throw "Could not create stop signal";
  130. receiveSignal = CreateEvent(nullptr, false, false, nullptr);
  131. if (!receiveSignal.Valid())
  132. throw "Could not create receive signal";
  133. restartSignal = CreateEvent(nullptr, true, false, nullptr);
  134. if (!restartSignal.Valid())
  135. throw "Could not create restart signal";
  136. exitSignal = CreateEvent(nullptr, true, false, nullptr);
  137. if (!exitSignal.Valid())
  138. throw "Could not create exit signal";
  139. initSignal = CreateEvent(nullptr, false, false, nullptr);
  140. if (!initSignal.Valid())
  141. throw "Could not create init signal";
  142. reconnectSignal = CreateEvent(nullptr, false, false, nullptr);
  143. if (!reconnectSignal.Valid())
  144. throw "Could not create reconnect signal";
  145. reconnectThread = CreateThread(
  146. nullptr, 0, WASAPISource::ReconnectThread, this, 0, nullptr);
  147. if (!reconnectThread.Valid())
  148. throw "Failed to create reconnect thread";
  149. notify = new WASAPINotify(this);
  150. if (!notify)
  151. throw "Could not create WASAPINotify";
  152. HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
  153. CLSCTX_ALL,
  154. IID_PPV_ARGS(enumerator.Assign()));
  155. if (FAILED(hr))
  156. throw HRError("Failed to create enumerator", hr);
  157. hr = enumerator->RegisterEndpointNotificationCallback(notify);
  158. if (FAILED(hr))
  159. throw HRError("Failed to register endpoint callback", hr);
  160. captureThread = CreateThread(nullptr, 0, WASAPISource::CaptureThread,
  161. this, 0, nullptr);
  162. if (!captureThread.Valid()) {
  163. enumerator->UnregisterEndpointNotificationCallback(notify);
  164. throw "Failed to create capture thread";
  165. }
  166. Start();
  167. }
  168. void WASAPISource::Start()
  169. {
  170. SetEvent(initSignal);
  171. }
  172. void WASAPISource::Stop()
  173. {
  174. SetEvent(stopSignal);
  175. blog(LOG_INFO, "WASAPI: Device '%s' Terminated", device_name.c_str());
  176. WaitForSingleObject(idleSignal, INFINITE);
  177. SetEvent(exitSignal);
  178. WaitForSingleObject(reconnectThread, INFINITE);
  179. WaitForSingleObject(captureThread, INFINITE);
  180. }
  181. WASAPISource::~WASAPISource()
  182. {
  183. enumerator->UnregisterEndpointNotificationCallback(notify);
  184. Stop();
  185. }
  186. void WASAPISource::UpdateSettings(obs_data_t *settings)
  187. {
  188. device_id = obs_data_get_string(settings, OPT_DEVICE_ID);
  189. useDeviceTiming = obs_data_get_bool(settings, OPT_USE_DEVICE_TIMING);
  190. isDefaultDevice = _strcmpi(device_id.c_str(), "default") == 0;
  191. }
  192. void WASAPISource::Update(obs_data_t *settings)
  193. {
  194. const string newDevice = obs_data_get_string(settings, OPT_DEVICE_ID);
  195. const bool restart = newDevice.compare(device_id) != 0;
  196. UpdateSettings(settings);
  197. if (restart)
  198. SetEvent(restartSignal);
  199. }
  200. ComPtr<IMMDevice> WASAPISource::InitDevice(IMMDeviceEnumerator *enumerator,
  201. bool isDefaultDevice,
  202. bool isInputDevice,
  203. const string device_id,
  204. wstring &default_id)
  205. {
  206. ComPtr<IMMDevice> device;
  207. if (isDefaultDevice) {
  208. HRESULT res = enumerator->GetDefaultAudioEndpoint(
  209. isInputDevice ? eCapture : eRender,
  210. isInputDevice ? eCommunications : eConsole,
  211. device.Assign());
  212. if (FAILED(res))
  213. throw HRError("Failed GetDefaultAudioEndpoint", res);
  214. CoTaskMemPtr<wchar_t> id;
  215. res = device->GetId(&id);
  216. if (FAILED(res))
  217. throw HRError("Failed to get default id", res);
  218. default_id = id;
  219. } else {
  220. wchar_t *w_id;
  221. os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
  222. if (!w_id)
  223. throw "Failed to widen device id string";
  224. const HRESULT res =
  225. enumerator->GetDevice(w_id, device.Assign());
  226. bfree(w_id);
  227. if (FAILED(res))
  228. throw HRError("Failed to enumerate device", res);
  229. }
  230. return device;
  231. }
  232. #define BUFFER_TIME_100NS (5 * 10000000)
  233. ComPtr<IAudioClient> WASAPISource::InitClient(IMMDevice *device,
  234. bool isInputDevice,
  235. enum speaker_layout &speakers,
  236. enum audio_format &format,
  237. uint32_t &sampleRate)
  238. {
  239. ComPtr<IAudioClient> client;
  240. HRESULT res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
  241. nullptr, (void **)client.Assign());
  242. if (FAILED(res))
  243. throw HRError("Failed to activate client context", res);
  244. CoTaskMemPtr<WAVEFORMATEX> wfex;
  245. res = client->GetMixFormat(&wfex);
  246. if (FAILED(res))
  247. throw HRError("Failed to get mix format", res);
  248. InitFormat(wfex, speakers, format, sampleRate);
  249. DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
  250. if (!isInputDevice)
  251. flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
  252. res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, flags,
  253. BUFFER_TIME_100NS, 0, wfex, nullptr);
  254. if (FAILED(res))
  255. throw HRError("Failed to initialize audio client", res);
  256. return client;
  257. }
  258. void WASAPISource::ClearBuffer(IMMDevice *device)
  259. {
  260. CoTaskMemPtr<WAVEFORMATEX> wfex;
  261. HRESULT res;
  262. LPBYTE buffer;
  263. UINT32 frames;
  264. ComPtr<IAudioClient> client;
  265. res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
  266. (void **)client.Assign());
  267. if (FAILED(res))
  268. throw HRError("Failed to activate client context", res);
  269. res = client->GetMixFormat(&wfex);
  270. if (FAILED(res))
  271. throw HRError("Failed to get mix format", res);
  272. res = client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, BUFFER_TIME_100NS,
  273. 0, wfex, nullptr);
  274. if (FAILED(res))
  275. throw HRError("Failed to initialize audio client", res);
  276. /* Silent loopback fix. Prevents audio stream from stopping and */
  277. /* messing up timestamps and other weird glitches during silence */
  278. /* by playing a silent sample all over again. */
  279. res = client->GetBufferSize(&frames);
  280. if (FAILED(res))
  281. throw HRError("Failed to get buffer size", res);
  282. ComPtr<IAudioRenderClient> render;
  283. res = client->GetService(IID_PPV_ARGS(render.Assign()));
  284. if (FAILED(res))
  285. throw HRError("Failed to get render client", res);
  286. res = render->GetBuffer(frames, &buffer);
  287. if (FAILED(res))
  288. throw HRError("Failed to get buffer", res);
  289. memset(buffer, 0, (size_t)frames * (size_t)wfex->nBlockAlign);
  290. render->ReleaseBuffer(frames, 0);
  291. }
  292. static speaker_layout ConvertSpeakerLayout(DWORD layout, WORD channels)
  293. {
  294. switch (layout) {
  295. case KSAUDIO_SPEAKER_2POINT1:
  296. return SPEAKERS_2POINT1;
  297. case KSAUDIO_SPEAKER_SURROUND:
  298. return SPEAKERS_4POINT0;
  299. case OBS_KSAUDIO_SPEAKER_4POINT1:
  300. return SPEAKERS_4POINT1;
  301. case KSAUDIO_SPEAKER_5POINT1_SURROUND:
  302. return SPEAKERS_5POINT1;
  303. case KSAUDIO_SPEAKER_7POINT1_SURROUND:
  304. return SPEAKERS_7POINT1;
  305. }
  306. return (speaker_layout)channels;
  307. }
  308. void WASAPISource::InitFormat(const WAVEFORMATEX *wfex,
  309. enum speaker_layout &speakers,
  310. enum audio_format &format, uint32_t &sampleRate)
  311. {
  312. DWORD layout = 0;
  313. if (wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
  314. WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE *)wfex;
  315. layout = ext->dwChannelMask;
  316. }
  317. /* WASAPI is always float */
  318. speakers = ConvertSpeakerLayout(layout, wfex->nChannels);
  319. format = AUDIO_FORMAT_FLOAT;
  320. sampleRate = wfex->nSamplesPerSec;
  321. }
  322. ComPtr<IAudioCaptureClient> WASAPISource::InitCapture(IAudioClient *client,
  323. HANDLE receiveSignal)
  324. {
  325. ComPtr<IAudioCaptureClient> capture;
  326. HRESULT res = client->GetService(IID_PPV_ARGS(capture.Assign()));
  327. if (FAILED(res))
  328. throw HRError("Failed to create capture context", res);
  329. res = client->SetEventHandle(receiveSignal);
  330. if (FAILED(res))
  331. throw HRError("Failed to set event handle", res);
  332. res = client->Start();
  333. if (FAILED(res))
  334. throw HRError("Failed to start capture client", res);
  335. return capture;
  336. }
  337. void WASAPISource::Initialize()
  338. {
  339. ComPtr<IMMDevice> device = InitDevice(enumerator, isDefaultDevice,
  340. isInputDevice, device_id,
  341. default_id);
  342. device_name = GetDeviceName(device);
  343. ResetEvent(receiveSignal);
  344. ComPtr<IAudioClient> temp_client =
  345. InitClient(device, isInputDevice, speakers, format, sampleRate);
  346. if (!isInputDevice)
  347. ClearBuffer(device);
  348. ComPtr<IAudioCaptureClient> temp_capture =
  349. InitCapture(temp_client, receiveSignal);
  350. client = std::move(temp_client);
  351. capture = std::move(temp_capture);
  352. blog(LOG_INFO, "WASAPI: Device '%s' [%" PRIu32 " Hz] initialized",
  353. device_name.c_str(), sampleRate);
  354. }
  355. bool WASAPISource::TryInitialize()
  356. {
  357. bool success = false;
  358. try {
  359. Initialize();
  360. success = true;
  361. } catch (HRError &error) {
  362. if (!previouslyFailed) {
  363. blog(LOG_WARNING,
  364. "[WASAPISource::TryInitialize]:[%s] %s: %lX",
  365. device_name.empty() ? device_id.c_str()
  366. : device_name.c_str(),
  367. error.str, error.hr);
  368. }
  369. } catch (const char *error) {
  370. if (!previouslyFailed) {
  371. blog(LOG_WARNING,
  372. "[WASAPISource::TryInitialize]:[%s] %s",
  373. device_name.empty() ? device_id.c_str()
  374. : device_name.c_str(),
  375. error);
  376. }
  377. }
  378. previouslyFailed = !success;
  379. return success;
  380. }
  381. DWORD WINAPI WASAPISource::ReconnectThread(LPVOID param)
  382. {
  383. os_set_thread_name("win-wasapi: reconnect thread");
  384. WASAPISource *source = (WASAPISource *)param;
  385. const HANDLE sigs[] = {
  386. source->exitSignal,
  387. source->reconnectSignal,
  388. };
  389. bool exit = false;
  390. while (!exit) {
  391. const DWORD ret = WaitForMultipleObjects(_countof(sigs), sigs,
  392. false, INFINITE);
  393. switch (ret) {
  394. case WAIT_OBJECT_0:
  395. exit = true;
  396. break;
  397. default:
  398. assert(ret == (WAIT_OBJECT_0 + 1));
  399. if (source->reconnectDuration > 0) {
  400. WaitForSingleObject(source->stopSignal,
  401. source->reconnectDuration);
  402. }
  403. source->Start();
  404. }
  405. }
  406. return 0;
  407. }
  408. bool WASAPISource::ProcessCaptureData()
  409. {
  410. HRESULT res;
  411. LPBYTE buffer;
  412. UINT32 frames;
  413. DWORD flags;
  414. UINT64 pos, ts;
  415. UINT captureSize = 0;
  416. while (true) {
  417. res = capture->GetNextPacketSize(&captureSize);
  418. if (FAILED(res)) {
  419. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  420. blog(LOG_WARNING,
  421. "[WASAPISource::ProcessCaptureData]"
  422. " capture->GetNextPacketSize"
  423. " failed: %lX",
  424. res);
  425. return false;
  426. }
  427. if (!captureSize)
  428. break;
  429. res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);
  430. if (FAILED(res)) {
  431. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  432. blog(LOG_WARNING,
  433. "[WASAPISource::ProcessCaptureData]"
  434. " capture->GetBuffer"
  435. " failed: %lX",
  436. res);
  437. return false;
  438. }
  439. obs_source_audio data = {};
  440. data.data[0] = (const uint8_t *)buffer;
  441. data.frames = (uint32_t)frames;
  442. data.speakers = speakers;
  443. data.samples_per_sec = sampleRate;
  444. data.format = format;
  445. data.timestamp = useDeviceTiming ? ts * 100 : os_gettime_ns();
  446. if (!useDeviceTiming)
  447. data.timestamp -= util_mul_div64(frames, 1000000000ULL,
  448. sampleRate);
  449. obs_source_output_audio(source, &data);
  450. capture->ReleaseBuffer(frames);
  451. }
  452. return true;
  453. }
  454. #define RECONNECT_INTERVAL 3000
  455. DWORD WINAPI WASAPISource::CaptureThread(LPVOID param)
  456. {
  457. os_set_thread_name("win-wasapi: capture thread");
  458. const HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
  459. const bool com_initialized = SUCCEEDED(hr);
  460. if (!com_initialized) {
  461. blog(LOG_ERROR,
  462. "[WASAPISource::CaptureThread]"
  463. " CoInitializeEx failed: 0x%08X",
  464. hr);
  465. }
  466. WASAPISource *source = (WASAPISource *)param;
  467. const HANDLE inactive_sigs[] = {
  468. source->exitSignal,
  469. source->stopSignal,
  470. source->initSignal,
  471. };
  472. const HANDLE active_sigs[] = {
  473. source->exitSignal,
  474. source->stopSignal,
  475. source->receiveSignal,
  476. source->restartSignal,
  477. };
  478. DWORD sig_count = _countof(inactive_sigs);
  479. const HANDLE *sigs = inactive_sigs;
  480. bool exit = false;
  481. while (!exit) {
  482. bool idle = false;
  483. bool stop = false;
  484. bool reconnect = false;
  485. do {
  486. /* Windows 7 does not seem to wake up for LOOPBACK */
  487. const DWORD dwMilliseconds = ((sigs == active_sigs) &&
  488. !source->isInputDevice)
  489. ? 10
  490. : INFINITE;
  491. const DWORD ret = WaitForMultipleObjects(
  492. sig_count, sigs, false, dwMilliseconds);
  493. switch (ret) {
  494. case WAIT_OBJECT_0: {
  495. exit = true;
  496. stop = true;
  497. idle = true;
  498. break;
  499. }
  500. case WAIT_OBJECT_0 + 1:
  501. stop = true;
  502. idle = true;
  503. break;
  504. case WAIT_OBJECT_0 + 2:
  505. case WAIT_TIMEOUT:
  506. if (sigs == inactive_sigs) {
  507. assert(ret != WAIT_TIMEOUT);
  508. if (source->TryInitialize()) {
  509. sig_count =
  510. _countof(active_sigs);
  511. sigs = active_sigs;
  512. } else {
  513. blog(LOG_INFO,
  514. "WASAPI: Device '%s' failed to start",
  515. source->device_id.c_str());
  516. stop = true;
  517. reconnect = true;
  518. source->reconnectDuration =
  519. RECONNECT_INTERVAL;
  520. }
  521. } else {
  522. stop = !source->ProcessCaptureData();
  523. if (stop) {
  524. blog(LOG_INFO,
  525. "Device '%s' invalidated. Retrying",
  526. source->device_name
  527. .c_str());
  528. stop = true;
  529. reconnect = true;
  530. source->reconnectDuration =
  531. RECONNECT_INTERVAL;
  532. }
  533. }
  534. break;
  535. default:
  536. assert(sigs == active_sigs);
  537. assert(ret == WAIT_OBJECT_0 + 3);
  538. stop = true;
  539. reconnect = true;
  540. source->reconnectDuration = 0;
  541. ResetEvent(source->restartSignal);
  542. }
  543. } while (!stop);
  544. sig_count = _countof(inactive_sigs);
  545. sigs = inactive_sigs;
  546. if (source->client) {
  547. source->client->Stop();
  548. source->capture.Clear();
  549. source->client.Clear();
  550. }
  551. if (idle) {
  552. SetEvent(source->idleSignal);
  553. } else if (reconnect) {
  554. blog(LOG_INFO, "Device '%s' invalidated. Retrying",
  555. source->device_name.c_str());
  556. SetEvent(source->reconnectSignal);
  557. }
  558. }
  559. if (com_initialized)
  560. CoUninitialize();
  561. return 0;
  562. }
  563. void WASAPISource::SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id)
  564. {
  565. if (!isDefaultDevice)
  566. return;
  567. EDataFlow expectedFlow = isInputDevice ? eCapture : eRender;
  568. ERole expectedRole = isInputDevice ? eCommunications : eConsole;
  569. if (flow != expectedFlow || role != expectedRole)
  570. return;
  571. if (id && default_id.compare(id) == 0)
  572. return;
  573. blog(LOG_INFO, "WASAPI: Default %s device changed",
  574. isInputDevice ? "input" : "output");
  575. /* reset device only once every 300ms */
  576. uint64_t t = os_gettime_ns();
  577. if (t - lastNotifyTime < 300000000)
  578. return;
  579. lastNotifyTime = t;
  580. SetEvent(restartSignal);
  581. }
  582. /* ------------------------------------------------------------------------- */
  583. static const char *GetWASAPIInputName(void *)
  584. {
  585. return obs_module_text("AudioInput");
  586. }
  587. static const char *GetWASAPIOutputName(void *)
  588. {
  589. return obs_module_text("AudioOutput");
  590. }
  591. static void GetWASAPIDefaultsInput(obs_data_t *settings)
  592. {
  593. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  594. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, false);
  595. }
  596. static void GetWASAPIDefaultsOutput(obs_data_t *settings)
  597. {
  598. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  599. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, true);
  600. }
  601. static void *CreateWASAPISource(obs_data_t *settings, obs_source_t *source,
  602. bool input)
  603. {
  604. try {
  605. return new WASAPISource(settings, source, input);
  606. } catch (const char *error) {
  607. blog(LOG_ERROR, "[CreateWASAPISource] %s", error);
  608. }
  609. return nullptr;
  610. }
  611. static void *CreateWASAPIInput(obs_data_t *settings, obs_source_t *source)
  612. {
  613. return CreateWASAPISource(settings, source, true);
  614. }
  615. static void *CreateWASAPIOutput(obs_data_t *settings, obs_source_t *source)
  616. {
  617. return CreateWASAPISource(settings, source, false);
  618. }
  619. static void DestroyWASAPISource(void *obj)
  620. {
  621. delete static_cast<WASAPISource *>(obj);
  622. }
  623. static void UpdateWASAPISource(void *obj, obs_data_t *settings)
  624. {
  625. static_cast<WASAPISource *>(obj)->Update(settings);
  626. }
  627. static obs_properties_t *GetWASAPIProperties(bool input)
  628. {
  629. obs_properties_t *props = obs_properties_create();
  630. vector<AudioDeviceInfo> devices;
  631. obs_property_t *device_prop = obs_properties_add_list(
  632. props, OPT_DEVICE_ID, obs_module_text("Device"),
  633. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  634. GetWASAPIAudioDevices(devices, input);
  635. if (devices.size())
  636. obs_property_list_add_string(
  637. device_prop, obs_module_text("Default"), "default");
  638. for (size_t i = 0; i < devices.size(); i++) {
  639. AudioDeviceInfo &device = devices[i];
  640. obs_property_list_add_string(device_prop, device.name.c_str(),
  641. device.id.c_str());
  642. }
  643. obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING,
  644. obs_module_text("UseDeviceTiming"));
  645. return props;
  646. }
  647. static obs_properties_t *GetWASAPIPropertiesInput(void *)
  648. {
  649. return GetWASAPIProperties(true);
  650. }
  651. static obs_properties_t *GetWASAPIPropertiesOutput(void *)
  652. {
  653. return GetWASAPIProperties(false);
  654. }
  655. void RegisterWASAPIInput()
  656. {
  657. obs_source_info info = {};
  658. info.id = "wasapi_input_capture";
  659. info.type = OBS_SOURCE_TYPE_INPUT;
  660. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE;
  661. info.get_name = GetWASAPIInputName;
  662. info.create = CreateWASAPIInput;
  663. info.destroy = DestroyWASAPISource;
  664. info.update = UpdateWASAPISource;
  665. info.get_defaults = GetWASAPIDefaultsInput;
  666. info.get_properties = GetWASAPIPropertiesInput;
  667. info.icon_type = OBS_ICON_TYPE_AUDIO_INPUT;
  668. obs_register_source(&info);
  669. }
  670. void RegisterWASAPIOutput()
  671. {
  672. obs_source_info info = {};
  673. info.id = "wasapi_output_capture";
  674. info.type = OBS_SOURCE_TYPE_INPUT;
  675. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE |
  676. OBS_SOURCE_DO_NOT_SELF_MONITOR;
  677. info.get_name = GetWASAPIOutputName;
  678. info.create = CreateWASAPIOutput;
  679. info.destroy = DestroyWASAPISource;
  680. info.update = UpdateWASAPISource;
  681. info.get_defaults = GetWASAPIDefaultsOutput;
  682. info.get_properties = GetWASAPIPropertiesOutput;
  683. info.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT;
  684. obs_register_source(&info);
  685. }