win-wasapi.cpp 17 KB

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