win-wasapi.cpp 18 KB

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