1
0

oss-input.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. /*
  2. Copyright (C) 2020. Ka Ho Ng <[email protected]>
  3. Copyright (C) 2020. Ed Maste <[email protected]>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include <util/bmem.h>
  16. #include <util/platform.h>
  17. #include <util/threading.h>
  18. #include <obs-module.h>
  19. #include <poll.h>
  20. #include <unistd.h>
  21. #include <fcntl.h>
  22. #include <pthread.h>
  23. #include "oss-platform.h"
  24. #define blog(level, msg, ...) blog(level, "oss-audio: " msg, ##__VA_ARGS__)
  25. #define NSEC_PER_SEC 1000000000LL
  26. #define OSS_MAX_CHANNELS 8
  27. #define OSS_DSP_DEFAULT "/dev/dsp"
  28. #define OSS_SNDSTAT_PATH "/dev/sndstat"
  29. #define OSS_RATE_DEFAULT 48000
  30. #define OSS_CHANNELS_DEFAULT 2
  31. /**
  32. * Control block of plugin instance
  33. */
  34. struct oss_input_data {
  35. obs_source_t *source;
  36. char *device;
  37. int channels;
  38. int rate;
  39. int sample_fmt;
  40. pthread_t reader_thr;
  41. int notify_pipe[2];
  42. int dsp_fd;
  43. void *dsp_buf;
  44. size_t dsp_fragsize;
  45. };
  46. #define OBS_PROPS_DSP "dsp"
  47. #define OBS_PROPS_CUSTOM_DSP "custom_dsp"
  48. #define OBS_PATH_DSP_CUSTOM "/"
  49. #define OBS_PROPS_CHANNELS "channels"
  50. #define OBS_PROPS_RATE "rate"
  51. #define OBS_PROPS_SAMPLE_FMT "sample_fmt"
  52. /**
  53. * Common sampling rate table
  54. */
  55. struct rate_option {
  56. int rate;
  57. char *desc;
  58. } rate_table[] = {
  59. {8000, "8000 Hz"}, {11025, "11025 Hz"}, {16000, "16000 Hz"},
  60. {22050, "22050 Hz"}, {32000, "32000 Hz"}, {44100, "44100 Hz"},
  61. {48000, "48000 Hz"}, {96000, "96000 Hz"}, {192000, "192000 Hz"},
  62. {384000, "384000 Hz"},
  63. };
  64. static unsigned int oss_sample_size(unsigned int sample_fmt)
  65. {
  66. switch (sample_fmt) {
  67. case AFMT_U8:
  68. case AFMT_S8:
  69. return 8;
  70. case AFMT_S16_LE:
  71. case AFMT_S16_BE:
  72. case AFMT_U16_LE:
  73. case AFMT_U16_BE:
  74. return 16;
  75. case AFMT_S32_LE:
  76. case AFMT_S32_BE:
  77. case AFMT_U32_LE:
  78. case AFMT_U32_BE:
  79. case AFMT_S24_LE:
  80. case AFMT_S24_BE:
  81. case AFMT_U24_LE:
  82. case AFMT_U24_BE:
  83. return 32;
  84. }
  85. return 0;
  86. }
  87. static size_t oss_calc_framesize(unsigned int channels, unsigned int sample_fmt)
  88. {
  89. return oss_sample_size(sample_fmt) * channels / 8;
  90. }
  91. static enum audio_format oss_fmt_to_obs_audio_format(int fmt)
  92. {
  93. switch (fmt) {
  94. case AFMT_U8:
  95. return AUDIO_FORMAT_U8BIT;
  96. case AFMT_S16_LE:
  97. return AUDIO_FORMAT_16BIT;
  98. case AFMT_S32_LE:
  99. return AUDIO_FORMAT_32BIT;
  100. }
  101. return AUDIO_FORMAT_UNKNOWN;
  102. }
  103. static enum speaker_layout oss_channels_to_obs_speakers(unsigned int channels)
  104. {
  105. switch (channels) {
  106. case 1:
  107. return SPEAKERS_MONO;
  108. case 2:
  109. return SPEAKERS_STEREO;
  110. case 3:
  111. return SPEAKERS_2POINT1;
  112. case 4:
  113. return SPEAKERS_4POINT0;
  114. case 5:
  115. return SPEAKERS_4POINT1;
  116. case 6:
  117. return SPEAKERS_5POINT1;
  118. case 8:
  119. return SPEAKERS_7POINT1;
  120. }
  121. return SPEAKERS_UNKNOWN;
  122. }
  123. static int oss_setup_device(struct oss_input_data *handle)
  124. {
  125. size_t dsp_fragsize;
  126. void *dsp_buf = NULL;
  127. int fd = -1, err;
  128. audio_buf_info bi;
  129. fd = open(handle->device, O_RDONLY);
  130. if (fd < 0) {
  131. blog(LOG_ERROR, "Failed to open device '%s'.", handle->device);
  132. return -1;
  133. }
  134. int val = handle->channels;
  135. err = ioctl(fd, SNDCTL_DSP_CHANNELS, &val);
  136. if (err) {
  137. blog(LOG_ERROR, "Failed to set number of channels on DSP '%s'.",
  138. handle->device);
  139. goto failed_state;
  140. }
  141. val = handle->sample_fmt;
  142. err = ioctl(fd, SNDCTL_DSP_SETFMT, &val);
  143. if (err) {
  144. blog(LOG_ERROR, "Failed to set format on DSP '%s'.",
  145. handle->device);
  146. goto failed_state;
  147. }
  148. val = handle->rate;
  149. err = ioctl(fd, SNDCTL_DSP_SPEED, &val);
  150. if (err) {
  151. blog(LOG_ERROR, "Failed to set sample rate on DSP '%s'.",
  152. handle->device);
  153. goto failed_state;
  154. }
  155. err = ioctl(fd, SNDCTL_DSP_GETISPACE, &bi);
  156. if (err) {
  157. blog(LOG_ERROR, "Failed to get fragment size on DSP '%s'.",
  158. handle->device);
  159. goto failed_state;
  160. }
  161. dsp_fragsize = bi.fragsize;
  162. dsp_buf = bmalloc(dsp_fragsize);
  163. if (dsp_buf == NULL)
  164. goto failed_state;
  165. handle->dsp_buf = dsp_buf;
  166. handle->dsp_fragsize = dsp_fragsize;
  167. handle->dsp_fd = fd;
  168. return 0;
  169. failed_state:
  170. if (fd != -1)
  171. close(fd);
  172. bfree(dsp_buf);
  173. return -1;
  174. }
  175. static void oss_close_device(struct oss_input_data *handle)
  176. {
  177. if (handle->dsp_fd != -1)
  178. close(handle->dsp_fd);
  179. bfree(handle->dsp_buf);
  180. handle->dsp_fd = -1;
  181. handle->dsp_buf = NULL;
  182. handle->dsp_fragsize = 0;
  183. }
  184. static void *oss_reader_thr(void *vptr)
  185. {
  186. struct oss_input_data *handle = vptr;
  187. struct pollfd fds[2] = {0};
  188. size_t framesize;
  189. framesize = oss_calc_framesize(handle->channels, handle->sample_fmt);
  190. fds[0].fd = handle->dsp_fd;
  191. fds[0].events = POLLIN;
  192. fds[1].fd = handle->notify_pipe[0];
  193. fds[1].events = POLLIN;
  194. assert(handle->dsp_buf);
  195. while (poll(fds, 2, INFTIM) >= 0) {
  196. if (fds[0].revents & POLLIN) {
  197. /*
  198. * Incoming audio frames
  199. */
  200. struct obs_source_audio out;
  201. ssize_t nbytes;
  202. do {
  203. nbytes = read(handle->dsp_fd, handle->dsp_buf,
  204. handle->dsp_fragsize);
  205. } while (nbytes < 0 && errno == EINTR);
  206. if (nbytes < 0) {
  207. blog(LOG_ERROR,
  208. "%s: Failed to read buffer on DSP '%s'. Errno %d",
  209. __func__, handle->device, errno);
  210. break;
  211. } else if (!nbytes) {
  212. blog(LOG_ERROR,
  213. "%s: Unexpected EOF on DSP '%s'.",
  214. __func__, handle->device);
  215. break;
  216. }
  217. out.data[0] = handle->dsp_buf;
  218. out.format =
  219. oss_fmt_to_obs_audio_format(handle->sample_fmt);
  220. out.speakers =
  221. oss_channels_to_obs_speakers(handle->channels);
  222. out.samples_per_sec = handle->rate;
  223. out.frames = nbytes / framesize;
  224. out.timestamp =
  225. os_gettime_ns() -
  226. ((out.frames * NSEC_PER_SEC) / handle->rate);
  227. obs_source_output_audio(handle->source, &out);
  228. }
  229. if (fds[1].revents & POLLIN) {
  230. char buf;
  231. ssize_t nbytes;
  232. do {
  233. nbytes = read(handle->notify_pipe[0], &buf, 1);
  234. assert(nbytes != 0);
  235. } while (nbytes < 0 && errno == EINTR);
  236. break;
  237. }
  238. }
  239. return NULL;
  240. }
  241. static int oss_start_reader(struct oss_input_data *handle)
  242. {
  243. int pfd[2];
  244. int err;
  245. pthread_t thr;
  246. err = pipe(pfd);
  247. if (err)
  248. return -1;
  249. err = pthread_create(&thr, NULL, oss_reader_thr, handle);
  250. if (err) {
  251. close(pfd[0]);
  252. close(pfd[1]);
  253. return -1;
  254. }
  255. handle->notify_pipe[0] = pfd[0];
  256. handle->notify_pipe[1] = pfd[1];
  257. handle->reader_thr = thr;
  258. return 0;
  259. }
  260. static void oss_stop_reader(struct oss_input_data *handle)
  261. {
  262. if (handle->reader_thr) {
  263. char buf = 0x0;
  264. write(handle->notify_pipe[1], &buf, 1);
  265. pthread_join(handle->reader_thr, NULL);
  266. }
  267. if (handle->notify_pipe[0] != -1) {
  268. close(handle->notify_pipe[0]);
  269. close(handle->notify_pipe[1]);
  270. }
  271. handle->notify_pipe[0] = -1;
  272. handle->notify_pipe[1] = -1;
  273. handle->reader_thr = NULL;
  274. }
  275. /**
  276. * Returns the name of the plugin
  277. */
  278. static const char *oss_getname(void *unused)
  279. {
  280. UNUSED_PARAMETER(unused);
  281. return obs_module_text("OSS Input");
  282. }
  283. /**
  284. * Create the plugin object
  285. */
  286. static void *oss_create(obs_data_t *settings, obs_source_t *source)
  287. {
  288. const char *dsp;
  289. const char *custom_dsp;
  290. struct oss_input_data *handle;
  291. dsp = obs_data_get_string(settings, OBS_PROPS_DSP);
  292. custom_dsp = obs_data_get_string(settings, OBS_PROPS_CUSTOM_DSP);
  293. handle = bmalloc(sizeof(struct oss_input_data));
  294. if (handle == NULL)
  295. return NULL;
  296. handle->source = source;
  297. handle->device = NULL;
  298. handle->channels = 0;
  299. handle->rate = 0;
  300. handle->sample_fmt = 0;
  301. handle->dsp_buf = NULL;
  302. handle->dsp_fragsize = 0;
  303. handle->dsp_fd = -1;
  304. handle->notify_pipe[0] = -1;
  305. handle->notify_pipe[1] = -1;
  306. handle->reader_thr = NULL;
  307. if (dsp == NULL)
  308. return handle;
  309. if (!strcmp(dsp, OBS_PATH_DSP_CUSTOM)) {
  310. if (custom_dsp == NULL)
  311. return handle;
  312. handle->device = bstrdup(custom_dsp);
  313. } else
  314. handle->device = bstrdup(dsp);
  315. if (handle->device == NULL)
  316. goto failed_state;
  317. handle->channels = obs_data_get_int(settings, OBS_PROPS_CHANNELS);
  318. handle->rate = obs_data_get_int(settings, OBS_PROPS_RATE);
  319. handle->sample_fmt = obs_data_get_int(settings, OBS_PROPS_SAMPLE_FMT);
  320. int err = oss_setup_device(handle);
  321. if (err)
  322. goto failed_state;
  323. err = oss_start_reader(handle);
  324. if (err) {
  325. oss_close_device(handle);
  326. goto failed_state;
  327. }
  328. return handle;
  329. failed_state:
  330. bfree(handle);
  331. return NULL;
  332. }
  333. /**
  334. * Destroy the plugin object and free all memory
  335. */
  336. static void oss_destroy(void *vptr)
  337. {
  338. struct oss_input_data *handle = vptr;
  339. oss_stop_reader(handle);
  340. oss_close_device(handle);
  341. bfree(handle->device);
  342. bfree(handle);
  343. }
  344. /**
  345. * Update the input settings
  346. */
  347. static void oss_update(void *vptr, obs_data_t *settings)
  348. {
  349. struct oss_input_data *handle = vptr;
  350. oss_stop_reader(handle);
  351. oss_close_device(handle);
  352. const char *dsp = obs_data_get_string(settings, OBS_PROPS_DSP);
  353. const char *custom_dsp =
  354. obs_data_get_string(settings, OBS_PROPS_CUSTOM_DSP);
  355. if (dsp == NULL) {
  356. bfree(handle->device);
  357. handle->device = NULL;
  358. return;
  359. }
  360. bfree(handle->device);
  361. handle->device = NULL;
  362. if (!strcmp(dsp, OBS_PATH_DSP_CUSTOM)) {
  363. if (custom_dsp == NULL)
  364. return;
  365. handle->device = bstrdup(custom_dsp);
  366. } else
  367. handle->device = bstrdup(dsp);
  368. if (handle->device == NULL)
  369. return;
  370. handle->channels = obs_data_get_int(settings, OBS_PROPS_CHANNELS);
  371. handle->rate = obs_data_get_int(settings, OBS_PROPS_RATE);
  372. handle->sample_fmt = obs_data_get_int(settings, OBS_PROPS_SAMPLE_FMT);
  373. int err = oss_setup_device(handle);
  374. if (err) {
  375. goto failed_state;
  376. return;
  377. }
  378. err = oss_start_reader(handle);
  379. if (err) {
  380. oss_close_device(handle);
  381. goto failed_state;
  382. }
  383. return;
  384. failed_state:
  385. bfree(handle->device);
  386. handle->device = NULL;
  387. }
  388. /**
  389. * Add audio devices to property
  390. */
  391. static void oss_prop_add_devices(obs_property_t *p)
  392. {
  393. #if defined(__FreeBSD__) || defined(__DragonFly__)
  394. char *line = NULL;
  395. size_t linecap = 0;
  396. FILE *fp;
  397. fp = fopen(OSS_SNDSTAT_PATH, "r");
  398. if (fp == NULL) {
  399. blog(LOG_ERROR, "Failed to open sndstat at '%s'.",
  400. OSS_SNDSTAT_PATH);
  401. return;
  402. }
  403. while (getline(&line, &linecap, fp) > 0) {
  404. int pcm;
  405. char *ptr, *pdesc, *descr, *devname, *pmode;
  406. if (sscanf(line, "pcm%i: ", &pcm) != 1)
  407. continue;
  408. if ((ptr = strchr(line, '<')) == NULL)
  409. continue;
  410. pdesc = ptr + 1;
  411. if ((ptr = strrchr(pdesc, '>')) == NULL)
  412. continue;
  413. *ptr++ = '\0';
  414. if (*ptr++ != ' ' || *ptr++ != '(')
  415. continue;
  416. pmode = ptr;
  417. if ((ptr = strrchr(pmode, ')')) == NULL)
  418. continue;
  419. *ptr++ = '\0';
  420. if (strcmp(pmode, "rec") != 0 && strcmp(pmode, "play/rec") != 0)
  421. continue;
  422. if (asprintf(&descr, "pcm%i: %s", pcm, pdesc) == -1)
  423. continue;
  424. if (asprintf(&devname, "/dev/dsp%i", pcm) == -1) {
  425. free(descr);
  426. continue;
  427. }
  428. obs_property_list_add_string(p, descr, devname);
  429. free(descr);
  430. free(devname);
  431. }
  432. free(line);
  433. fclose(fp);
  434. #endif /* defined(__FreeBSD__) || defined(__DragonFly__) */
  435. }
  436. /**
  437. * Get plugin defaults
  438. */
  439. static void oss_defaults(obs_data_t *settings)
  440. {
  441. obs_data_set_default_int(settings, OBS_PROPS_CHANNELS,
  442. OSS_CHANNELS_DEFAULT);
  443. obs_data_set_default_int(settings, OBS_PROPS_RATE, OSS_RATE_DEFAULT);
  444. obs_data_set_default_int(settings, OBS_PROPS_SAMPLE_FMT, AFMT_S16_LE);
  445. obs_data_set_default_string(settings, OBS_PROPS_DSP, OSS_DSP_DEFAULT);
  446. }
  447. /**
  448. * Get plugin properties:
  449. *
  450. * Fetch the engine information of the corresponding DSP
  451. */
  452. static bool oss_fill_device_info(obs_property_t *rate, obs_property_t *channels,
  453. const char *device)
  454. {
  455. oss_audioinfo ai;
  456. int fd = -1;
  457. int err;
  458. obs_property_list_clear(rate);
  459. obs_property_list_clear(channels);
  460. if (!strcmp(device, OBS_PATH_DSP_CUSTOM))
  461. goto cleanup;
  462. fd = open(device, O_RDONLY);
  463. if (fd < 0) {
  464. blog(LOG_ERROR, "Failed to open device '%s'.", device);
  465. goto cleanup;
  466. }
  467. ai.dev = -1;
  468. err = ioctl(fd, SNDCTL_ENGINEINFO, &ai);
  469. if (err) {
  470. blog(LOG_ERROR,
  471. "Failed to issue ioctl(SNDCTL_ENGINEINFO) on device '%s'. Errno: %d",
  472. device, errno);
  473. goto cleanup;
  474. }
  475. for (int i = ai.min_channels;
  476. i <= ai.max_channels && i <= OSS_MAX_CHANNELS; i++) {
  477. enum speaker_layout layout = oss_channels_to_obs_speakers(i);
  478. if (layout != SPEAKERS_UNKNOWN) {
  479. char name[] = "xxx";
  480. snprintf(name, sizeof(name), "%d", i);
  481. obs_property_list_add_int(channels, name, i);
  482. }
  483. }
  484. for (size_t i = 0; i < sizeof(rate_table) / sizeof(rate_table[0]);
  485. i++) {
  486. if (ai.min_rate <= rate_table[i].rate &&
  487. ai.max_rate >= rate_table[i].rate)
  488. obs_property_list_add_int(rate, rate_table[i].desc,
  489. rate_table[i].rate);
  490. }
  491. cleanup:
  492. if (!obs_property_list_item_count(rate))
  493. obs_property_list_add_int(rate, "48000 Hz", OSS_RATE_DEFAULT);
  494. if (!obs_property_list_item_count(channels))
  495. obs_property_list_add_int(channels, "2", OSS_CHANNELS_DEFAULT);
  496. if (fd != -1)
  497. close(fd);
  498. return true;
  499. }
  500. /**
  501. * Get plugin properties
  502. */
  503. static bool oss_on_devices_changed(obs_properties_t *props, obs_property_t *p,
  504. obs_data_t *settings)
  505. {
  506. obs_property_t *rate, *channels;
  507. obs_property_t *custom_dsp;
  508. const char *device;
  509. UNUSED_PARAMETER(p);
  510. device = obs_data_get_string(settings, OBS_PROPS_DSP);
  511. custom_dsp = obs_properties_get(props, OBS_PROPS_CUSTOM_DSP);
  512. rate = obs_properties_get(props, OBS_PROPS_RATE);
  513. channels = obs_properties_get(props, OBS_PROPS_CHANNELS);
  514. if (!strcmp(device, OBS_PATH_DSP_CUSTOM))
  515. obs_property_set_visible(custom_dsp, true);
  516. else
  517. obs_property_set_visible(custom_dsp, false);
  518. oss_fill_device_info(rate, channels, device);
  519. obs_property_modified(rate, settings);
  520. obs_property_modified(channels, settings);
  521. obs_property_modified(custom_dsp, settings);
  522. return true;
  523. }
  524. /**
  525. * Get plugin properties
  526. */
  527. static obs_properties_t *oss_properties(void *unused)
  528. {
  529. obs_properties_t *props;
  530. obs_property_t *devices;
  531. obs_property_t *rate;
  532. obs_property_t *sample_fmt;
  533. obs_property_t *channels;
  534. UNUSED_PARAMETER(unused);
  535. props = obs_properties_create();
  536. devices = obs_properties_add_list(props, OBS_PROPS_DSP,
  537. obs_module_text("DSP"),
  538. OBS_COMBO_TYPE_LIST,
  539. OBS_COMBO_FORMAT_STRING);
  540. obs_property_list_add_string(devices, "Default", OSS_DSP_DEFAULT);
  541. obs_property_list_add_string(devices, "Custom", OBS_PATH_DSP_CUSTOM);
  542. obs_property_set_modified_callback(devices, oss_on_devices_changed);
  543. obs_properties_add_text(props, OBS_PROPS_CUSTOM_DSP,
  544. obs_module_text("Custom DSP Path"),
  545. OBS_TEXT_DEFAULT);
  546. rate = obs_properties_add_list(props, OBS_PROPS_RATE,
  547. obs_module_text("Sample rate"),
  548. OBS_COMBO_TYPE_LIST,
  549. OBS_COMBO_FORMAT_INT);
  550. channels = obs_properties_add_list(props, OBS_PROPS_CHANNELS,
  551. obs_module_text("Channels"),
  552. OBS_COMBO_TYPE_LIST,
  553. OBS_COMBO_FORMAT_INT);
  554. oss_fill_device_info(rate, channels, OSS_DSP_DEFAULT);
  555. sample_fmt = obs_properties_add_list(props, OBS_PROPS_SAMPLE_FMT,
  556. obs_module_text("Sample format"),
  557. OBS_COMBO_TYPE_LIST,
  558. OBS_COMBO_FORMAT_INT);
  559. obs_property_list_add_int(sample_fmt, "pcm8", AFMT_U8);
  560. obs_property_list_add_int(sample_fmt, "pcm16le", AFMT_S16_LE);
  561. obs_property_list_add_int(sample_fmt, "pcm32le", AFMT_S32_LE);
  562. oss_prop_add_devices(devices);
  563. return props;
  564. }
  565. struct obs_source_info oss_input_capture = {
  566. .id = "oss_input_capture",
  567. .type = OBS_SOURCE_TYPE_INPUT,
  568. .output_flags = OBS_SOURCE_AUDIO,
  569. .get_name = oss_getname,
  570. .create = oss_create,
  571. .destroy = oss_destroy,
  572. .update = oss_update,
  573. .get_defaults = oss_defaults,
  574. .get_properties = oss_properties,
  575. .icon_type = OBS_ICON_TYPE_AUDIO_INPUT,
  576. };