win-wasapi.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738
  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. while (!WaitForSignal(source->stopSignal, RECONNECT_INTERVAL)) {
  375. if (source->TryInitialize())
  376. break;
  377. }
  378. if (com_initialized)
  379. CoUninitialize();
  380. source->reconnectThread = nullptr;
  381. source->reconnecting = false;
  382. return 0;
  383. }
  384. bool WASAPISource::ProcessCaptureData()
  385. {
  386. HRESULT res;
  387. LPBYTE buffer;
  388. UINT32 frames;
  389. DWORD flags;
  390. UINT64 pos, ts;
  391. UINT captureSize = 0;
  392. while (true) {
  393. res = capture->GetNextPacketSize(&captureSize);
  394. if (FAILED(res)) {
  395. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  396. blog(LOG_WARNING,
  397. "[WASAPISource::GetCaptureData]"
  398. " capture->GetNextPacketSize"
  399. " failed: %lX",
  400. res);
  401. return false;
  402. }
  403. if (!captureSize)
  404. break;
  405. res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);
  406. if (FAILED(res)) {
  407. if (res != AUDCLNT_E_DEVICE_INVALIDATED)
  408. blog(LOG_WARNING,
  409. "[WASAPISource::GetCaptureData]"
  410. " capture->GetBuffer"
  411. " failed: %lX",
  412. res);
  413. return false;
  414. }
  415. obs_source_audio data = {};
  416. data.data[0] = (const uint8_t *)buffer;
  417. data.frames = (uint32_t)frames;
  418. data.speakers = speakers;
  419. data.samples_per_sec = sampleRate;
  420. data.format = format;
  421. data.timestamp = useDeviceTiming ? ts * 100 : os_gettime_ns();
  422. if (!useDeviceTiming)
  423. data.timestamp -= util_mul_div64(frames, 1000000000ULL,
  424. sampleRate);
  425. obs_source_output_audio(source, &data);
  426. capture->ReleaseBuffer(frames);
  427. }
  428. return true;
  429. }
  430. static inline bool WaitForCaptureSignal(DWORD numSignals, const HANDLE *signals,
  431. DWORD duration)
  432. {
  433. DWORD ret;
  434. ret = WaitForMultipleObjects(numSignals, signals, false, duration);
  435. return ret == WAIT_OBJECT_0 || ret == WAIT_TIMEOUT;
  436. }
  437. DWORD WINAPI WASAPISource::CaptureThread(LPVOID param)
  438. {
  439. WASAPISource *source = (WASAPISource *)param;
  440. bool reconnect = false;
  441. /* Output devices don't signal, so just make it check every 10 ms */
  442. DWORD dur = source->isInputDevice ? RECONNECT_INTERVAL : 10;
  443. HANDLE sigs[2] = {source->receiveSignal, source->stopSignal};
  444. os_set_thread_name("win-wasapi: capture thread");
  445. while (WaitForCaptureSignal(2, sigs, dur)) {
  446. if (!source->ProcessCaptureData()) {
  447. reconnect = true;
  448. break;
  449. }
  450. }
  451. source->client->Stop();
  452. source->captureThread = nullptr;
  453. source->active = false;
  454. if (reconnect) {
  455. blog(LOG_INFO, "Device '%s' invalidated. Retrying",
  456. source->device_name.c_str());
  457. source->Reconnect();
  458. }
  459. return 0;
  460. }
  461. void WASAPISource::SetDefaultDevice(EDataFlow flow, ERole role, LPCWSTR id)
  462. {
  463. if (!isDefaultDevice)
  464. return;
  465. EDataFlow expectedFlow = isInputDevice ? eCapture : eRender;
  466. ERole expectedRole = isInputDevice ? eCommunications : eConsole;
  467. if (flow != expectedFlow || role != expectedRole)
  468. return;
  469. if (id && default_id.compare(id) == 0)
  470. return;
  471. blog(LOG_INFO, "WASAPI: Default %s device changed",
  472. isInputDevice ? "input" : "output");
  473. /* reset device only once every 300ms */
  474. uint64_t t = os_gettime_ns();
  475. if (t - lastNotifyTime < 300000000)
  476. return;
  477. std::thread([this]() {
  478. Stop();
  479. Start();
  480. }).detach();
  481. lastNotifyTime = t;
  482. }
  483. /* ------------------------------------------------------------------------- */
  484. static const char *GetWASAPIInputName(void *)
  485. {
  486. return obs_module_text("AudioInput");
  487. }
  488. static const char *GetWASAPIOutputName(void *)
  489. {
  490. return obs_module_text("AudioOutput");
  491. }
  492. static void GetWASAPIDefaultsInput(obs_data_t *settings)
  493. {
  494. obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
  495. obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, false);
  496. }
  497. static void GetWASAPIDefaultsOutput(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, true);
  501. }
  502. static void *CreateWASAPISource(obs_data_t *settings, obs_source_t *source,
  503. bool input)
  504. {
  505. try {
  506. return new WASAPISource(settings, source, input);
  507. } catch (const char *error) {
  508. blog(LOG_ERROR, "[CreateWASAPISource] %s", error);
  509. }
  510. return nullptr;
  511. }
  512. static void *CreateWASAPIInput(obs_data_t *settings, obs_source_t *source)
  513. {
  514. return CreateWASAPISource(settings, source, true);
  515. }
  516. static void *CreateWASAPIOutput(obs_data_t *settings, obs_source_t *source)
  517. {
  518. return CreateWASAPISource(settings, source, false);
  519. }
  520. static void DestroyWASAPISource(void *obj)
  521. {
  522. delete static_cast<WASAPISource *>(obj);
  523. }
  524. static void UpdateWASAPISource(void *obj, obs_data_t *settings)
  525. {
  526. static_cast<WASAPISource *>(obj)->Update(settings);
  527. }
  528. static obs_properties_t *GetWASAPIProperties(bool input)
  529. {
  530. obs_properties_t *props = obs_properties_create();
  531. vector<AudioDeviceInfo> devices;
  532. obs_property_t *device_prop = obs_properties_add_list(
  533. props, OPT_DEVICE_ID, obs_module_text("Device"),
  534. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
  535. GetWASAPIAudioDevices(devices, input);
  536. if (devices.size())
  537. obs_property_list_add_string(
  538. device_prop, obs_module_text("Default"), "default");
  539. for (size_t i = 0; i < devices.size(); i++) {
  540. AudioDeviceInfo &device = devices[i];
  541. obs_property_list_add_string(device_prop, device.name.c_str(),
  542. device.id.c_str());
  543. }
  544. obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING,
  545. obs_module_text("UseDeviceTiming"));
  546. return props;
  547. }
  548. static obs_properties_t *GetWASAPIPropertiesInput(void *)
  549. {
  550. return GetWASAPIProperties(true);
  551. }
  552. static obs_properties_t *GetWASAPIPropertiesOutput(void *)
  553. {
  554. return GetWASAPIProperties(false);
  555. }
  556. void RegisterWASAPIInput()
  557. {
  558. obs_source_info info = {};
  559. info.id = "wasapi_input_capture";
  560. info.type = OBS_SOURCE_TYPE_INPUT;
  561. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE;
  562. info.get_name = GetWASAPIInputName;
  563. info.create = CreateWASAPIInput;
  564. info.destroy = DestroyWASAPISource;
  565. info.update = UpdateWASAPISource;
  566. info.get_defaults = GetWASAPIDefaultsInput;
  567. info.get_properties = GetWASAPIPropertiesInput;
  568. info.icon_type = OBS_ICON_TYPE_AUDIO_INPUT;
  569. obs_register_source(&info);
  570. }
  571. void RegisterWASAPIOutput()
  572. {
  573. obs_source_info info = {};
  574. info.id = "wasapi_output_capture";
  575. info.type = OBS_SOURCE_TYPE_INPUT;
  576. info.output_flags = OBS_SOURCE_AUDIO | OBS_SOURCE_DO_NOT_DUPLICATE |
  577. OBS_SOURCE_DO_NOT_SELF_MONITOR;
  578. info.get_name = GetWASAPIOutputName;
  579. info.create = CreateWASAPIOutput;
  580. info.destroy = DestroyWASAPISource;
  581. info.update = UpdateWASAPISource;
  582. info.get_defaults = GetWASAPIDefaultsOutput;
  583. info.get_properties = GetWASAPIPropertiesOutput;
  584. info.icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT;
  585. obs_register_source(&info);
  586. }