1 /*
2  * Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
3  *
4  * This program is made available under an ISC-style license.  See the
5  * accompanying file LICENSE for details.
6  */
7 #include <sys/audioio.h>
8 #include <sys/ioctl.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 #include <pthread.h>
12 #include <stdbool.h>
13 #include <stdlib.h>
14 #include <stdio.h>
15 #include <string.h>
16 #include <limits.h>
17 #include "cubeb/cubeb.h"
18 #include "cubeb-internal.h"
19 
20 /* Default to 4 + 1 for the default device. */
21 #ifndef SUN_DEVICE_COUNT
22 #define SUN_DEVICE_COUNT (5)
23 #endif
24 
25 /* Supported well by most hardware. */
26 #ifndef SUN_PREFER_RATE
27 #define SUN_PREFER_RATE (48000)
28 #endif
29 
30 /* Standard acceptable minimum. */
31 #ifndef SUN_LATENCY_MS
32 #define SUN_LATENCY_MS (40)
33 #endif
34 
35 #ifndef SUN_DEFAULT_DEVICE
36 #define SUN_DEFAULT_DEVICE "/dev/audio"
37 #endif
38 
39 #ifndef SUN_BUFFER_FRAMES
40 #define SUN_BUFFER_FRAMES (32)
41 #endif
42 
43 /*
44  * Supported on NetBSD regardless of hardware.
45  */
46 
47 #ifndef SUN_MAX_CHANNELS
48 # ifdef __NetBSD__
49 #  define SUN_MAX_CHANNELS (12)
50 # else
51 #  define SUN_MAX_CHANNELS (2)
52 # endif
53 #endif
54 
55 #ifndef SUN_MIN_RATE
56 #define SUN_MIN_RATE (1000)
57 #endif
58 
59 #ifndef SUN_MAX_RATE
60 #define SUN_MAX_RATE (192000)
61 #endif
62 
63 static struct cubeb_ops const sun_ops;
64 
65 struct cubeb {
66   struct cubeb_ops const * ops;
67 };
68 
69 struct sun_stream {
70   char name[32];
71   int fd;
72   void * buf;
73   struct audio_info info;
74   unsigned frame_size; /* precision in bytes * channels */
75   bool floating;
76 };
77 
78 struct cubeb_stream {
79   struct cubeb * context;
80   void * user_ptr;
81   pthread_t thread;
82   pthread_mutex_t mutex; /* protects running, volume, frames_written */
83   bool running;
84   float volume;
85   struct sun_stream play;
86   struct sun_stream record;
87   cubeb_data_callback data_cb;
88   cubeb_state_callback state_cb;
89   uint64_t frames_written;
90   uint64_t blocks_written;
91 };
92 
93 int
sun_init(cubeb ** context,char const * context_name)94 sun_init(cubeb ** context, char const * context_name)
95 {
96   cubeb * c;
97 
98   (void)context_name;
99   if ((c = calloc(1, sizeof(cubeb))) == NULL) {
100     return CUBEB_ERROR;
101   }
102   c->ops = &sun_ops;
103   *context = c;
104   return CUBEB_OK;
105 }
106 
107 static void
sun_destroy(cubeb * context)108 sun_destroy(cubeb * context)
109 {
110   free(context);
111 }
112 
113 static char const *
sun_get_backend_id(cubeb * context)114 sun_get_backend_id(cubeb * context)
115 {
116   return "sun";
117 }
118 
119 static int
sun_get_preferred_sample_rate(cubeb * context,uint32_t * rate)120 sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
121 {
122   (void)context;
123 
124   *rate = SUN_PREFER_RATE;
125   return CUBEB_OK;
126 }
127 
128 static int
sun_get_max_channel_count(cubeb * context,uint32_t * max_channels)129 sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
130 {
131   (void)context;
132 
133   *max_channels = SUN_MAX_CHANNELS;
134   return CUBEB_OK;
135 }
136 
137 static int
sun_get_min_latency(cubeb * context,cubeb_stream_params params,uint32_t * latency_frames)138 sun_get_min_latency(cubeb * context, cubeb_stream_params params,
139                     uint32_t * latency_frames)
140 {
141   (void)context;
142 
143   *latency_frames = SUN_LATENCY_MS * params.rate / 1000;
144   return CUBEB_OK;
145 }
146 
147 static int
sun_get_hwinfo(const char * device,struct audio_info * format,int * props,struct audio_device * dev)148 sun_get_hwinfo(const char * device, struct audio_info * format,
149                int * props, struct audio_device * dev)
150 {
151   int fd = -1;
152 
153   if ((fd = open(device, O_RDONLY)) == -1) {
154     goto error;
155   }
156 #ifdef AUDIO_GETFORMAT
157   if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
158     goto error;
159   }
160 #endif
161 #ifdef AUDIO_GETPROPS
162   if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
163     goto error;
164   }
165 #endif
166   if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
167     goto error;
168   }
169   close(fd);
170   return CUBEB_OK;
171 error:
172   if (fd != -1) {
173     close(fd);
174   }
175   return CUBEB_ERROR;
176 }
177 
178 /*
179  * XXX: PR kern/54264
180  */
181 static int
sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)182 sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
183 {
184    return prinfo->precision >= 8 && prinfo->precision <= 32 &&
185      prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
186      prinfo->sample_rate < SUN_MAX_RATE && prinfo->sample_rate > SUN_MIN_RATE;
187 }
188 
189 static int
sun_enumerate_devices(cubeb * context,cubeb_device_type type,cubeb_device_collection * collection)190 sun_enumerate_devices(cubeb * context, cubeb_device_type type,
191                       cubeb_device_collection * collection)
192 {
193   unsigned i;
194   cubeb_device_info device = {0};
195   char dev[16] = SUN_DEFAULT_DEVICE;
196   char dev_friendly[64];
197   struct audio_info hwfmt;
198   struct audio_device hwname;
199   struct audio_prinfo *prinfo = NULL;
200   int hwprops;
201 
202   collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
203   if (collection->device == NULL) {
204     return CUBEB_ERROR;
205   }
206   collection->count = 0;
207 
208   for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
209     if (i > 0) {
210       (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
211     }
212     if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
213       continue;
214     }
215 #ifdef AUDIO_GETPROPS
216     device.type = 0;
217     if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
218         sun_prinfo_verify_sanity(&hwfmt.record)) {
219       /* the device supports recording, probably */
220       device.type |= CUBEB_DEVICE_TYPE_INPUT;
221     }
222     if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
223         sun_prinfo_verify_sanity(&hwfmt.play)) {
224       /* the device supports playback, probably */
225       device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
226     }
227     switch (device.type) {
228     case 0:
229       /* device doesn't do input or output, aliens probably involved */
230       continue;
231     case CUBEB_DEVICE_TYPE_INPUT:
232       if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
233         /* this device is input only, not scanning for those, skip it */
234         continue;
235       }
236       break;
237     case CUBEB_DEVICE_TYPE_OUTPUT:
238       if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
239         /* this device is output only, not scanning for those, skip it */
240         continue;
241       }
242       break;
243     }
244     if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
245       prinfo = &hwfmt.record;
246     }
247     if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
248       prinfo = &hwfmt.play;
249     }
250 #endif
251     if (i > 0) {
252       (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
253                      hwname.name, hwname.version, hwname.config, i - 1);
254     } else {
255       (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
256                      hwname.name, hwname.version, hwname.config);
257     }
258     device.devid = (void *)(uintptr_t)i;
259     device.device_id = strdup(dev);
260     device.friendly_name = strdup(dev_friendly);
261     device.group_id = strdup(dev);
262     device.vendor_name = strdup(hwname.name);
263     device.type = type;
264     device.state = CUBEB_DEVICE_STATE_ENABLED;
265     device.preferred = (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
266 #ifdef AUDIO_GETFORMAT
267     device.max_channels = prinfo->channels;
268     device.default_rate = prinfo->sample_rate;
269 #else
270     device.max_channels = 2;
271     device.default_rate = SUN_PREFER_RATE;
272 #endif
273     device.default_format = CUBEB_DEVICE_FMT_S16NE;
274     device.format = CUBEB_DEVICE_FMT_S16NE;
275     device.min_rate = SUN_MIN_RATE;
276     device.max_rate = SUN_MAX_RATE;
277     device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
278     device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
279     collection->device[collection->count++] = device;
280   }
281   return CUBEB_OK;
282 }
283 
284 static int
sun_device_collection_destroy(cubeb * context,cubeb_device_collection * collection)285 sun_device_collection_destroy(cubeb * context,
286                               cubeb_device_collection * collection)
287 {
288   unsigned i;
289 
290   for (i = 0; i < collection->count; ++i) {
291     free((char *)collection->device[i].device_id);
292     free((char *)collection->device[i].friendly_name);
293     free((char *)collection->device[i].group_id);
294     free((char *)collection->device[i].vendor_name);
295   }
296   free(collection->device);
297   return CUBEB_OK;
298 }
299 
300 static int
sun_copy_params(int fd,cubeb_stream * stream,cubeb_stream_params * params,struct audio_info * info,struct audio_prinfo * prinfo)301 sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
302                 struct audio_info * info, struct audio_prinfo * prinfo)
303 {
304   prinfo->channels = params->channels;
305   prinfo->sample_rate = params->rate;
306 #ifdef AUDIO_ENCODING_SLINEAR_LE
307   switch (params->format) {
308   case CUBEB_SAMPLE_S16LE:
309     prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
310     prinfo->precision = 16;
311     break;
312   case CUBEB_SAMPLE_S16BE:
313     prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
314     prinfo->precision = 16;
315     break;
316   case CUBEB_SAMPLE_FLOAT32NE:
317     prinfo->encoding = AUDIO_ENCODING_SLINEAR;
318     prinfo->precision = 32;
319     break;
320   default:
321     LOG("Unsupported format");
322     return CUBEB_ERROR_INVALID_FORMAT;
323   }
324 #else
325   switch (params->format) {
326   case CUBEB_SAMPLE_S16NE:
327     prinfo->encoding = AUDIO_ENCODING_LINEAR;
328     prinfo->precision = 16;
329     break;
330   case CUBEB_SAMPLE_FLOAT32NE:
331     prinfo->encoding = AUDIO_ENCODING_LINEAR;
332     prinfo->precision = 32;
333     break;
334   default:
335     LOG("Unsupported format");
336     return CUBEB_ERROR_INVALID_FORMAT;
337   }
338 #endif
339   if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
340     return CUBEB_ERROR;
341   }
342   if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
343     return CUBEB_ERROR;
344   }
345   return CUBEB_OK;
346 }
347 
348 static int
sun_stream_stop(cubeb_stream * s)349 sun_stream_stop(cubeb_stream * s)
350 {
351   pthread_mutex_lock(&s->mutex);
352   if (s->running) {
353     s->running = false;
354     pthread_mutex_unlock(&s->mutex);
355     pthread_join(s->thread, NULL);
356   } else {
357     pthread_mutex_unlock(&s->mutex);
358   }
359   return CUBEB_OK;
360 }
361 
362 static void
sun_stream_destroy(cubeb_stream * s)363 sun_stream_destroy(cubeb_stream * s)
364 {
365   sun_stream_stop(s);
366   pthread_mutex_destroy(&s->mutex);
367   if (s->play.fd != -1) {
368     close(s->play.fd);
369   }
370   if (s->record.fd != -1) {
371     close(s->record.fd);
372   }
373   free(s->play.buf);
374   free(s->record.buf);
375   free(s);
376 }
377 
378 static void
sun_float_to_linear32(void * buf,unsigned sample_count,float vol)379 sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
380 {
381   float * in = buf;
382   int32_t * out = buf;
383   int32_t * tail = out + sample_count;
384 
385   while (out < tail) {
386     float f = *(in++) * vol;
387     if (f < -1.0)
388       f = -1.0;
389     else if (f > 1.0)
390       f = 1.0;
391     *(out++) = f * (float)INT32_MAX;
392   }
393 }
394 
395 static void
sun_linear32_to_float(void * buf,unsigned sample_count)396 sun_linear32_to_float(void * buf, unsigned sample_count)
397 {
398   int32_t * in = buf;
399   float * out = buf;
400   float * tail = out + sample_count;
401 
402   while (out < tail) {
403     *(out++) = (1.0 / 0x80000000) * *(in++);
404   }
405 }
406 
407 static void
sun_linear16_set_vol(int16_t * buf,unsigned sample_count,float vol)408 sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
409 {
410   unsigned i;
411   int32_t multiplier = vol * 0x8000;
412 
413   for (i = 0; i < sample_count; ++i) {
414     buf[i] = (buf[i] * multiplier) >> 15;
415   }
416 }
417 
418 static void *
sun_io_routine(void * arg)419 sun_io_routine(void * arg)
420 {
421   cubeb_stream *s = arg;
422   cubeb_state state = CUBEB_STATE_STARTED;
423   size_t to_read = 0;
424   long to_write = 0;
425   size_t write_ofs = 0;
426   size_t read_ofs = 0;
427   int drain = 0;
428 
429   s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
430   while (state != CUBEB_STATE_ERROR) {
431     pthread_mutex_lock(&s->mutex);
432     if (!s->running) {
433       pthread_mutex_unlock(&s->mutex);
434       state = CUBEB_STATE_STOPPED;
435       break;
436     }
437     pthread_mutex_unlock(&s->mutex);
438     if (s->record.fd != -1 && s->record.floating) {
439       sun_linear32_to_float(s->record.buf,
440                             s->record.info.record.channels * SUN_BUFFER_FRAMES);
441     }
442     to_write = s->data_cb(s, s->user_ptr,
443                           s->record.buf, s->play.buf, SUN_BUFFER_FRAMES);
444     if (to_write == CUBEB_ERROR) {
445       state = CUBEB_STATE_ERROR;
446       break;
447     }
448     if (s->play.fd != -1) {
449       float vol;
450 
451       pthread_mutex_lock(&s->mutex);
452       vol = s->volume;
453       pthread_mutex_unlock(&s->mutex);
454 
455       if (s->play.floating) {
456         sun_float_to_linear32(s->play.buf,
457                               s->play.info.play.channels * to_write, vol);
458       } else {
459         sun_linear16_set_vol(s->play.buf,
460                              s->play.info.play.channels * to_write, vol);
461       }
462     }
463     if (to_write < SUN_BUFFER_FRAMES) {
464       drain = 1;
465     }
466     to_write = s->play.fd != -1 ? to_write : 0;
467     to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
468     write_ofs = 0;
469     read_ofs = 0;
470     while (to_write > 0 || to_read > 0) {
471       size_t bytes;
472       ssize_t n, frames;
473 
474       if (to_write > 0) {
475         bytes = to_write * s->play.frame_size;
476         if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) < 0) {
477           state = CUBEB_STATE_ERROR;
478           break;
479         }
480         frames = n / s->play.frame_size;
481         pthread_mutex_lock(&s->mutex);
482         s->frames_written += frames;
483         pthread_mutex_unlock(&s->mutex);
484         to_write -= frames;
485         write_ofs += n;
486       }
487       if (to_read > 0) {
488         bytes = to_read * s->record.frame_size;
489         if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) {
490           state = CUBEB_STATE_ERROR;
491           break;
492         }
493         frames = n / s->record.frame_size;
494         to_read -= frames;
495         read_ofs += n;
496       }
497     }
498     if (drain && state != CUBEB_STATE_ERROR) {
499       state = CUBEB_STATE_DRAINED;
500       break;
501     }
502   }
503   s->state_cb(s, s->user_ptr, state);
504   return NULL;
505 }
506 
507 static int
sun_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 latency_frames,cubeb_data_callback data_callback,cubeb_state_callback state_callback,void * user_ptr)508 sun_stream_init(cubeb * context,
509                 cubeb_stream ** stream,
510                 char const * stream_name,
511                 cubeb_devid input_device,
512                 cubeb_stream_params * input_stream_params,
513                 cubeb_devid output_device,
514                 cubeb_stream_params * output_stream_params,
515                 unsigned latency_frames,
516                 cubeb_data_callback data_callback,
517                 cubeb_state_callback state_callback,
518                 void * user_ptr)
519 {
520   int ret = CUBEB_OK;
521   cubeb_stream *s = NULL;
522 
523   (void)stream_name;
524   (void)latency_frames;
525   if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
526     ret = CUBEB_ERROR;
527     goto error;
528   }
529   s->record.fd = -1;
530   s->play.fd = -1;
531   if (input_device != 0) {
532     snprintf(s->record.name, sizeof(s->record.name),
533       "/dev/audio%zu", (uintptr_t)input_device - 1);
534   } else {
535     snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
536   }
537   if (output_device != 0) {
538     snprintf(s->play.name, sizeof(s->play.name),
539       "/dev/audio%zu", (uintptr_t)output_device - 1);
540   } else {
541     snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
542   }
543   if (input_stream_params != NULL) {
544     if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
545       LOG("Loopback not supported");
546       ret = CUBEB_ERROR_NOT_SUPPORTED;
547       goto error;
548     }
549     if (s->record.fd == -1) {
550       if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
551         LOG("Audio device could not be opened as read-only");
552         ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
553         goto error;
554       }
555     }
556     AUDIO_INITINFO(&s->record.info);
557 #ifdef AUMODE_RECORD
558     s->record.info.mode = AUMODE_RECORD;
559 #endif
560     if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
561                                &s->record.info, &s->record.info.record)) != CUBEB_OK) {
562       LOG("Setting record params failed");
563       goto error;
564     }
565     s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
566   }
567   if (output_stream_params != NULL) {
568     if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
569       LOG("Loopback not supported");
570       ret = CUBEB_ERROR_NOT_SUPPORTED;
571       goto error;
572     }
573     if (s->play.fd == -1) {
574       if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
575         LOG("Audio device could not be opened as write-only");
576         ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
577         goto error;
578       }
579     }
580     AUDIO_INITINFO(&s->play.info);
581 #ifdef AUMODE_PLAY
582     s->play.info.mode = AUMODE_PLAY;
583 #endif
584     if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
585                                &s->play.info, &s->play.info.play)) != CUBEB_OK) {
586       LOG("Setting play params failed");
587       goto error;
588     }
589     s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
590   }
591   s->context = context;
592   s->volume = 1.0;
593   s->state_cb = state_callback;
594   s->data_cb = data_callback;
595   s->user_ptr = user_ptr;
596   if (pthread_mutex_init(&s->mutex, NULL) != 0) {
597     LOG("Failed to create mutex");
598     goto error;
599   }
600   s->play.frame_size = s->play.info.play.channels *
601                       (s->play.info.play.precision / 8);
602   if (s->play.fd != -1 &&
603      (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
604     ret = CUBEB_ERROR;
605     goto error;
606   }
607   s->record.frame_size = s->record.info.record.channels *
608                         (s->record.info.record.precision / 8);
609   if (s->record.fd != -1 &&
610      (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == NULL) {
611     ret = CUBEB_ERROR;
612     goto error;
613   }
614   *stream = s;
615   return CUBEB_OK;
616 error:
617   if (s != NULL) {
618     sun_stream_destroy(s);
619   }
620   return ret;
621 }
622 
623 static int
sun_stream_start(cubeb_stream * s)624 sun_stream_start(cubeb_stream * s)
625 {
626   s->running = true;
627   if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
628     LOG("Couldn't create thread");
629     return CUBEB_ERROR;
630   }
631   return CUBEB_OK;
632 }
633 
634 static int
sun_stream_get_position(cubeb_stream * s,uint64_t * position)635 sun_stream_get_position(cubeb_stream * s, uint64_t * position)
636 {
637 #ifdef AUDIO_GETOOFFS
638   struct audio_offset offset;
639 
640   if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
641     return CUBEB_ERROR;
642   }
643   s->blocks_written += offset.deltablks;
644   *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
645   return CUBEB_OK;
646 #else
647   pthread_mutex_lock(&s->mutex);
648   *position = s->frames_written;
649   pthread_mutex_unlock(&s->mutex);
650   return CUBEB_OK;
651 #endif
652 }
653 
654 static int
sun_stream_get_latency(cubeb_stream * s,uint32_t * latency)655 sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
656 {
657 #ifdef AUDIO_GETBUFINFO
658   struct audio_info info;
659 
660   if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
661     return CUBEB_ERROR;
662   }
663 
664   *latency = (info.play.seek + info.blocksize) / s->play.frame_size;
665   return CUBEB_OK;
666 #else
667   cubeb_stream_params params;
668 
669   params.rate = s->play.info.play.sample_rate;
670 
671   return sun_get_min_latency(NULL, params, latency);
672 #endif
673 }
674 
675 static int
sun_stream_set_volume(cubeb_stream * stream,float volume)676 sun_stream_set_volume(cubeb_stream * stream, float volume)
677 {
678   pthread_mutex_lock(&stream->mutex);
679   stream->volume = volume;
680   pthread_mutex_unlock(&stream->mutex);
681   return CUBEB_OK;
682 }
683 
684 static int
sun_get_current_device(cubeb_stream * stream,cubeb_device ** const device)685 sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
686 {
687   *device = calloc(1, sizeof(cubeb_device));
688   if (*device == NULL) {
689     return CUBEB_ERROR;
690   }
691   (*device)->input_name = stream->record.fd != -1 ?
692     strdup(stream->record.name) : NULL;
693   (*device)->output_name = stream->play.fd != -1 ?
694     strdup(stream->play.name) : NULL;
695   return CUBEB_OK;
696 }
697 
698 static int
sun_stream_device_destroy(cubeb_stream * stream,cubeb_device * device)699 sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
700 {
701   (void)stream;
702   free(device->input_name);
703   free(device->output_name);
704   free(device);
705   return CUBEB_OK;
706 }
707 
708 static struct cubeb_ops const sun_ops = {
709   .init = sun_init,
710   .get_backend_id = sun_get_backend_id,
711   .get_max_channel_count = sun_get_max_channel_count,
712   .get_min_latency = sun_get_min_latency,
713   .get_preferred_sample_rate = sun_get_preferred_sample_rate,
714   .enumerate_devices = sun_enumerate_devices,
715   .device_collection_destroy = sun_device_collection_destroy,
716   .destroy = sun_destroy,
717   .stream_init = sun_stream_init,
718   .stream_destroy = sun_stream_destroy,
719   .stream_start = sun_stream_start,
720   .stream_stop = sun_stream_stop,
721   .stream_get_position = sun_stream_get_position,
722   .stream_get_latency = sun_stream_get_latency,
723   .stream_get_input_latency = NULL,
724   .stream_set_volume = sun_stream_set_volume,
725   .stream_set_name = NULL,
726   .stream_get_current_device = sun_get_current_device,
727   .stream_device_destroy = sun_stream_device_destroy,
728   .stream_register_device_changed_callback = NULL,
729   .register_device_collection_changed = NULL
730 };
731