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_reset_default_device = NULL,
722 .stream_get_position = sun_stream_get_position,
723 .stream_get_latency = sun_stream_get_latency,
724 .stream_get_input_latency = NULL,
725 .stream_set_volume = sun_stream_set_volume,
726 .stream_set_name = NULL,
727 .stream_get_current_device = sun_get_current_device,
728 .stream_device_destroy = sun_stream_device_destroy,
729 .stream_register_device_changed_callback = NULL,
730 .register_device_collection_changed = NULL
731 };
732