oss-input.c 16 KB

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