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