sndio-input.c 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. /*
  2. Copyright (C) 2020 by Vadim Zhukov <[email protected]>
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. #include <sys/types.h>
  15. #include <sys/socket.h>
  16. #include <errno.h>
  17. #include <poll.h>
  18. #include <pthread.h>
  19. #include <sndio.h>
  20. #include <string.h>
  21. #include <unistd.h>
  22. #include <util/bmem.h>
  23. #include <util/platform.h>
  24. #include <util/threading.h>
  25. #include <util/util_uint64.h>
  26. #include <obs.h>
  27. #include <obs-module.h>
  28. #include "sndio-input.h"
  29. #define blog(level, msg, ...) \
  30. blog(level, "sndio-input: %s: " msg, __func__, ##__VA_ARGS__);
  31. #define berr(level, msg, ...) \
  32. do { \
  33. const char *errstr = strerror(errno); \
  34. blog(level, msg ": %s", ##__VA_ARGS__, errstr); \
  35. } while (0)
  36. #define NSEC_PER_SEC 1000000000LL
  37. /**
  38. * Returns the name of the plugin
  39. */
  40. static const char *sndio_input_getname(void *unused)
  41. {
  42. UNUSED_PARAMETER(unused);
  43. return obs_module_text("SndioInput");
  44. }
  45. static enum speaker_layout sndio_channels_to_obs_speakers(int channels)
  46. {
  47. switch (channels) {
  48. case 1:
  49. return SPEAKERS_MONO;
  50. case 2:
  51. return SPEAKERS_STEREO;
  52. case 3:
  53. return SPEAKERS_2POINT1;
  54. case 4:
  55. return SPEAKERS_4POINT0;
  56. case 5:
  57. return SPEAKERS_4POINT1;
  58. case 6:
  59. return SPEAKERS_5POINT1;
  60. case 8:
  61. return SPEAKERS_7POINT1;
  62. }
  63. return SPEAKERS_UNKNOWN;
  64. }
  65. static enum audio_format sndio_to_obs_audio_format(const struct sio_par *par)
  66. {
  67. switch (par->bits) {
  68. case 8:
  69. return AUDIO_FORMAT_U8BIT;
  70. case 16:
  71. return AUDIO_FORMAT_16BIT;
  72. case 32:
  73. return AUDIO_FORMAT_32BIT;
  74. }
  75. return AUDIO_FORMAT_UNKNOWN;
  76. }
  77. /**
  78. * Runs sndio tasks on a pre-opened audio device.
  79. * Whenever user chooses another device, this thread gets signalled to exit,
  80. * and another one starts immediately, without waiting for the previous.
  81. */
  82. static void *sndio_thread(void *attr)
  83. {
  84. struct sndio_thr_data *thrdata = attr;
  85. size_t msgread = 0; // msg bytes read from socket
  86. size_t nsiofds = sio_nfds(thrdata->hdl);
  87. struct pollfd pfd[1 + nsiofds];
  88. struct sio_par par;
  89. uint64_t ts;
  90. ssize_t nread;
  91. int pollres;
  92. uint8_t *buf;
  93. size_t bufsz;
  94. ts = os_gettime_ns();
  95. bufsz = thrdata->par.appbufsz * thrdata->par.bps * 2;
  96. if ((buf = bmalloc(bufsz * 2)) == NULL) {
  97. blog(LOG_ERROR, "could not allocate record buffer of %zu bytes",
  98. bufsz);
  99. goto finish;
  100. }
  101. memset(pfd, 0, sizeof(pfd));
  102. pfd[0].fd = thrdata->sock;
  103. for (;;) {
  104. for (size_t i = 0; i <= nsiofds; i++)
  105. pfd[i].revents = 0;
  106. pfd[0].events = POLLIN;
  107. sio_pollfd(thrdata->hdl, pfd + 1, POLLIN);
  108. if (poll(pfd, 1 + nsiofds, /*INFTIM*/ -1) == -1) {
  109. if (errno == EINTR)
  110. continue;
  111. berr(LOG_ERROR, "exiting due to poll error");
  112. goto finish;
  113. }
  114. if ((pfd[0].revents & POLLHUP) == POLLHUP) {
  115. blog(LOG_INFO,
  116. "exiting upon receiving EOF at IPC socket");
  117. goto finish;
  118. }
  119. if ((pfd[0].revents & POLLIN) == POLLIN) {
  120. nread = read(pfd[0].fd, ((uint8_t *)&par) + msgread,
  121. sizeof(par) - msgread);
  122. switch (nread) {
  123. case -1:
  124. if (errno == EAGAIN)
  125. goto proceed_sio;
  126. berr(LOG_ERROR,
  127. "reading from IPC socket failed");
  128. goto finish;
  129. case 0:
  130. blog(LOG_INFO,
  131. "exiting upon receiving EOF at IPC socket");
  132. goto finish;
  133. default:
  134. msgread += (size_t)nread;
  135. if (msgread == sizeof(struct sio_par)) {
  136. size_t tbufsz;
  137. uint8_t *tbuf;
  138. msgread = 0;
  139. sio_stop(thrdata->hdl);
  140. if (!sio_setpar(thrdata->hdl, &par)) {
  141. blog(LOG_WARNING,
  142. "sio_setpar failed, keeping old params");
  143. }
  144. blog(LOG_INFO,
  145. "after sio_setpar(): appbufsz=%u bps=%u",
  146. par.appbufsz, par.bps);
  147. memcpy(&thrdata->par, &par,
  148. sizeof(struct sio_par));
  149. tbufsz = thrdata->par.appbufsz *
  150. thrdata->par.bps * 2;
  151. if ((tbuf = brealloc(buf, tbufsz)) ==
  152. NULL) {
  153. blog(LOG_ERROR,
  154. "could not reallocate record buffer of %zu bytes",
  155. tbufsz);
  156. goto finish;
  157. }
  158. buf = tbuf;
  159. bufsz = tbufsz;
  160. if (!sio_start(thrdata->hdl)) {
  161. blog(LOG_ERROR,
  162. "sio_start failed, exiting");
  163. goto finish;
  164. }
  165. ts = os_gettime_ns();
  166. // Since we restarted recording,
  167. // do not try to handle events we lost.
  168. continue;
  169. }
  170. }
  171. }
  172. proceed_sio:
  173. pollres = sio_revents(thrdata->hdl, pfd + 1);
  174. if ((pollres & POLLHUP) == POLLHUP) {
  175. blog(LOG_ERROR, "sndio device error happened, exiting");
  176. goto finish;
  177. }
  178. if ((pollres & POLLIN) == POLLIN) {
  179. struct obs_source_audio out;
  180. unsigned int nframes;
  181. nread = (ssize_t)sio_read(thrdata->hdl, buf, bufsz);
  182. if (nread == 0) {
  183. if (sio_eof(thrdata->hdl)) {
  184. blog(LOG_ERROR,
  185. "sndio device EOF happened, exiting");
  186. goto finish;
  187. }
  188. continue;
  189. }
  190. nframes = (unsigned int)nread / thrdata->par.bps;
  191. //blog(LOG_INFO, "sio_read returned %u, nframes = %u", nread, nframes);
  192. memset(&out, 0, sizeof(struct obs_source_audio));
  193. out.data[0] = buf;
  194. out.frames = nframes;
  195. out.format = sndio_to_obs_audio_format(&thrdata->par);
  196. out.speakers = sndio_channels_to_obs_speakers(
  197. thrdata->par.rchan);
  198. out.samples_per_sec = thrdata->par.rate;
  199. out.timestamp = ts;
  200. ts += util_mul_div64(nframes, NSEC_PER_SEC,
  201. thrdata->par.rate);
  202. obs_source_output_audio(thrdata->source, &out);
  203. }
  204. }
  205. finish:
  206. sio_close(thrdata->hdl);
  207. close(thrdata->sock);
  208. bfree(buf);
  209. bfree(thrdata);
  210. return NULL;
  211. }
  212. /**
  213. * Destroy the plugin object and free all memory
  214. */
  215. static void sndio_destroy(void *vptr)
  216. {
  217. struct sndio_data *data = vptr;
  218. if (!data)
  219. return;
  220. close(data->sock);
  221. data->sock = -1;
  222. pthread_attr_destroy(&data->attr);
  223. bfree(data);
  224. }
  225. /**
  226. * Tries to apply the input settings.
  227. * Must be called on stopped device.
  228. */
  229. static void sndio_apply(struct sndio_data *data, obs_data_t *settings)
  230. {
  231. struct sndio_thr_data *thrdata;
  232. pthread_t thread;
  233. int ec;
  234. int socks[2] = {-1, -1};
  235. const char *devname = obs_data_get_string(settings, "device");
  236. if ((thrdata = bzalloc(sizeof(struct sndio_thr_data))) == NULL) {
  237. blog(LOG_ERROR, "malloc");
  238. return;
  239. }
  240. if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0,
  241. socks) == -1) {
  242. berr(LOG_ERROR, "socketpair");
  243. goto error;
  244. }
  245. if (data->sock != -1)
  246. close(data->sock);
  247. data->sock = socks[0];
  248. thrdata->sock = socks[1];
  249. thrdata->source = data->source;
  250. thrdata->hdl = sio_open(devname, SIO_REC, 0);
  251. if (!thrdata->hdl) {
  252. berr(LOG_ERROR, "could not open %s sndio device", devname);
  253. goto error;
  254. }
  255. sio_initpar(&thrdata->par);
  256. thrdata->par.bits = obs_data_get_int(settings, "bits");
  257. thrdata->par.bps = SIO_BPS(thrdata->par.bits);
  258. thrdata->par.sig = thrdata->par.bits > 8;
  259. thrdata->par.le = 1;
  260. thrdata->par.rate = obs_data_get_int(settings, "rate");
  261. thrdata->par.rchan = obs_data_get_int(settings, "channels");
  262. thrdata->par.xrun = SIO_SYNC; // makes timestamping easy
  263. if (!sio_setpar(thrdata->hdl, &thrdata->par)) {
  264. berr(LOG_ERROR, "could not set parameters for %s sndio device",
  265. devname);
  266. goto error;
  267. }
  268. blog(LOG_INFO, "after initial sio_setpar(): appbufsz=%u bps=%u",
  269. thrdata->par.appbufsz, thrdata->par.bps);
  270. if (!sio_start(thrdata->hdl)) {
  271. berr(LOG_ERROR, "could not start recording on %s sndio device",
  272. devname);
  273. goto error;
  274. }
  275. ec = pthread_create(&thread, &data->attr, sndio_thread, thrdata);
  276. if (ec != 0) {
  277. blog(LOG_ERROR, "could not start thread");
  278. goto error;
  279. }
  280. return;
  281. error:
  282. if (thrdata->hdl != NULL)
  283. sio_close(thrdata->hdl);
  284. close(socks[0]);
  285. close(socks[1]);
  286. bfree(thrdata);
  287. }
  288. /**
  289. * Update the input settings.
  290. */
  291. static void sndio_update(void *vptr, obs_data_t *settings)
  292. {
  293. struct sndio_data *data = vptr;
  294. sndio_apply(data, settings);
  295. }
  296. /**
  297. * Create the plugin object
  298. */
  299. static void *sndio_create(obs_data_t *settings, obs_source_t *source)
  300. {
  301. struct sndio_data *data = bzalloc(sizeof(struct sndio_data));
  302. pthread_attr_init(&data->attr);
  303. pthread_attr_setdetachstate(&data->attr, PTHREAD_CREATE_DETACHED);
  304. data->source = source;
  305. data->sock = -1;
  306. sndio_apply(data, settings);
  307. return data;
  308. }
  309. /**
  310. * Get plugin defaults
  311. */
  312. static void sndio_input_defaults(obs_data_t *settings)
  313. {
  314. obs_data_set_default_string(settings, "device", SIO_DEVANY);
  315. obs_data_set_default_int(settings, "rate", 48000);
  316. obs_data_set_default_int(settings, "bits", 16);
  317. obs_data_set_default_int(settings, "channels", 2);
  318. }
  319. /**
  320. * Get plugin properties
  321. */
  322. static obs_properties_t *sndio_input_properties(void *unused)
  323. {
  324. obs_property_t *rate, *bits;
  325. UNUSED_PARAMETER(unused);
  326. obs_properties_t *props = obs_properties_create();
  327. obs_properties_add_text(props, "device", obs_module_text("Device"),
  328. OBS_TEXT_DEFAULT);
  329. rate = obs_properties_add_list(props, "rate", obs_module_text("Rate"),
  330. OBS_COMBO_TYPE_LIST,
  331. OBS_COMBO_FORMAT_INT);
  332. obs_property_list_add_int(rate, "11025 Hz", 11025);
  333. obs_property_list_add_int(rate, "22050 Hz", 22050);
  334. obs_property_list_add_int(rate, "32000 Hz", 32000);
  335. obs_property_list_add_int(rate, "44100 Hz", 44100);
  336. obs_property_list_add_int(rate, "48000 Hz", 48000);
  337. obs_property_list_add_int(rate, "96000 Hz", 96000);
  338. obs_property_list_add_int(rate, "192000 Hz", 192000);
  339. bits = obs_properties_add_list(props, "bits",
  340. obs_module_text("BitsPerSample"),
  341. OBS_COMBO_TYPE_LIST,
  342. OBS_COMBO_FORMAT_INT);
  343. obs_property_list_add_int(bits, "8", 8);
  344. obs_property_list_add_int(bits, "16", 16);
  345. obs_property_list_add_int(bits, "32", 32);
  346. obs_properties_add_int(props, "channels", obs_module_text("Channels"),
  347. 1, 8, 1);
  348. return props;
  349. }
  350. struct obs_source_info sndio_output_capture = {
  351. .id = "sndio_output_capture",
  352. .type = OBS_SOURCE_TYPE_INPUT,
  353. .output_flags = OBS_SOURCE_AUDIO,
  354. .get_name = sndio_input_getname,
  355. .create = sndio_create,
  356. .destroy = sndio_destroy,
  357. #if SHUTDOWN_ON_DEACTIVATE
  358. .activate = sndio_activate,
  359. .deactivate = sndio_deactivate,
  360. #endif
  361. .update = sndio_update,
  362. .get_defaults = sndio_input_defaults,
  363. .get_properties = sndio_input_properties,
  364. .icon_type = OBS_ICON_TYPE_AUDIO_OUTPUT,
  365. };