1 /*
2  * Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org>
3  *
4  * This program is made available under an ISC-style license.  See the
5  * accompanying file LICENSE for details.
6  */
7 #include "cubeb-internal.h"
8 #include "cubeb/cubeb.h"
9 #include <assert.h>
10 #include <dlfcn.h>
11 #include <inttypes.h>
12 #include <math.h>
13 #include <poll.h>
14 #include <pthread.h>
15 #include <sndio.h>
16 #include <stdbool.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 
20 #if defined(CUBEB_SNDIO_DEBUG)
21 #define DPR(...) fprintf(stderr, __VA_ARGS__);
22 #else
23 #define DPR(...)                                                               \
24   do {                                                                         \
25   } while (0)
26 #endif
27 
28 #ifdef DISABLE_LIBSNDIO_DLOPEN
29 #define WRAP(x) x
30 #else
31 #define WRAP(x) (*cubeb_##x)
32 #define LIBSNDIO_API_VISIT(X)                                                  \
33   X(sio_close)                                                                 \
34   X(sio_eof)                                                                   \
35   X(sio_getpar)                                                                \
36   X(sio_initpar)                                                               \
37   X(sio_nfds)                                                                  \
38   X(sio_onmove)                                                                \
39   X(sio_open)                                                                  \
40   X(sio_pollfd)                                                                \
41   X(sio_read)                                                                  \
42   X(sio_revents)                                                               \
43   X(sio_setpar)                                                                \
44   X(sio_start)                                                                 \
45   X(sio_stop)                                                                  \
46   X(sio_write)
47 
48 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
49 LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
50 #undef MAKE_TYPEDEF
51 #endif
52 
53 static struct cubeb_ops const sndio_ops;
54 
55 struct cubeb {
56   struct cubeb_ops const * ops;
57   void * libsndio;
58 };
59 
60 struct cubeb_stream {
61   /* Note: Must match cubeb_stream layout in cubeb.c. */
62   cubeb * context;
63   void * arg; /* user arg to {data,state}_cb */
64   /**/
65   pthread_t th;                  /* to run real-time audio i/o */
66   pthread_mutex_t mtx;           /* protects hdl and pos */
67   struct sio_hdl * hdl;          /* link us to sndio */
68   int mode;                      /* bitmap of SIO_{PLAY,REC} */
69   int active;                    /* cubec_start() called */
70   int conv;                      /* need float->s16 conversion */
71   unsigned char * rbuf;          /* rec data consumed from here */
72   unsigned char * pbuf;          /* play data is prepared here */
73   unsigned int nfr;              /* number of frames in ibuf and obuf */
74   unsigned int rbpf;             /* rec bytes per frame */
75   unsigned int pbpf;             /* play bytes per frame */
76   unsigned int rchan;            /* number of rec channels */
77   unsigned int pchan;            /* number of play channels */
78   unsigned int nblks;            /* number of blocks in the buffer */
79   uint64_t hwpos;                /* frame number Joe hears right now */
80   uint64_t swpos;                /* number of frames produced/consumed */
81   cubeb_data_callback data_cb;   /* cb to preapare data */
82   cubeb_state_callback state_cb; /* cb to notify about state changes */
83   float volume;                  /* current volume */
84 };
85 
86 static void
s16_setvol(void * ptr,long nsamp,float volume)87 s16_setvol(void * ptr, long nsamp, float volume)
88 {
89   int16_t * dst = ptr;
90   int32_t mult = volume * 32768;
91   int32_t s;
92 
93   while (nsamp-- > 0) {
94     s = *dst;
95     s = (s * mult) >> 15;
96     *(dst++) = s;
97   }
98 }
99 
100 static void
float_to_s16(void * ptr,long nsamp,float volume)101 float_to_s16(void * ptr, long nsamp, float volume)
102 {
103   int16_t * dst = ptr;
104   float * src = ptr;
105   float mult = volume * 32768;
106   int s;
107 
108   while (nsamp-- > 0) {
109     s = lrintf(*(src++) * mult);
110     if (s < -32768)
111       s = -32768;
112     else if (s > 32767)
113       s = 32767;
114     *(dst++) = s;
115   }
116 }
117 
118 static void
s16_to_float(void * ptr,long nsamp)119 s16_to_float(void * ptr, long nsamp)
120 {
121   int16_t * src = ptr;
122   float * dst = ptr;
123 
124   src += nsamp;
125   dst += nsamp;
126   while (nsamp-- > 0)
127     *(--dst) = (1. / 32768) * *(--src);
128 }
129 
130 static const char *
sndio_get_device()131 sndio_get_device()
132 {
133 #ifdef __linux__
134   /*
135    * On other platforms default to sndio devices,
136    * so cubebs other backends can be used instead.
137    */
138   const char * dev = getenv("AUDIODEVICE");
139   if (dev == NULL || *dev == '\0')
140     return "snd/0";
141   return dev;
142 #else
143   return SIO_DEVANY;
144 #endif
145 }
146 
147 static void
sndio_onmove(void * arg,int delta)148 sndio_onmove(void * arg, int delta)
149 {
150   cubeb_stream * s = (cubeb_stream *)arg;
151 
152   s->hwpos += delta;
153 }
154 
155 static void *
sndio_mainloop(void * arg)156 sndio_mainloop(void * arg)
157 {
158   struct pollfd * pfds;
159   cubeb_stream * s = arg;
160   int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
161   size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
162   long nfr;
163 
164   nfds = WRAP(sio_nfds)(s->hdl);
165   pfds = calloc(nfds, sizeof(struct pollfd));
166   if (pfds == NULL)
167     return NULL;
168 
169   DPR("sndio_mainloop()\n");
170   s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
171   pthread_mutex_lock(&s->mtx);
172   if (!WRAP(sio_start)(s->hdl)) {
173     pthread_mutex_unlock(&s->mtx);
174     free(pfds);
175     return NULL;
176   }
177   DPR("sndio_mainloop(), started\n");
178 
179   if (s->mode & SIO_PLAY) {
180     pstart = pend = s->nfr * s->pbpf;
181     prime = s->nblks;
182     if (s->mode & SIO_REC) {
183       memset(s->rbuf, 0, s->nfr * s->rbpf);
184       rstart = rend = s->nfr * s->rbpf;
185     }
186   } else {
187     prime = 0;
188     rstart = 0;
189     rend = s->nfr * s->rbpf;
190   }
191 
192   for (;;) {
193     if (!s->active) {
194       DPR("sndio_mainloop() stopped\n");
195       state = CUBEB_STATE_STOPPED;
196       break;
197     }
198 
199     /* do we have a complete block? */
200     if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
201         (!(s->mode & SIO_REC) || rstart == rend)) {
202 
203       if (eof) {
204         DPR("sndio_mainloop() drained\n");
205         state = CUBEB_STATE_DRAINED;
206         break;
207       }
208 
209       if ((s->mode & SIO_REC) && s->conv)
210         s16_to_float(s->rbuf, s->nfr * s->rchan);
211 
212       /* invoke call-back, it returns less that s->nfr if done */
213       pthread_mutex_unlock(&s->mtx);
214       nfr = s->data_cb(s, s->arg, s->rbuf, s->pbuf, s->nfr);
215       pthread_mutex_lock(&s->mtx);
216       if (nfr < 0) {
217         DPR("sndio_mainloop() cb err\n");
218         state = CUBEB_STATE_ERROR;
219         break;
220       }
221       s->swpos += nfr;
222 
223       /* was this last call-back invocation (aka end-of-stream) ? */
224       if (nfr < s->nfr) {
225 
226         if (!(s->mode & SIO_PLAY) || nfr == 0) {
227           state = CUBEB_STATE_DRAINED;
228           break;
229         }
230 
231         /* need to write (aka drain) the partial play block we got */
232         pend = nfr * s->pbpf;
233         eof = 1;
234       }
235 
236       if (prime > 0)
237         prime--;
238 
239       if (s->mode & SIO_PLAY) {
240         if (s->conv)
241           float_to_s16(s->pbuf, nfr * s->pchan, s->volume);
242         else
243           s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
244       }
245 
246       if (s->mode & SIO_REC)
247         rstart = 0;
248       if (s->mode & SIO_PLAY)
249         pstart = 0;
250     }
251 
252     events = 0;
253     if ((s->mode & SIO_REC) && rstart < rend && prime == 0)
254       events |= POLLIN;
255     if ((s->mode & SIO_PLAY) && pstart < pend)
256       events |= POLLOUT;
257     nfds = WRAP(sio_pollfd)(s->hdl, pfds, events);
258 
259     if (nfds > 0) {
260       pthread_mutex_unlock(&s->mtx);
261       n = poll(pfds, nfds, -1);
262       pthread_mutex_lock(&s->mtx);
263       if (n < 0)
264         continue;
265     }
266 
267     revents = WRAP(sio_revents)(s->hdl, pfds);
268 
269     if (revents & POLLHUP) {
270       state = CUBEB_STATE_ERROR;
271       break;
272     }
273 
274     if (revents & POLLOUT) {
275       n = WRAP(sio_write)(s->hdl, s->pbuf + pstart, pend - pstart);
276       if (n == 0 && WRAP(sio_eof)(s->hdl)) {
277         DPR("sndio_mainloop() werr\n");
278         state = CUBEB_STATE_ERROR;
279         break;
280       }
281       pstart += n;
282     }
283 
284     if (revents & POLLIN) {
285       n = WRAP(sio_read)(s->hdl, s->rbuf + rstart, rend - rstart);
286       if (n == 0 && WRAP(sio_eof)(s->hdl)) {
287         DPR("sndio_mainloop() rerr\n");
288         state = CUBEB_STATE_ERROR;
289         break;
290       }
291       rstart += n;
292     }
293 
294     /* skip rec block, if not recording (yet) */
295     if (prime > 0 && (s->mode & SIO_REC))
296       rstart = rend;
297   }
298   WRAP(sio_stop)(s->hdl);
299   s->hwpos = s->swpos;
300   pthread_mutex_unlock(&s->mtx);
301   s->state_cb(s, s->arg, state);
302   free(pfds);
303   return NULL;
304 }
305 
306 /*static*/ int
sndio_init(cubeb ** context,char const * context_name)307 sndio_init(cubeb ** context, char const * context_name)
308 {
309   void * libsndio = NULL;
310   struct sio_hdl * hdl;
311 
312   assert(context);
313 
314 #ifndef DISABLE_LIBSNDIO_DLOPEN
315   libsndio = dlopen("libsndio.so.7.0", RTLD_LAZY);
316   if (!libsndio) {
317     libsndio = dlopen("libsndio.so", RTLD_LAZY);
318     if (!libsndio) {
319       DPR("sndio_init(%s) failed dlopen(libsndio.so)\n", context_name);
320       return CUBEB_ERROR;
321     }
322   }
323 
324 #define LOAD(x)                                                                \
325   {                                                                            \
326     cubeb_##x = dlsym(libsndio, #x);                                           \
327     if (!cubeb_##x) {                                                          \
328       DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x);              \
329       dlclose(libsndio);                                                       \
330       return CUBEB_ERROR;                                                      \
331     }                                                                          \
332   }
333 
334   LIBSNDIO_API_VISIT(LOAD);
335 #undef LOAD
336 #endif
337 
338   /* test if sndio works */
339   hdl = WRAP(sio_open)(sndio_get_device(), SIO_PLAY, 1);
340   if (hdl == NULL) {
341     return CUBEB_ERROR;
342   }
343   WRAP(sio_close)(hdl);
344 
345   DPR("sndio_init(%s)\n", context_name);
346   *context = malloc(sizeof(**context));
347   if (*context == NULL)
348     return CUBEB_ERROR;
349   (*context)->libsndio = libsndio;
350   (*context)->ops = &sndio_ops;
351   (void)context_name;
352   return CUBEB_OK;
353 }
354 
355 static char const *
sndio_get_backend_id(cubeb * context)356 sndio_get_backend_id(cubeb * context)
357 {
358   return "sndio";
359 }
360 
361 static void
sndio_destroy(cubeb * context)362 sndio_destroy(cubeb * context)
363 {
364   DPR("sndio_destroy()\n");
365   if (context->libsndio)
366     dlclose(context->libsndio);
367   free(context);
368 }
369 
370 static int
sndio_stream_init(cubeb * context,cubeb_stream ** stream,char const * stream_name,cubeb_devid input_device,cubeb_stream_params * input_stream_params,cubeb_devid output_device,cubeb_stream_params * output_stream_params,unsigned int latency_frames,cubeb_data_callback data_callback,cubeb_state_callback state_callback,void * user_ptr)371 sndio_stream_init(cubeb * context, cubeb_stream ** stream,
372                   char const * stream_name, cubeb_devid input_device,
373                   cubeb_stream_params * input_stream_params,
374                   cubeb_devid output_device,
375                   cubeb_stream_params * output_stream_params,
376                   unsigned int latency_frames,
377                   cubeb_data_callback data_callback,
378                   cubeb_state_callback state_callback, void * user_ptr)
379 {
380   cubeb_stream * s;
381   struct sio_par wpar, rpar;
382   cubeb_sample_format format;
383   int rate;
384   size_t bps;
385 
386   DPR("sndio_stream_init(%s)\n", stream_name);
387 
388   s = malloc(sizeof(cubeb_stream));
389   if (s == NULL)
390     return CUBEB_ERROR;
391   memset(s, 0, sizeof(cubeb_stream));
392   s->mode = 0;
393   if (input_stream_params) {
394     if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
395       DPR("sndio_stream_init(), loopback not supported\n");
396       goto err;
397     }
398     s->mode |= SIO_REC;
399     format = input_stream_params->format;
400     rate = input_stream_params->rate;
401   }
402   if (output_stream_params) {
403     if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
404       DPR("sndio_stream_init(), loopback not supported\n");
405       goto err;
406     }
407     s->mode |= SIO_PLAY;
408     format = output_stream_params->format;
409     rate = output_stream_params->rate;
410   }
411   if (s->mode == 0) {
412     DPR("sndio_stream_init(), neither playing nor recording\n");
413     goto err;
414   }
415   s->context = context;
416   s->hdl = WRAP(sio_open)(sndio_get_device(), s->mode, 1);
417   if (s->hdl == NULL) {
418     DPR("sndio_stream_init(), sio_open() failed\n");
419     goto err;
420   }
421   WRAP(sio_initpar)(&wpar);
422   wpar.sig = 1;
423   wpar.bits = 16;
424   switch (format) {
425   case CUBEB_SAMPLE_S16LE:
426     wpar.le = 1;
427     break;
428   case CUBEB_SAMPLE_S16BE:
429     wpar.le = 0;
430     break;
431   case CUBEB_SAMPLE_FLOAT32NE:
432     wpar.le = SIO_LE_NATIVE;
433     break;
434   default:
435     DPR("sndio_stream_init() unsupported format\n");
436     goto err;
437   }
438   wpar.rate = rate;
439   if (s->mode & SIO_REC)
440     wpar.rchan = input_stream_params->channels;
441   if (s->mode & SIO_PLAY)
442     wpar.pchan = output_stream_params->channels;
443   wpar.appbufsz = latency_frames;
444   if (!WRAP(sio_setpar)(s->hdl, &wpar) || !WRAP(sio_getpar)(s->hdl, &rpar)) {
445     DPR("sndio_stream_init(), sio_setpar() failed\n");
446     goto err;
447   }
448   if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
449       rpar.rate != wpar.rate ||
450       ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
451       ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
452     DPR("sndio_stream_init() unsupported params\n");
453     goto err;
454   }
455   WRAP(sio_onmove)(s->hdl, sndio_onmove, s);
456   s->active = 0;
457   s->nfr = rpar.round;
458   s->rbpf = rpar.bps * rpar.rchan;
459   s->pbpf = rpar.bps * rpar.pchan;
460   s->rchan = rpar.rchan;
461   s->pchan = rpar.pchan;
462   s->nblks = rpar.bufsz / rpar.round;
463   s->data_cb = data_callback;
464   s->state_cb = state_callback;
465   s->arg = user_ptr;
466   s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
467   s->hwpos = s->swpos = 0;
468   if (format == CUBEB_SAMPLE_FLOAT32LE) {
469     s->conv = 1;
470     bps = sizeof(float);
471   } else {
472     s->conv = 0;
473     bps = rpar.bps;
474   }
475   if (s->mode & SIO_PLAY) {
476     s->pbuf = malloc(bps * rpar.pchan * rpar.round);
477     if (s->pbuf == NULL)
478       goto err;
479   }
480   if (s->mode & SIO_REC) {
481     s->rbuf = malloc(bps * rpar.rchan * rpar.round);
482     if (s->rbuf == NULL)
483       goto err;
484   }
485   s->volume = 1.;
486   *stream = s;
487   DPR("sndio_stream_init() end, ok\n");
488   (void)context;
489   (void)stream_name;
490   return CUBEB_OK;
491 err:
492   if (s->hdl)
493     WRAP(sio_close)(s->hdl);
494   if (s->pbuf)
495     free(s->pbuf);
496   if (s->rbuf)
497     free(s->pbuf);
498   free(s);
499   return CUBEB_ERROR;
500 }
501 
502 static int
sndio_get_max_channel_count(cubeb * ctx,uint32_t * max_channels)503 sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
504 {
505   assert(ctx && max_channels);
506 
507   *max_channels = 8;
508 
509   return CUBEB_OK;
510 }
511 
512 static int
sndio_get_preferred_sample_rate(cubeb * ctx,uint32_t * rate)513 sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
514 {
515   /*
516    * We've no device-independent prefered rate; any rate will work if
517    * sndiod is running. If it isn't, 48kHz is what is most likely to
518    * work as most (but not all) devices support it.
519    */
520   *rate = 48000;
521   return CUBEB_OK;
522 }
523 
524 static int
sndio_get_min_latency(cubeb * ctx,cubeb_stream_params params,uint32_t * latency_frames)525 sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
526                       uint32_t * latency_frames)
527 {
528   /*
529    * We've no device-independent minimum latency.
530    */
531   *latency_frames = 2048;
532 
533   return CUBEB_OK;
534 }
535 
536 static void
sndio_stream_destroy(cubeb_stream * s)537 sndio_stream_destroy(cubeb_stream * s)
538 {
539   DPR("sndio_stream_destroy()\n");
540   WRAP(sio_close)(s->hdl);
541   if (s->mode & SIO_PLAY)
542     free(s->pbuf);
543   if (s->mode & SIO_REC)
544     free(s->rbuf);
545   free(s);
546 }
547 
548 static int
sndio_stream_start(cubeb_stream * s)549 sndio_stream_start(cubeb_stream * s)
550 {
551   int err;
552 
553   DPR("sndio_stream_start()\n");
554   s->active = 1;
555   err = pthread_create(&s->th, NULL, sndio_mainloop, s);
556   if (err) {
557     s->active = 0;
558     return CUBEB_ERROR;
559   }
560   return CUBEB_OK;
561 }
562 
563 static int
sndio_stream_stop(cubeb_stream * s)564 sndio_stream_stop(cubeb_stream * s)
565 {
566   void * dummy;
567 
568   DPR("sndio_stream_stop()\n");
569   if (s->active) {
570     s->active = 0;
571     pthread_join(s->th, &dummy);
572   }
573   return CUBEB_OK;
574 }
575 
576 static int
sndio_stream_get_position(cubeb_stream * s,uint64_t * p)577 sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
578 {
579   pthread_mutex_lock(&s->mtx);
580   DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
581   *p = s->hwpos;
582   pthread_mutex_unlock(&s->mtx);
583   return CUBEB_OK;
584 }
585 
586 static int
sndio_stream_set_volume(cubeb_stream * s,float volume)587 sndio_stream_set_volume(cubeb_stream * s, float volume)
588 {
589   DPR("sndio_stream_set_volume(%f)\n", volume);
590   pthread_mutex_lock(&s->mtx);
591   if (volume < 0.)
592     volume = 0.;
593   else if (volume > 1.0)
594     volume = 1.;
595   s->volume = volume;
596   pthread_mutex_unlock(&s->mtx);
597   return CUBEB_OK;
598 }
599 
600 int
sndio_stream_get_latency(cubeb_stream * stm,uint32_t * latency)601 sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
602 {
603   // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
604   // in the "Measuring the latency and buffers usage" paragraph.
605   *latency = stm->swpos - stm->hwpos;
606   return CUBEB_OK;
607 }
608 
609 static int
sndio_enumerate_devices(cubeb * context,cubeb_device_type type,cubeb_device_collection * collection)610 sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
611                         cubeb_device_collection * collection)
612 {
613   static char dev[] = SIO_DEVANY;
614   cubeb_device_info * device;
615 
616   device = malloc(sizeof(cubeb_device_info));
617   if (device == NULL)
618     return CUBEB_ERROR;
619 
620   device->devid = dev;         /* passed to stream_init() */
621   device->device_id = dev;     /* printable in UI */
622   device->friendly_name = dev; /* same, but friendly */
623   device->group_id = dev;      /* actual device if full-duplex */
624   device->vendor_name = NULL;  /* may be NULL */
625   device->type = type;         /* Input/Output */
626   device->state = CUBEB_DEVICE_STATE_ENABLED;
627   device->preferred = CUBEB_DEVICE_PREF_ALL;
628   device->format = CUBEB_DEVICE_FMT_S16NE;
629   device->default_format = CUBEB_DEVICE_FMT_S16NE;
630   device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
631   device->default_rate = 48000;
632   device->min_rate = 4000;
633   device->max_rate = 192000;
634   device->latency_lo = 480;
635   device->latency_hi = 9600;
636   collection->device = device;
637   collection->count = 1;
638   return CUBEB_OK;
639 }
640 
641 static int
sndio_device_collection_destroy(cubeb * context,cubeb_device_collection * collection)642 sndio_device_collection_destroy(cubeb * context,
643                                 cubeb_device_collection * collection)
644 {
645   free(collection->device);
646   return CUBEB_OK;
647 }
648 
649 static struct cubeb_ops const sndio_ops = {
650     .init = sndio_init,
651     .get_backend_id = sndio_get_backend_id,
652     .get_max_channel_count = sndio_get_max_channel_count,
653     .get_min_latency = sndio_get_min_latency,
654     .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
655     .enumerate_devices = sndio_enumerate_devices,
656     .device_collection_destroy = sndio_device_collection_destroy,
657     .destroy = sndio_destroy,
658     .stream_init = sndio_stream_init,
659     .stream_destroy = sndio_stream_destroy,
660     .stream_start = sndio_stream_start,
661     .stream_stop = sndio_stream_stop,
662     .stream_get_position = sndio_stream_get_position,
663     .stream_get_latency = sndio_stream_get_latency,
664     .stream_set_volume = sndio_stream_set_volume,
665     .stream_set_name = NULL,
666     .stream_get_current_device = NULL,
667     .stream_device_destroy = NULL,
668     .stream_register_device_changed_callback = NULL,
669     .register_device_collection_changed = NULL};
670