Line data Source code
1 : /*
2 : * Copyright © 2013 Mozilla Foundation
3 : *
4 : * This program is made available under an ISC-style license. See the
5 : * accompanying file LICENSE for details.
6 : */
7 : #undef NDEBUG
8 : #include <assert.h>
9 : #include <stddef.h>
10 : #include <stdlib.h>
11 : #include <string.h>
12 : #include "cubeb/cubeb.h"
13 : #include "cubeb-internal.h"
14 :
15 : #define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
16 :
17 : struct cubeb {
18 : struct cubeb_ops * ops;
19 : };
20 :
21 : struct cubeb_stream {
22 : struct cubeb * context;
23 : };
24 :
25 : #if defined(USE_PULSE)
26 : int pulse_init(cubeb ** context, char const * context_name);
27 : #endif
28 : #if defined(USE_PULSE_RUST)
29 : int pulse_rust_init(cubeb ** contet, char const * context_name);
30 : #endif
31 : #if defined(USE_JACK)
32 : int jack_init (cubeb ** context, char const * context_name);
33 : #endif
34 : #if defined(USE_ALSA)
35 : int alsa_init(cubeb ** context, char const * context_name);
36 : #endif
37 : #if defined(USE_AUDIOUNIT)
38 : int audiounit_init(cubeb ** context, char const * context_name);
39 : #endif
40 : #if defined(USE_WINMM)
41 : int winmm_init(cubeb ** context, char const * context_name);
42 : #endif
43 : #if defined(USE_WASAPI)
44 : int wasapi_init(cubeb ** context, char const * context_name);
45 : #endif
46 : #if defined(USE_SNDIO)
47 : int sndio_init(cubeb ** context, char const * context_name);
48 : #endif
49 : #if defined(USE_OPENSL)
50 : int opensl_init(cubeb ** context, char const * context_name);
51 : #endif
52 : #if defined(USE_AUDIOTRACK)
53 : int audiotrack_init(cubeb ** context, char const * context_name);
54 : #endif
55 : #if defined(USE_KAI)
56 : int kai_init(cubeb ** context, char const * context_name);
57 : #endif
58 :
59 : static int
60 0 : validate_stream_params(cubeb_stream_params * input_stream_params,
61 : cubeb_stream_params * output_stream_params)
62 : {
63 0 : XASSERT(input_stream_params || output_stream_params);
64 0 : if (output_stream_params) {
65 0 : if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
66 0 : output_stream_params->channels < 1 || output_stream_params->channels > 8) {
67 0 : return CUBEB_ERROR_INVALID_FORMAT;
68 : }
69 : }
70 0 : if (input_stream_params) {
71 0 : if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
72 0 : input_stream_params->channels < 1 || input_stream_params->channels > 8) {
73 0 : return CUBEB_ERROR_INVALID_FORMAT;
74 : }
75 : }
76 : // Rate and sample format must be the same for input and output, if using a
77 : // duplex stream
78 0 : if (input_stream_params && output_stream_params) {
79 0 : if (input_stream_params->rate != output_stream_params->rate ||
80 0 : input_stream_params->format != output_stream_params->format) {
81 0 : return CUBEB_ERROR_INVALID_FORMAT;
82 : }
83 : }
84 :
85 0 : cubeb_stream_params * params = input_stream_params ?
86 0 : input_stream_params : output_stream_params;
87 :
88 0 : switch (params->format) {
89 : case CUBEB_SAMPLE_S16LE:
90 : case CUBEB_SAMPLE_S16BE:
91 : case CUBEB_SAMPLE_FLOAT32LE:
92 : case CUBEB_SAMPLE_FLOAT32BE:
93 0 : return CUBEB_OK;
94 : }
95 :
96 0 : return CUBEB_ERROR_INVALID_FORMAT;
97 : }
98 :
99 : static int
100 0 : validate_latency(int latency)
101 : {
102 0 : if (latency < 1 || latency > 96000) {
103 0 : return CUBEB_ERROR_INVALID_PARAMETER;
104 : }
105 0 : return CUBEB_OK;
106 : }
107 :
108 : int
109 0 : cubeb_init(cubeb ** context, char const * context_name, char const * backend_name)
110 : {
111 0 : int (* init_oneshot)(cubeb **, char const *) = NULL;
112 :
113 0 : if (backend_name != NULL) {
114 0 : if (!strcmp(backend_name, "pulse")) {
115 : #if defined(USE_PULSE)
116 0 : init_oneshot = pulse_init;
117 : #endif
118 0 : } else if (!strcmp(backend_name, "pulse-rust")) {
119 : #if defined(USE_PULSE_RUST)
120 0 : init_oneshot = pulse_rust_init;
121 : #endif
122 0 : } else if (!strcmp(backend_name, "jack")) {
123 : #if defined(USE_JACK)
124 : init_oneshot = jack_init;
125 : #endif
126 0 : } else if (!strcmp(backend_name, "alsa")) {
127 : #if defined(USE_ALSA)
128 : init_oneshot = alsa_init;
129 : #endif
130 0 : } else if (!strcmp(backend_name, "audiounit")) {
131 : #if defined(USE_AUDIOUNIT)
132 : init_oneshot = audiounit_init;
133 : #endif
134 0 : } else if (!strcmp(backend_name, "wasapi")) {
135 : #if defined(USE_WASAPI)
136 : init_oneshot = wasapi_init;
137 : #endif
138 0 : } else if (!strcmp(backend_name, "winmm")) {
139 : #if defined(USE_WINMM)
140 : init_oneshot = winmm_init;
141 : #endif
142 0 : } else if (!strcmp(backend_name, "sndio")) {
143 : #if defined(USE_SNDIO)
144 : init_oneshot = sndio_init;
145 : #endif
146 0 : } else if (!strcmp(backend_name, "opensl")) {
147 : #if defined(USE_OPENSL)
148 : init_oneshot = opensl_init;
149 : #endif
150 0 : } else if (!strcmp(backend_name, "audiotrack")) {
151 : #if defined(USE_AUDIOTRACK)
152 : init_oneshot = audiotrack_init;
153 : #endif
154 0 : } else if (!strcmp(backend_name, "kai")) {
155 : #if defined(USE_KAI)
156 : init_oneshot = kai_init;
157 : #endif
158 : } else {
159 : /* Already set */
160 : }
161 : }
162 :
163 0 : int (* default_init[])(cubeb **, char const *) = {
164 : /*
165 : * init_oneshot must be at the top to allow user
166 : * to override all other choices
167 : */
168 : init_oneshot,
169 : #if defined(NIGHTLY_BUILD) && defined(USE_PULSE_RUST)
170 : pulse_rust_init,
171 : #endif
172 : #if defined(USE_PULSE)
173 : pulse_init,
174 : #endif
175 : #if defined(USE_JACK)
176 : jack_init,
177 : #endif
178 : #if defined(USE_ALSA)
179 : alsa_init,
180 : #endif
181 : #if defined(USE_AUDIOUNIT)
182 : audiounit_init,
183 : #endif
184 : #if defined(USE_WASAPI)
185 : wasapi_init,
186 : #endif
187 : #if defined(USE_WINMM)
188 : winmm_init,
189 : #endif
190 : #if defined(USE_SNDIO)
191 : sndio_init,
192 : #endif
193 : #if defined(USE_OPENSL)
194 : opensl_init,
195 : #endif
196 : #if defined(USE_AUDIOTRACK)
197 : audiotrack_init,
198 : #endif
199 : #if defined(USE_KAI)
200 : kai_init,
201 : #endif
202 : };
203 : int i;
204 :
205 0 : if (!context) {
206 0 : return CUBEB_ERROR_INVALID_PARAMETER;
207 : }
208 :
209 : #define OK(fn) assert((* context)->ops->fn)
210 0 : for (i = 0; i < NELEMS(default_init); ++i) {
211 0 : if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
212 : /* Assert that the minimal API is implemented. */
213 0 : OK(get_backend_id);
214 0 : OK(destroy);
215 0 : OK(stream_init);
216 0 : OK(stream_destroy);
217 0 : OK(stream_start);
218 0 : OK(stream_stop);
219 0 : OK(stream_get_position);
220 0 : return CUBEB_OK;
221 : }
222 : }
223 0 : return CUBEB_ERROR;
224 : }
225 :
226 : char const *
227 0 : cubeb_get_backend_id(cubeb * context)
228 : {
229 0 : if (!context) {
230 0 : return NULL;
231 : }
232 :
233 0 : return context->ops->get_backend_id(context);
234 : }
235 :
236 : int
237 0 : cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
238 : {
239 0 : if (!context || !max_channels) {
240 0 : return CUBEB_ERROR_INVALID_PARAMETER;
241 : }
242 :
243 0 : if (!context->ops->get_max_channel_count) {
244 0 : return CUBEB_ERROR_NOT_SUPPORTED;
245 : }
246 :
247 0 : return context->ops->get_max_channel_count(context, max_channels);
248 : }
249 :
250 : int
251 0 : cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
252 : {
253 0 : if (!context || !latency_ms) {
254 0 : return CUBEB_ERROR_INVALID_PARAMETER;
255 : }
256 :
257 0 : if (!context->ops->get_min_latency) {
258 0 : return CUBEB_ERROR_NOT_SUPPORTED;
259 : }
260 :
261 0 : return context->ops->get_min_latency(context, params, latency_ms);
262 : }
263 :
264 : int
265 0 : cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
266 : {
267 0 : if (!context || !rate) {
268 0 : return CUBEB_ERROR_INVALID_PARAMETER;
269 : }
270 :
271 0 : if (!context->ops->get_preferred_sample_rate) {
272 0 : return CUBEB_ERROR_NOT_SUPPORTED;
273 : }
274 :
275 0 : return context->ops->get_preferred_sample_rate(context, rate);
276 : }
277 :
278 : int
279 0 : cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout)
280 : {
281 0 : if (!context || !layout) {
282 0 : return CUBEB_ERROR_INVALID_PARAMETER;
283 : }
284 :
285 0 : if (!context->ops->get_preferred_channel_layout) {
286 0 : return CUBEB_ERROR_NOT_SUPPORTED;
287 : }
288 :
289 0 : return context->ops->get_preferred_channel_layout(context, layout);
290 : }
291 :
292 : void
293 0 : cubeb_destroy(cubeb * context)
294 : {
295 0 : if (!context) {
296 0 : return;
297 : }
298 :
299 0 : context->ops->destroy(context);
300 : }
301 :
302 : int
303 0 : cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
304 : cubeb_devid input_device,
305 : cubeb_stream_params * input_stream_params,
306 : cubeb_devid output_device,
307 : cubeb_stream_params * output_stream_params,
308 : unsigned int latency,
309 : cubeb_data_callback data_callback,
310 : cubeb_state_callback state_callback,
311 : void * user_ptr)
312 : {
313 : int r;
314 :
315 0 : if (!context || !stream) {
316 0 : return CUBEB_ERROR_INVALID_PARAMETER;
317 : }
318 :
319 0 : if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK ||
320 0 : (r = validate_latency(latency)) != CUBEB_OK) {
321 0 : return r;
322 : }
323 :
324 0 : r = context->ops->stream_init(context, stream, stream_name,
325 : input_device,
326 : input_stream_params,
327 : output_device,
328 : output_stream_params,
329 : latency,
330 : data_callback,
331 : state_callback,
332 : user_ptr);
333 :
334 0 : if (r == CUBEB_ERROR_INVALID_FORMAT) {
335 0 : LOG("Invalid format, %p %p %d %d",
336 : output_stream_params, input_stream_params,
337 : output_stream_params && output_stream_params->format,
338 : input_stream_params && input_stream_params->format);
339 : }
340 :
341 0 : return r;
342 : }
343 :
344 : void
345 0 : cubeb_stream_destroy(cubeb_stream * stream)
346 : {
347 0 : if (!stream) {
348 0 : return;
349 : }
350 :
351 0 : stream->context->ops->stream_destroy(stream);
352 : }
353 :
354 : int
355 0 : cubeb_stream_start(cubeb_stream * stream)
356 : {
357 0 : if (!stream) {
358 0 : return CUBEB_ERROR_INVALID_PARAMETER;
359 : }
360 :
361 0 : return stream->context->ops->stream_start(stream);
362 : }
363 :
364 : int
365 0 : cubeb_stream_stop(cubeb_stream * stream)
366 : {
367 0 : if (!stream) {
368 0 : return CUBEB_ERROR_INVALID_PARAMETER;
369 : }
370 :
371 0 : return stream->context->ops->stream_stop(stream);
372 : }
373 :
374 : int
375 0 : cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position)
376 : {
377 0 : if (!stream || !position) {
378 0 : return CUBEB_ERROR_INVALID_PARAMETER;
379 : }
380 :
381 0 : return stream->context->ops->stream_get_position(stream, position);
382 : }
383 :
384 : int
385 0 : cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
386 : {
387 0 : if (!stream || !latency) {
388 0 : return CUBEB_ERROR_INVALID_PARAMETER;
389 : }
390 :
391 0 : if (!stream->context->ops->stream_get_latency) {
392 0 : return CUBEB_ERROR_NOT_SUPPORTED;
393 : }
394 :
395 0 : return stream->context->ops->stream_get_latency(stream, latency);
396 : }
397 :
398 : int
399 0 : cubeb_stream_set_volume(cubeb_stream * stream, float volume)
400 : {
401 0 : if (!stream || volume > 1.0 || volume < 0.0) {
402 0 : return CUBEB_ERROR_INVALID_PARAMETER;
403 : }
404 :
405 0 : if (!stream->context->ops->stream_set_volume) {
406 0 : return CUBEB_ERROR_NOT_SUPPORTED;
407 : }
408 :
409 0 : return stream->context->ops->stream_set_volume(stream, volume);
410 : }
411 :
412 0 : int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
413 : {
414 0 : if (!stream || panning < -1.0 || panning > 1.0) {
415 0 : return CUBEB_ERROR_INVALID_PARAMETER;
416 : }
417 :
418 0 : if (!stream->context->ops->stream_set_panning) {
419 0 : return CUBEB_ERROR_NOT_SUPPORTED;
420 : }
421 :
422 0 : return stream->context->ops->stream_set_panning(stream, panning);
423 : }
424 :
425 0 : int cubeb_stream_get_current_device(cubeb_stream * stream,
426 : cubeb_device ** const device)
427 : {
428 0 : if (!stream || !device) {
429 0 : return CUBEB_ERROR_INVALID_PARAMETER;
430 : }
431 :
432 0 : if (!stream->context->ops->stream_get_current_device) {
433 0 : return CUBEB_ERROR_NOT_SUPPORTED;
434 : }
435 :
436 0 : return stream->context->ops->stream_get_current_device(stream, device);
437 : }
438 :
439 0 : int cubeb_stream_device_destroy(cubeb_stream * stream,
440 : cubeb_device * device)
441 : {
442 0 : if (!stream || !device) {
443 0 : return CUBEB_ERROR_INVALID_PARAMETER;
444 : }
445 :
446 0 : if (!stream->context->ops->stream_device_destroy) {
447 0 : return CUBEB_ERROR_NOT_SUPPORTED;
448 : }
449 :
450 0 : return stream->context->ops->stream_device_destroy(stream, device);
451 : }
452 :
453 0 : int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
454 : cubeb_device_changed_callback device_changed_callback)
455 : {
456 0 : if (!stream) {
457 0 : return CUBEB_ERROR_INVALID_PARAMETER;
458 : }
459 :
460 0 : if (!stream->context->ops->stream_register_device_changed_callback) {
461 0 : return CUBEB_ERROR_NOT_SUPPORTED;
462 : }
463 :
464 0 : return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
465 : }
466 :
467 : static
468 0 : void log_device(cubeb_device_info * device_info)
469 : {
470 0 : char devfmts[128] = "";
471 : const char * devtype, * devstate, * devdeffmt;
472 :
473 0 : switch (device_info->type) {
474 : case CUBEB_DEVICE_TYPE_INPUT:
475 0 : devtype = "input";
476 0 : break;
477 : case CUBEB_DEVICE_TYPE_OUTPUT:
478 0 : devtype = "output";
479 0 : break;
480 : case CUBEB_DEVICE_TYPE_UNKNOWN:
481 : default:
482 0 : devtype = "unknown?";
483 0 : break;
484 : };
485 :
486 0 : switch (device_info->state) {
487 : case CUBEB_DEVICE_STATE_DISABLED:
488 0 : devstate = "disabled";
489 0 : break;
490 : case CUBEB_DEVICE_STATE_UNPLUGGED:
491 0 : devstate = "unplugged";
492 0 : break;
493 : case CUBEB_DEVICE_STATE_ENABLED:
494 0 : devstate = "enabled";
495 0 : break;
496 : default:
497 0 : devstate = "unknown?";
498 0 : break;
499 : };
500 :
501 0 : switch (device_info->default_format) {
502 : case CUBEB_DEVICE_FMT_S16LE:
503 0 : devdeffmt = "S16LE";
504 0 : break;
505 : case CUBEB_DEVICE_FMT_S16BE:
506 0 : devdeffmt = "S16BE";
507 0 : break;
508 : case CUBEB_DEVICE_FMT_F32LE:
509 0 : devdeffmt = "F32LE";
510 0 : break;
511 : case CUBEB_DEVICE_FMT_F32BE:
512 0 : devdeffmt = "F32BE";
513 0 : break;
514 : default:
515 0 : devdeffmt = "unknown?";
516 0 : break;
517 : };
518 :
519 0 : if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
520 0 : strcat(devfmts, " S16LE");
521 : }
522 0 : if (device_info->format & CUBEB_DEVICE_FMT_S16BE) {
523 0 : strcat(devfmts, " S16BE");
524 : }
525 0 : if (device_info->format & CUBEB_DEVICE_FMT_F32LE) {
526 0 : strcat(devfmts, " F32LE");
527 : }
528 0 : if (device_info->format & CUBEB_DEVICE_FMT_F32BE) {
529 0 : strcat(devfmts, " F32BE");
530 : }
531 :
532 0 : LOG("DeviceID: \"%s\"%s\n"
533 : "\tName:\t\"%s\"\n"
534 : "\tGroup:\t\"%s\"\n"
535 : "\tVendor:\t\"%s\"\n"
536 : "\tType:\t%s\n"
537 : "\tState:\t%s\n"
538 : "\tMaximum channels:\t%u\n"
539 : "\tFormat:\t%s (0x%x) (default: %s)\n"
540 : "\tRate:\t[%u, %u] (default: %u)\n"
541 : "\tLatency: lo %u frames, hi %u frames",
542 : device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
543 : device_info->friendly_name,
544 : device_info->group_id,
545 : device_info->vendor_name,
546 : devtype,
547 : devstate,
548 : device_info->max_channels,
549 : (devfmts[0] == '\0') ? devfmts : devfmts + 1, (unsigned int)device_info->format, devdeffmt,
550 : device_info->min_rate, device_info->max_rate, device_info->default_rate,
551 : device_info->latency_lo, device_info->latency_hi);
552 0 : }
553 :
554 0 : int cubeb_enumerate_devices(cubeb * context,
555 : cubeb_device_type devtype,
556 : cubeb_device_collection * collection)
557 : {
558 : int rv;
559 0 : if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
560 0 : return CUBEB_ERROR_INVALID_PARAMETER;
561 0 : if (collection == NULL)
562 0 : return CUBEB_ERROR_INVALID_PARAMETER;
563 0 : if (!context->ops->enumerate_devices)
564 0 : return CUBEB_ERROR_NOT_SUPPORTED;
565 :
566 0 : rv = context->ops->enumerate_devices(context, devtype, collection);
567 :
568 0 : if (g_cubeb_log_callback) {
569 0 : for (size_t i = 0; i < collection->count; i++) {
570 0 : log_device(&collection->device[i]);
571 : }
572 : }
573 :
574 0 : return rv;
575 : }
576 :
577 0 : int cubeb_device_collection_destroy(cubeb * context,
578 : cubeb_device_collection * collection)
579 : {
580 : int r;
581 :
582 0 : if (context == NULL || collection == NULL)
583 0 : return CUBEB_ERROR_INVALID_PARAMETER;
584 :
585 0 : if (!context->ops->device_collection_destroy)
586 0 : return CUBEB_ERROR_NOT_SUPPORTED;
587 :
588 0 : if (!collection->device)
589 0 : return CUBEB_OK;
590 :
591 0 : r = context->ops->device_collection_destroy(context, collection);
592 0 : if (r == CUBEB_OK) {
593 0 : collection->device = NULL;
594 0 : collection->count = 0;
595 : }
596 :
597 0 : return r;
598 : }
599 :
600 0 : int cubeb_register_device_collection_changed(cubeb * context,
601 : cubeb_device_type devtype,
602 : cubeb_device_collection_changed_callback callback,
603 : void * user_ptr)
604 : {
605 0 : if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
606 0 : return CUBEB_ERROR_INVALID_PARAMETER;
607 :
608 0 : if (!context->ops->register_device_collection_changed) {
609 0 : return CUBEB_ERROR_NOT_SUPPORTED;
610 : }
611 :
612 0 : return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr);
613 : }
614 :
615 3 : int cubeb_set_log_callback(cubeb_log_level log_level,
616 : cubeb_log_callback log_callback)
617 : {
618 3 : if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
619 0 : return CUBEB_ERROR_INVALID_FORMAT;
620 : }
621 :
622 3 : if (!log_callback && log_level != CUBEB_LOG_DISABLED) {
623 0 : return CUBEB_ERROR_INVALID_PARAMETER;
624 : }
625 :
626 3 : if (g_cubeb_log_callback && log_callback) {
627 0 : return CUBEB_ERROR_NOT_SUPPORTED;
628 : }
629 :
630 3 : g_cubeb_log_callback = log_callback;
631 3 : g_cubeb_log_level = log_level;
632 :
633 : // Logging a message here allows to initialize the asynchronous logger from a
634 : // thread that is not the audio rendering thread, and especially to not
635 : // initialize it the first time we find a verbose log, which is often in the
636 : // audio rendering callback, that runs from the audio rendering thread, and
637 : // that is high priority, and that we don't want to block.
638 3 : if (log_level >= CUBEB_LOG_VERBOSE) {
639 0 : ALOGV("Starting cubeb log");
640 : }
641 :
642 3 : return CUBEB_OK;
643 : }
644 :
|