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 "cubeb-internal.h"
8 #include "cubeb/cubeb.h"
9 #include <fcntl.h>
10 #include <limits.h>
11 #include <pthread.h>
12 #include <stdbool.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/audioio.h>
17 #include <sys/ioctl.h>
18 #include <unistd.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, int * props,
149                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 &&
187          prinfo->sample_rate > SUN_MIN_RATE;
188 }
189 
190 static int
sun_enumerate_devices(cubeb * context,cubeb_device_type type,cubeb_device_collection * collection)191 sun_enumerate_devices(cubeb * context, cubeb_device_type type,
192                       cubeb_device_collection * collection)
193 {
194   unsigned i;
195   cubeb_device_info device = {0};
196   char dev[16] = SUN_DEFAULT_DEVICE;
197   char dev_friendly[64];
198   struct audio_info hwfmt;
199   struct audio_device hwname;
200   struct audio_prinfo * prinfo = NULL;
201   int hwprops;
202 
203   collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
204   if (collection->device == NULL) {
205     return CUBEB_ERROR;
206   }
207   collection->count = 0;
208 
209   for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
210     if (i > 0) {
211       (void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
212     }
213     if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
214       continue;
215     }
216 #ifdef AUDIO_GETPROPS
217     device.type = 0;
218     if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
219         sun_prinfo_verify_sanity(&hwfmt.record)) {
220       /* the device supports recording, probably */
221       device.type |= CUBEB_DEVICE_TYPE_INPUT;
222     }
223     if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
224         sun_prinfo_verify_sanity(&hwfmt.play)) {
225       /* the device supports playback, probably */
226       device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
227     }
228     switch (device.type) {
229     case 0:
230       /* device doesn't do input or output, aliens probably involved */
231       continue;
232     case CUBEB_DEVICE_TYPE_INPUT:
233       if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
234         /* this device is input only, not scanning for those, skip it */
235         continue;
236       }
237       break;
238     case CUBEB_DEVICE_TYPE_OUTPUT:
239       if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
240         /* this device is output only, not scanning for those, skip it */
241         continue;
242       }
243       break;
244     }
245     if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
246       prinfo = &hwfmt.record;
247     }
248     if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
249       prinfo = &hwfmt.play;
250     }
251 #endif
252     if (i > 0) {
253       (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
254                      hwname.name, hwname.version, hwname.config, i - 1);
255     } else {
256       (void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
257                      hwname.name, hwname.version, hwname.config);
258     }
259     device.devid = (void *)(uintptr_t)i;
260     device.device_id = strdup(dev);
261     device.friendly_name = strdup(dev_friendly);
262     device.group_id = strdup(dev);
263     device.vendor_name = strdup(hwname.name);
264     device.type = type;
265     device.state = CUBEB_DEVICE_STATE_ENABLED;
266     device.preferred =
267         (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
268 #ifdef AUDIO_GETFORMAT
269     device.max_channels = prinfo->channels;
270     device.default_rate = prinfo->sample_rate;
271 #else
272     device.max_channels = 2;
273     device.default_rate = SUN_PREFER_RATE;
274 #endif
275     device.default_format = CUBEB_DEVICE_FMT_S16NE;
276     device.format = CUBEB_DEVICE_FMT_S16NE;
277     device.min_rate = SUN_MIN_RATE;
278     device.max_rate = SUN_MAX_RATE;
279     device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
280     device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
281     collection->device[collection->count++] = device;
282   }
283   return CUBEB_OK;
284 }
285 
286 static int
sun_device_collection_destroy(cubeb * context,cubeb_device_collection * collection)287 sun_device_collection_destroy(cubeb * context,
288                               cubeb_device_collection * collection)
289 {
290   unsigned i;
291 
292   for (i = 0; i < collection->count; ++i) {
293     free((char *)collection->device[i].device_id);
294     free((char *)collection->device[i].friendly_name);
295     free((char *)collection->device[i].group_id);
296     free((char *)collection->device[i].vendor_name);
297   }
298   free(collection->device);
299   return CUBEB_OK;
300 }
301 
302 static int
sun_copy_params(int fd,cubeb_stream * stream,cubeb_stream_params * params,struct audio_info * info,struct audio_prinfo * prinfo)303 sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
304                 struct audio_info * info, struct audio_prinfo * prinfo)
305 {
306   prinfo->channels = params->channels;
307   prinfo->sample_rate = params->rate;
308 #ifdef AUDIO_ENCODING_SLINEAR_LE
309   switch (params->format) {
310   case CUBEB_SAMPLE_S16LE:
311     prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
312     prinfo->precision = 16;
313     break;
314   case CUBEB_SAMPLE_S16BE:
315     prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
316     prinfo->precision = 16;
317     break;
318   case CUBEB_SAMPLE_FLOAT32NE:
319     prinfo->encoding = AUDIO_ENCODING_SLINEAR;
320     prinfo->precision = 32;
321     break;
322   default:
323     LOG("Unsupported format");
324     return CUBEB_ERROR_INVALID_FORMAT;
325   }
326 #else
327   switch (params->format) {
328   case CUBEB_SAMPLE_S16NE:
329     prinfo->encoding = AUDIO_ENCODING_LINEAR;
330     prinfo->precision = 16;
331     break;
332   case CUBEB_SAMPLE_FLOAT32NE:
333     prinfo->encoding = AUDIO_ENCODING_LINEAR;
334     prinfo->precision = 32;
335     break;
336   default:
337     LOG("Unsupported format");
338     return CUBEB_ERROR_INVALID_FORMAT;
339   }
340 #endif
341   if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
342     return CUBEB_ERROR;
343   }
344   if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
345     return CUBEB_ERROR;
346   }
347   return CUBEB_OK;
348 }
349 
350 static int
sun_stream_stop(cubeb_stream * s)351 sun_stream_stop(cubeb_stream * s)
352 {
353   pthread_mutex_lock(&s->mutex);
354   if (s->running) {
355     s->running = false;
356     pthread_mutex_unlock(&s->mutex);
357     pthread_join(s->thread, NULL);
358   } else {
359     pthread_mutex_unlock(&s->mutex);
360   }
361   return CUBEB_OK;
362 }
363 
364 static void
sun_stream_destroy(cubeb_stream * s)365 sun_stream_destroy(cubeb_stream * s)
366 {
367   sun_stream_stop(s);
368   pthread_mutex_destroy(&s->mutex);
369   if (s->play.fd != -1) {
370     close(s->play.fd);
371   }
372   if (s->record.fd != -1) {
373     close(s->record.fd);
374   }
375   free(s->play.buf);
376   free(s->record.buf);
377   free(s);
378 }
379 
380 static void
sun_float_to_linear32(void * buf,unsigned sample_count,float vol)381 sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
382 {
383   float * in = buf;
384   int32_t * out = buf;
385   int32_t * tail = out + sample_count;
386 
387   while (out < tail) {
388     float f = *(in++) * vol;
389     if (f < -1.0)
390       f = -1.0;
391     else if (f > 1.0)
392       f = 1.0;
393     *(out++) = f * (float)INT32_MAX;
394   }
395 }
396 
397 static void
sun_linear32_to_float(void * buf,unsigned sample_count)398 sun_linear32_to_float(void * buf, unsigned sample_count)
399 {
400   int32_t * in = buf;
401   float * out = buf;
402   float * tail = out + sample_count;
403 
404   while (out < tail) {
405     *(out++) = (1.0 / 0x80000000) * *(in++);
406   }
407 }
408 
409 static void
sun_linear16_set_vol(int16_t * buf,unsigned sample_count,float vol)410 sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
411 {
412   unsigned i;
413   int32_t multiplier = vol * 0x8000;
414 
415   for (i = 0; i < sample_count; ++i) {
416     buf[i] = (buf[i] * multiplier) >> 15;
417   }
418 }
419 
420 static void *
sun_io_routine(void * arg)421 sun_io_routine(void * arg)
422 {
423   cubeb_stream * s = arg;
424   cubeb_state state = CUBEB_STATE_STARTED;
425   size_t to_read = 0;
426   long to_write = 0;
427   size_t write_ofs = 0;
428   size_t read_ofs = 0;
429   int drain = 0;
430 
431   s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
432   while (state != CUBEB_STATE_ERROR) {
433     pthread_mutex_lock(&s->mutex);
434     if (!s->running) {
435       pthread_mutex_unlock(&s->mutex);
436       state = CUBEB_STATE_STOPPED;
437       break;
438     }
439     pthread_mutex_unlock(&s->mutex);
440     if (s->record.fd != -1 && s->record.floating) {
441       sun_linear32_to_float(s->record.buf,
442                             s->record.info.record.channels * SUN_BUFFER_FRAMES);
443     }
444     to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
445                           SUN_BUFFER_FRAMES);
446     if (to_write == CUBEB_ERROR) {
447       state = CUBEB_STATE_ERROR;
448       break;
449     }
450     if (s->play.fd != -1) {
451       float vol;
452 
453       pthread_mutex_lock(&s->mutex);
454       vol = s->volume;
455       pthread_mutex_unlock(&s->mutex);
456 
457       if (s->play.floating) {
458         sun_float_to_linear32(s->play.buf,
459                               s->play.info.play.channels * to_write, vol);
460       } else {
461         sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
462                              vol);
463       }
464     }
465     if (to_write < SUN_BUFFER_FRAMES) {
466       drain = 1;
467     }
468     to_write = s->play.fd != -1 ? to_write : 0;
469     to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
470     write_ofs = 0;
471     read_ofs = 0;
472     while (to_write > 0 || to_read > 0) {
473       size_t bytes;
474       ssize_t n, frames;
475 
476       if (to_write > 0) {
477         bytes = to_write * s->play.frame_size;
478         if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
479             0) {
480           state = CUBEB_STATE_ERROR;
481           break;
482         }
483         frames = n / s->play.frame_size;
484         pthread_mutex_lock(&s->mutex);
485         s->frames_written += frames;
486         pthread_mutex_unlock(&s->mutex);
487         to_write -= frames;
488         write_ofs += n;
489       }
490       if (to_read > 0) {
491         bytes = to_read * s->record.frame_size;
492         if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
493                       bytes)) < 0) {
494           state = CUBEB_STATE_ERROR;
495           break;
496         }
497         frames = n / s->record.frame_size;
498         to_read -= frames;
499         read_ofs += n;
500       }
501     }
502     if (drain && state != CUBEB_STATE_ERROR) {
503       state = CUBEB_STATE_DRAINED;
504       break;
505     }
506   }
507   s->state_cb(s, s->user_ptr, state);
508   return NULL;
509 }
510 
511 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)512 sun_stream_init(cubeb * context, cubeb_stream ** stream,
513                 char const * stream_name, cubeb_devid input_device,
514                 cubeb_stream_params * input_stream_params,
515                 cubeb_devid output_device,
516                 cubeb_stream_params * output_stream_params,
517                 unsigned latency_frames, cubeb_data_callback data_callback,
518                 cubeb_state_callback state_callback, 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), "/dev/audio%zu",
533              (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), "/dev/audio%zu",
539              (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)) !=
562         CUBEB_OK) {
563       LOG("Setting record params failed");
564       goto error;
565     }
566     s->record.floating =
567         (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
568   }
569   if (output_stream_params != NULL) {
570     if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
571       LOG("Loopback not supported");
572       ret = CUBEB_ERROR_NOT_SUPPORTED;
573       goto error;
574     }
575     if (s->play.fd == -1) {
576       if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
577         LOG("Audio device could not be opened as write-only");
578         ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
579         goto error;
580       }
581     }
582     AUDIO_INITINFO(&s->play.info);
583 #ifdef AUMODE_PLAY
584     s->play.info.mode = AUMODE_PLAY;
585 #endif
586     if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
587                                &s->play.info, &s->play.info.play)) !=
588         CUBEB_OK) {
589       LOG("Setting play params failed");
590       goto error;
591     }
592     s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
593   }
594   s->context = context;
595   s->volume = 1.0;
596   s->state_cb = state_callback;
597   s->data_cb = data_callback;
598   s->user_ptr = user_ptr;
599   if (pthread_mutex_init(&s->mutex, NULL) != 0) {
600     LOG("Failed to create mutex");
601     goto error;
602   }
603   s->play.frame_size =
604       s->play.info.play.channels * (s->play.info.play.precision / 8);
605   if (s->play.fd != -1 &&
606       (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
607     ret = CUBEB_ERROR;
608     goto error;
609   }
610   s->record.frame_size =
611       s->record.info.record.channels * (s->record.info.record.precision / 8);
612   if (s->record.fd != -1 &&
613       (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
614           NULL) {
615     ret = CUBEB_ERROR;
616     goto error;
617   }
618   *stream = s;
619   return CUBEB_OK;
620 error:
621   if (s != NULL) {
622     sun_stream_destroy(s);
623   }
624   return ret;
625 }
626 
627 static int
sun_stream_start(cubeb_stream * s)628 sun_stream_start(cubeb_stream * s)
629 {
630   s->running = true;
631   if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
632     LOG("Couldn't create thread");
633     return CUBEB_ERROR;
634   }
635   return CUBEB_OK;
636 }
637 
638 static int
sun_stream_get_position(cubeb_stream * s,uint64_t * position)639 sun_stream_get_position(cubeb_stream * s, uint64_t * position)
640 {
641 #ifdef AUDIO_GETOOFFS
642   struct audio_offset offset;
643 
644   if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
645     return CUBEB_ERROR;
646   }
647   s->blocks_written += offset.deltablks;
648   *position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
649   return CUBEB_OK;
650 #else
651   pthread_mutex_lock(&s->mutex);
652   *position = s->frames_written;
653   pthread_mutex_unlock(&s->mutex);
654   return CUBEB_OK;
655 #endif
656 }
657 
658 static int
sun_stream_get_latency(cubeb_stream * s,uint32_t * latency)659 sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
660 {
661 #ifdef AUDIO_GETBUFINFO
662   struct audio_info info;
663 
664   if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
665     return CUBEB_ERROR;
666   }
667 
668   *latency = (info.play.seek + info.blocksize) / s->play.frame_size;
669   return CUBEB_OK;
670 #else
671   cubeb_stream_params params;
672 
673   params.rate = s->play.info.play.sample_rate;
674 
675   return sun_get_min_latency(NULL, params, latency);
676 #endif
677 }
678 
679 static int
sun_stream_set_volume(cubeb_stream * stream,float volume)680 sun_stream_set_volume(cubeb_stream * stream, float volume)
681 {
682   pthread_mutex_lock(&stream->mutex);
683   stream->volume = volume;
684   pthread_mutex_unlock(&stream->mutex);
685   return CUBEB_OK;
686 }
687 
688 static int
sun_get_current_device(cubeb_stream * stream,cubeb_device ** const device)689 sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
690 {
691   *device = calloc(1, sizeof(cubeb_device));
692   if (*device == NULL) {
693     return CUBEB_ERROR;
694   }
695   (*device)->input_name =
696       stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
697   (*device)->output_name =
698       stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
699   return CUBEB_OK;
700 }
701 
702 static int
sun_stream_device_destroy(cubeb_stream * stream,cubeb_device * device)703 sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
704 {
705   (void)stream;
706   free(device->input_name);
707   free(device->output_name);
708   free(device);
709   return CUBEB_OK;
710 }
711 
712 static struct cubeb_ops const sun_ops = {
713     .init = sun_init,
714     .get_backend_id = sun_get_backend_id,
715     .get_max_channel_count = sun_get_max_channel_count,
716     .get_min_latency = sun_get_min_latency,
717     .get_preferred_sample_rate = sun_get_preferred_sample_rate,
718     .enumerate_devices = sun_enumerate_devices,
719     .device_collection_destroy = sun_device_collection_destroy,
720     .destroy = sun_destroy,
721     .stream_init = sun_stream_init,
722     .stream_destroy = sun_stream_destroy,
723     .stream_start = sun_stream_start,
724     .stream_stop = sun_stream_stop,
725     .stream_get_position = sun_stream_get_position,
726     .stream_get_latency = sun_stream_get_latency,
727     .stream_get_input_latency = NULL,
728     .stream_set_volume = sun_stream_set_volume,
729     .stream_set_name = NULL,
730     .stream_get_current_device = sun_get_current_device,
731     .stream_device_destroy = sun_stream_device_destroy,
732     .stream_register_device_changed_callback = NULL,
733     .register_device_collection_changed = NULL};
734