Line data Source code
1 : /*
2 : * Copyright © 2011 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 <dlfcn.h>
10 : #include <stdlib.h>
11 : #include <pulse/pulseaudio.h>
12 : #include <string.h>
13 : #include "cubeb/cubeb.h"
14 : #include "cubeb-internal.h"
15 : #include "cubeb_mixer.h"
16 : #include "cubeb_utils.h"
17 : #include <stdio.h>
18 :
19 : #ifdef DISABLE_LIBPULSE_DLOPEN
20 : #define WRAP(x) x
21 : #else
22 : #define WRAP(x) cubeb_##x
23 : #define LIBPULSE_API_VISIT(X) \
24 : X(pa_channel_map_can_balance) \
25 : X(pa_channel_map_init) \
26 : X(pa_context_connect) \
27 : X(pa_context_disconnect) \
28 : X(pa_context_drain) \
29 : X(pa_context_get_server_info) \
30 : X(pa_context_get_sink_info_by_name) \
31 : X(pa_context_get_sink_info_list) \
32 : X(pa_context_get_sink_input_info) \
33 : X(pa_context_get_source_info_list) \
34 : X(pa_context_get_state) \
35 : X(pa_context_new) \
36 : X(pa_context_rttime_new) \
37 : X(pa_context_set_sink_input_volume) \
38 : X(pa_context_set_state_callback) \
39 : X(pa_context_unref) \
40 : X(pa_cvolume_set) \
41 : X(pa_cvolume_set_balance) \
42 : X(pa_frame_size) \
43 : X(pa_operation_get_state) \
44 : X(pa_operation_unref) \
45 : X(pa_proplist_gets) \
46 : X(pa_rtclock_now) \
47 : X(pa_stream_begin_write) \
48 : X(pa_stream_cancel_write) \
49 : X(pa_stream_connect_playback) \
50 : X(pa_stream_cork) \
51 : X(pa_stream_disconnect) \
52 : X(pa_stream_get_channel_map) \
53 : X(pa_stream_get_index) \
54 : X(pa_stream_get_latency) \
55 : X(pa_stream_get_sample_spec) \
56 : X(pa_stream_get_state) \
57 : X(pa_stream_get_time) \
58 : X(pa_stream_new) \
59 : X(pa_stream_set_state_callback) \
60 : X(pa_stream_set_write_callback) \
61 : X(pa_stream_unref) \
62 : X(pa_stream_update_timing_info) \
63 : X(pa_stream_write) \
64 : X(pa_sw_volume_from_linear) \
65 : X(pa_threaded_mainloop_free) \
66 : X(pa_threaded_mainloop_get_api) \
67 : X(pa_threaded_mainloop_in_thread) \
68 : X(pa_threaded_mainloop_lock) \
69 : X(pa_threaded_mainloop_new) \
70 : X(pa_threaded_mainloop_signal) \
71 : X(pa_threaded_mainloop_start) \
72 : X(pa_threaded_mainloop_stop) \
73 : X(pa_threaded_mainloop_unlock) \
74 : X(pa_threaded_mainloop_wait) \
75 : X(pa_usec_to_bytes) \
76 : X(pa_stream_set_read_callback) \
77 : X(pa_stream_connect_record) \
78 : X(pa_stream_readable_size) \
79 : X(pa_stream_writable_size) \
80 : X(pa_stream_peek) \
81 : X(pa_stream_drop) \
82 : X(pa_stream_get_buffer_attr) \
83 : X(pa_stream_get_device_name) \
84 : X(pa_context_set_subscribe_callback) \
85 : X(pa_context_subscribe) \
86 : X(pa_mainloop_api_once) \
87 :
88 : #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
89 : LIBPULSE_API_VISIT(MAKE_TYPEDEF);
90 : #undef MAKE_TYPEDEF
91 : #endif
92 :
93 : static struct cubeb_ops const pulse_ops;
94 :
95 : struct cubeb {
96 : struct cubeb_ops const * ops;
97 : void * libpulse;
98 : pa_threaded_mainloop * mainloop;
99 : pa_context * context;
100 : pa_sink_info * default_sink_info;
101 : char * context_name;
102 : int error;
103 : cubeb_device_collection_changed_callback collection_changed_callback;
104 : void * collection_changed_user_ptr;
105 : };
106 :
107 : struct cubeb_stream {
108 : cubeb * context;
109 : pa_stream * output_stream;
110 : pa_stream * input_stream;
111 : cubeb_data_callback data_callback;
112 : cubeb_state_callback state_callback;
113 : void * user_ptr;
114 : pa_time_event * drain_timer;
115 : pa_sample_spec output_sample_spec;
116 : pa_sample_spec input_sample_spec;
117 : int shutdown;
118 : float volume;
119 : cubeb_state state;
120 : };
121 :
122 : static const float PULSE_NO_GAIN = -1.0;
123 :
124 : enum cork_state {
125 : UNCORK = 0,
126 : CORK = 1 << 0,
127 : NOTIFY = 1 << 1
128 : };
129 :
130 : static void
131 0 : sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
132 : {
133 : (void)context;
134 0 : cubeb * ctx = u;
135 0 : if (!eol) {
136 0 : free(ctx->default_sink_info);
137 0 : ctx->default_sink_info = malloc(sizeof(pa_sink_info));
138 0 : memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
139 : }
140 0 : WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
141 0 : }
142 :
143 : static void
144 0 : server_info_callback(pa_context * context, const pa_server_info * info, void * u)
145 : {
146 : pa_operation * o;
147 0 : o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
148 0 : if (o) {
149 0 : WRAP(pa_operation_unref)(o);
150 : }
151 0 : }
152 :
153 : static void
154 0 : context_state_callback(pa_context * c, void * u)
155 : {
156 0 : cubeb * ctx = u;
157 0 : if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
158 0 : ctx->error = 1;
159 : }
160 0 : WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
161 0 : }
162 :
163 : static void
164 0 : context_notify_callback(pa_context * c, void * u)
165 : {
166 : (void)c;
167 0 : cubeb * ctx = u;
168 0 : WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
169 0 : }
170 :
171 : static void
172 0 : stream_success_callback(pa_stream * s, int success, void * u)
173 : {
174 : (void)s;
175 : (void)success;
176 0 : cubeb_stream * stm = u;
177 0 : WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
178 0 : }
179 :
180 : static void
181 0 : stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
182 : {
183 0 : stm->state = s;
184 0 : stm->state_callback(stm, stm->user_ptr, s);
185 0 : }
186 :
187 : static void
188 0 : stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
189 : {
190 : (void)a;
191 : (void)tv;
192 0 : cubeb_stream * stm = u;
193 0 : assert(stm->drain_timer == e);
194 0 : stream_state_change_callback(stm, CUBEB_STATE_DRAINED);
195 : /* there's no pa_rttime_free, so use this instead. */
196 0 : a->time_free(stm->drain_timer);
197 0 : stm->drain_timer = NULL;
198 0 : WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
199 0 : }
200 :
201 : static void
202 0 : stream_state_callback(pa_stream * s, void * u)
203 : {
204 0 : cubeb_stream * stm = u;
205 0 : if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
206 0 : stream_state_change_callback(stm, CUBEB_STATE_ERROR);
207 : }
208 0 : WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
209 0 : }
210 :
211 : static void
212 0 : trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cubeb_stream * stm)
213 : {
214 : void * buffer;
215 : size_t size;
216 : int r;
217 : long got;
218 : size_t towrite, read_offset;
219 : size_t frame_size;
220 :
221 0 : frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
222 0 : assert(nbytes % frame_size == 0);
223 :
224 0 : towrite = nbytes;
225 0 : read_offset = 0;
226 0 : while (towrite) {
227 0 : size = towrite;
228 0 : r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
229 : // Note: this has failed running under rr on occassion - needs investigation.
230 0 : assert(r == 0);
231 0 : assert(size > 0);
232 0 : assert(size % frame_size == 0);
233 :
234 0 : LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd", size, read_offset);
235 0 : got = stm->data_callback(stm, stm->user_ptr, (uint8_t const *)input_data + read_offset, buffer, size / frame_size);
236 0 : if (got < 0) {
237 0 : WRAP(pa_stream_cancel_write)(s);
238 0 : stm->shutdown = 1;
239 0 : return;
240 : }
241 : // If more iterations move offset of read buffer
242 0 : if (input_data) {
243 0 : size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
244 0 : read_offset += (size / frame_size) * in_frame_size;
245 : }
246 :
247 0 : if (stm->volume != PULSE_NO_GAIN) {
248 0 : uint32_t samples = size * stm->output_sample_spec.channels / frame_size ;
249 :
250 0 : if (stm->output_sample_spec.format == PA_SAMPLE_S16BE ||
251 0 : stm->output_sample_spec.format == PA_SAMPLE_S16LE) {
252 0 : short * b = buffer;
253 0 : for (uint32_t i = 0; i < samples; i++) {
254 0 : b[i] *= stm->volume;
255 : }
256 : } else {
257 0 : float * b = buffer;
258 0 : for (uint32_t i = 0; i < samples; i++) {
259 0 : b[i] *= stm->volume;
260 : }
261 : }
262 : }
263 :
264 0 : r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
265 0 : assert(r == 0);
266 :
267 0 : if ((size_t) got < size / frame_size) {
268 0 : pa_usec_t latency = 0;
269 0 : r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
270 0 : if (r == -PA_ERR_NODATA) {
271 : /* this needs a better guess. */
272 0 : latency = 100 * PA_USEC_PER_MSEC;
273 : }
274 0 : assert(r == 0 || r == -PA_ERR_NODATA);
275 : /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
276 : /* arbitrary safety margin: double the current latency. */
277 0 : assert(!stm->drain_timer);
278 0 : stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
279 0 : stm->shutdown = 1;
280 0 : return;
281 : }
282 :
283 0 : towrite -= size;
284 : }
285 :
286 0 : assert(towrite == 0);
287 : }
288 :
289 : static int
290 0 : read_from_input(pa_stream * s, void const ** buffer, size_t * size)
291 : {
292 0 : size_t readable_size = WRAP(pa_stream_readable_size)(s);
293 0 : if (readable_size > 0) {
294 0 : if (WRAP(pa_stream_peek)(s, buffer, size) < 0) {
295 0 : return -1;
296 : }
297 : }
298 0 : return readable_size;
299 : }
300 :
301 : static void
302 0 : stream_write_callback(pa_stream * s, size_t nbytes, void * u)
303 : {
304 0 : LOGV("Output callback to be written buffer size %zd", nbytes);
305 0 : cubeb_stream * stm = u;
306 0 : if (stm->shutdown ||
307 0 : stm->state != CUBEB_STATE_STARTED) {
308 0 : return;
309 : }
310 :
311 0 : if (!stm->input_stream){
312 : // Output/playback only operation.
313 : // Write directly to output
314 0 : assert(!stm->input_stream && stm->output_stream);
315 0 : trigger_user_callback(s, NULL, nbytes, stm);
316 : }
317 : }
318 :
319 : static void
320 0 : stream_read_callback(pa_stream * s, size_t nbytes, void * u)
321 : {
322 0 : LOGV("Input callback buffer size %zd", nbytes);
323 0 : cubeb_stream * stm = u;
324 0 : if (stm->shutdown) {
325 0 : return;
326 : }
327 :
328 0 : void const * read_data = NULL;
329 : size_t read_size;
330 0 : while (read_from_input(s, &read_data, &read_size) > 0) {
331 : /* read_data can be NULL in case of a hole. */
332 0 : if (read_data) {
333 0 : size_t in_frame_size = WRAP(pa_frame_size)(&stm->input_sample_spec);
334 0 : size_t read_frames = read_size / in_frame_size;
335 :
336 0 : if (stm->output_stream) {
337 : // input/capture + output/playback operation
338 0 : size_t out_frame_size = WRAP(pa_frame_size)(&stm->output_sample_spec);
339 0 : size_t write_size = read_frames * out_frame_size;
340 : // Offer full duplex data for writing
341 0 : trigger_user_callback(stm->output_stream, read_data, write_size, stm);
342 : } else {
343 : // input/capture only operation. Call callback directly
344 0 : long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL, read_frames);
345 0 : if (got < 0 || (size_t) got != read_frames) {
346 0 : WRAP(pa_stream_cancel_write)(s);
347 0 : stm->shutdown = 1;
348 0 : break;
349 : }
350 : }
351 : }
352 0 : if (read_size > 0) {
353 0 : WRAP(pa_stream_drop)(s);
354 : }
355 :
356 0 : if (stm->shutdown) {
357 0 : return;
358 : }
359 : }
360 : }
361 :
362 : static int
363 0 : wait_until_context_ready(cubeb * ctx)
364 : {
365 0 : for (;;) {
366 0 : pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
367 0 : if (!PA_CONTEXT_IS_GOOD(state))
368 0 : return -1;
369 0 : if (state == PA_CONTEXT_READY)
370 0 : break;
371 0 : WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
372 : }
373 0 : return 0;
374 : }
375 :
376 : static int
377 0 : wait_until_io_stream_ready(pa_stream * stream, pa_threaded_mainloop * mainloop)
378 : {
379 0 : if (!stream || !mainloop){
380 0 : return -1;
381 : }
382 0 : for (;;) {
383 0 : pa_stream_state_t state = WRAP(pa_stream_get_state)(stream);
384 0 : if (!PA_STREAM_IS_GOOD(state))
385 0 : return -1;
386 0 : if (state == PA_STREAM_READY)
387 0 : break;
388 0 : WRAP(pa_threaded_mainloop_wait)(mainloop);
389 : }
390 0 : return 0;
391 : }
392 :
393 : static int
394 0 : wait_until_stream_ready(cubeb_stream * stm)
395 : {
396 0 : if (stm->output_stream &&
397 0 : wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) == -1) {
398 0 : return -1;
399 : }
400 0 : if(stm->input_stream &&
401 0 : wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) == -1) {
402 0 : return -1;
403 : }
404 0 : return 0;
405 : }
406 :
407 : static int
408 0 : operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
409 : {
410 0 : while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
411 0 : WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
412 0 : if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context))) {
413 0 : return -1;
414 : }
415 0 : if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream))) {
416 0 : return -1;
417 : }
418 : }
419 0 : return 0;
420 : }
421 :
422 : static void
423 0 : cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
424 : {
425 : pa_operation * o;
426 0 : if (!io_stream) {
427 0 : return;
428 : }
429 0 : o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback, stm);
430 0 : if (o) {
431 0 : operation_wait(stm->context, io_stream, o);
432 0 : WRAP(pa_operation_unref)(o);
433 : }
434 : }
435 :
436 : static void
437 0 : stream_cork(cubeb_stream * stm, enum cork_state state)
438 : {
439 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
440 0 : cork_io_stream(stm, stm->output_stream, state);
441 0 : cork_io_stream(stm, stm->input_stream, state);
442 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
443 :
444 0 : if (state & NOTIFY) {
445 0 : stream_state_change_callback(stm, state & CORK ? CUBEB_STATE_STOPPED
446 : : CUBEB_STATE_STARTED);
447 : }
448 0 : }
449 :
450 : static int
451 0 : stream_update_timing_info(cubeb_stream * stm)
452 : {
453 0 : int r = -1;
454 0 : pa_operation * o = NULL;
455 0 : if (stm->output_stream) {
456 0 : o = WRAP(pa_stream_update_timing_info)(stm->output_stream, stream_success_callback, stm);
457 0 : if (o) {
458 0 : r = operation_wait(stm->context, stm->output_stream, o);
459 0 : WRAP(pa_operation_unref)(o);
460 : }
461 0 : if (r != 0) {
462 0 : return r;
463 : }
464 : }
465 :
466 0 : if (stm->input_stream) {
467 0 : o = WRAP(pa_stream_update_timing_info)(stm->input_stream, stream_success_callback, stm);
468 0 : if (o) {
469 0 : r = operation_wait(stm->context, stm->input_stream, o);
470 0 : WRAP(pa_operation_unref)(o);
471 : }
472 : }
473 :
474 0 : return r;
475 : }
476 :
477 : static pa_channel_position_t
478 0 : cubeb_channel_to_pa_channel(cubeb_channel channel)
479 : {
480 0 : assert(channel != CHANNEL_INVALID);
481 :
482 : // This variable may be used for multiple times, so we should avoid to
483 : // allocate it in stack, or it will be created and removed repeatedly.
484 : // Use static to allocate this local variable in data space instead of stack.
485 : static pa_channel_position_t map[CHANNEL_MAX] = {
486 : // PA_CHANNEL_POSITION_INVALID, // CHANNEL_INVALID
487 : PA_CHANNEL_POSITION_MONO, // CHANNEL_MONO
488 : PA_CHANNEL_POSITION_FRONT_LEFT, // CHANNEL_LEFT
489 : PA_CHANNEL_POSITION_FRONT_RIGHT, // CHANNEL_RIGHT
490 : PA_CHANNEL_POSITION_FRONT_CENTER, // CHANNEL_CENTER
491 : PA_CHANNEL_POSITION_SIDE_LEFT, // CHANNEL_LS
492 : PA_CHANNEL_POSITION_SIDE_RIGHT, // CHANNEL_RS
493 : PA_CHANNEL_POSITION_REAR_LEFT, // CHANNEL_RLS
494 : PA_CHANNEL_POSITION_REAR_CENTER, // CHANNEL_RCENTER
495 : PA_CHANNEL_POSITION_REAR_RIGHT, // CHANNEL_RRS
496 : PA_CHANNEL_POSITION_LFE // CHANNEL_LFE
497 : };
498 :
499 0 : return map[channel];
500 : }
501 :
502 : static cubeb_channel
503 0 : pa_channel_to_cubeb_channel(pa_channel_position_t channel)
504 : {
505 0 : assert(channel != PA_CHANNEL_POSITION_INVALID);
506 0 : switch(channel) {
507 0 : case PA_CHANNEL_POSITION_MONO: return CHANNEL_MONO;
508 0 : case PA_CHANNEL_POSITION_FRONT_LEFT: return CHANNEL_LEFT;
509 0 : case PA_CHANNEL_POSITION_FRONT_RIGHT: return CHANNEL_RIGHT;
510 0 : case PA_CHANNEL_POSITION_FRONT_CENTER: return CHANNEL_CENTER;
511 0 : case PA_CHANNEL_POSITION_SIDE_LEFT: return CHANNEL_LS;
512 0 : case PA_CHANNEL_POSITION_SIDE_RIGHT: return CHANNEL_RS;
513 0 : case PA_CHANNEL_POSITION_REAR_LEFT: return CHANNEL_RLS;
514 0 : case PA_CHANNEL_POSITION_REAR_CENTER: return CHANNEL_RCENTER;
515 0 : case PA_CHANNEL_POSITION_REAR_RIGHT: return CHANNEL_RRS;
516 0 : case PA_CHANNEL_POSITION_LFE: return CHANNEL_LFE;
517 0 : default: return CHANNEL_INVALID;
518 : }
519 : }
520 :
521 : static void
522 0 : layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
523 : {
524 0 : assert(cm && layout != CUBEB_LAYOUT_UNDEFINED);
525 :
526 0 : WRAP(pa_channel_map_init)(cm);
527 0 : cm->channels = CUBEB_CHANNEL_LAYOUT_MAPS[layout].channels;
528 0 : for (uint8_t i = 0 ; i < cm->channels ; ++i) {
529 0 : cm->map[i] = cubeb_channel_to_pa_channel(CHANNEL_INDEX_TO_ORDER[layout][i]);
530 : }
531 0 : }
532 :
533 : static cubeb_channel_layout
534 0 : channel_map_to_layout(pa_channel_map * cm)
535 : {
536 : cubeb_channel_map cubeb_map;
537 0 : cubeb_map.channels = cm->channels;
538 0 : for (uint32_t i = 0 ; i < cm->channels ; ++i) {
539 0 : cubeb_map.map[i] = pa_channel_to_cubeb_channel(cm->map[i]);
540 : }
541 0 : return cubeb_channel_map_to_layout(&cubeb_map);
542 : }
543 :
544 : static void pulse_context_destroy(cubeb * ctx);
545 : static void pulse_destroy(cubeb * ctx);
546 :
547 : static int
548 0 : pulse_context_init(cubeb * ctx)
549 : {
550 0 : if (ctx->context) {
551 0 : assert(ctx->error == 1);
552 0 : pulse_context_destroy(ctx);
553 : }
554 :
555 0 : ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
556 0 : ctx->context_name);
557 0 : if (!ctx->context) {
558 0 : return -1;
559 : }
560 0 : WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
561 :
562 0 : WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
563 0 : WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
564 :
565 0 : if (wait_until_context_ready(ctx) != 0) {
566 0 : WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
567 0 : pulse_context_destroy(ctx);
568 0 : ctx->context = NULL;
569 0 : return -1;
570 : }
571 :
572 0 : WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
573 :
574 0 : ctx->error = 0;
575 :
576 0 : return 0;
577 : }
578 :
579 : /*static*/ int
580 0 : pulse_init(cubeb ** context, char const * context_name)
581 : {
582 0 : void * libpulse = NULL;
583 : cubeb * ctx;
584 : pa_operation * o;
585 :
586 0 : *context = NULL;
587 :
588 : #ifndef DISABLE_LIBPULSE_DLOPEN
589 0 : libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
590 0 : if (!libpulse) {
591 0 : return CUBEB_ERROR;
592 : }
593 :
594 : #define LOAD(x) { \
595 : cubeb_##x = dlsym(libpulse, #x); \
596 : if (!cubeb_##x) { \
597 : dlclose(libpulse); \
598 : return CUBEB_ERROR; \
599 : } \
600 : }
601 :
602 0 : LIBPULSE_API_VISIT(LOAD);
603 : #undef LOAD
604 : #endif
605 :
606 0 : ctx = calloc(1, sizeof(*ctx));
607 0 : assert(ctx);
608 :
609 0 : ctx->ops = &pulse_ops;
610 0 : ctx->libpulse = libpulse;
611 :
612 0 : ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
613 0 : ctx->default_sink_info = NULL;
614 :
615 0 : WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
616 :
617 0 : ctx->context_name = context_name ? strdup(context_name) : NULL;
618 0 : if (pulse_context_init(ctx) != 0) {
619 0 : pulse_destroy(ctx);
620 0 : return CUBEB_ERROR;
621 : }
622 :
623 : /* server_info_callback performs a second async query, which is
624 : responsible for initializing default_sink_info and signalling the
625 : mainloop to end the wait. */
626 0 : WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
627 0 : o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
628 0 : if (o) {
629 0 : operation_wait(ctx, NULL, o);
630 0 : WRAP(pa_operation_unref)(o);
631 : }
632 0 : WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
633 0 : assert(ctx->default_sink_info);
634 :
635 0 : *context = ctx;
636 :
637 0 : return CUBEB_OK;
638 : }
639 :
640 : static char const *
641 0 : pulse_get_backend_id(cubeb * ctx)
642 : {
643 : (void)ctx;
644 0 : return "pulse";
645 : }
646 :
647 : static int
648 0 : pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
649 : {
650 : (void)ctx;
651 0 : assert(ctx && max_channels);
652 :
653 0 : *max_channels = ctx->default_sink_info->channel_map.channels;
654 :
655 0 : return CUBEB_OK;
656 : }
657 :
658 : static int
659 0 : pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
660 : {
661 0 : assert(ctx && rate);
662 : (void)ctx;
663 :
664 0 : *rate = ctx->default_sink_info->sample_spec.rate;
665 :
666 0 : return CUBEB_OK;
667 : }
668 :
669 : static int
670 0 : pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout)
671 : {
672 0 : assert(ctx && layout);
673 : (void)ctx;
674 :
675 0 : *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map);
676 :
677 0 : return CUBEB_OK;
678 : }
679 :
680 : static int
681 0 : pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
682 : {
683 : (void)ctx;
684 : // According to PulseAudio developers, this is a safe minimum.
685 0 : *latency_frames = 25 * params.rate / 1000;
686 :
687 0 : return CUBEB_OK;
688 : }
689 :
690 : static void
691 0 : pulse_context_destroy(cubeb * ctx)
692 : {
693 : pa_operation * o;
694 :
695 0 : WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
696 0 : o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
697 0 : if (o) {
698 0 : operation_wait(ctx, NULL, o);
699 0 : WRAP(pa_operation_unref)(o);
700 : }
701 0 : WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
702 0 : WRAP(pa_context_disconnect)(ctx->context);
703 0 : WRAP(pa_context_unref)(ctx->context);
704 0 : WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
705 0 : }
706 :
707 : static void
708 0 : pulse_destroy(cubeb * ctx)
709 : {
710 0 : free(ctx->context_name);
711 0 : if (ctx->context) {
712 0 : pulse_context_destroy(ctx);
713 : }
714 :
715 0 : if (ctx->mainloop) {
716 0 : WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
717 0 : WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
718 : }
719 :
720 0 : if (ctx->libpulse) {
721 0 : dlclose(ctx->libpulse);
722 : }
723 0 : free(ctx->default_sink_info);
724 0 : free(ctx);
725 0 : }
726 :
727 : static void pulse_stream_destroy(cubeb_stream * stm);
728 :
729 : static pa_sample_format_t
730 0 : to_pulse_format(cubeb_sample_format format)
731 : {
732 0 : switch (format) {
733 : case CUBEB_SAMPLE_S16LE:
734 0 : return PA_SAMPLE_S16LE;
735 : case CUBEB_SAMPLE_S16BE:
736 0 : return PA_SAMPLE_S16BE;
737 : case CUBEB_SAMPLE_FLOAT32LE:
738 0 : return PA_SAMPLE_FLOAT32LE;
739 : case CUBEB_SAMPLE_FLOAT32BE:
740 0 : return PA_SAMPLE_FLOAT32BE;
741 : default:
742 0 : return PA_SAMPLE_INVALID;
743 : }
744 : }
745 :
746 : static int
747 0 : create_pa_stream(cubeb_stream * stm,
748 : pa_stream ** pa_stm,
749 : cubeb_stream_params * stream_params,
750 : char const * stream_name)
751 : {
752 0 : assert(stm && stream_params);
753 0 : assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm &&
754 : stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
755 : CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels));
756 0 : *pa_stm = NULL;
757 : pa_sample_spec ss;
758 0 : ss.format = to_pulse_format(stream_params->format);
759 0 : if (ss.format == PA_SAMPLE_INVALID)
760 0 : return CUBEB_ERROR_INVALID_FORMAT;
761 0 : ss.rate = stream_params->rate;
762 0 : ss.channels = stream_params->channels;
763 :
764 0 : if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) {
765 0 : *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
766 : } else {
767 : pa_channel_map cm;
768 0 : layout_to_channel_map(stream_params->layout, &cm);
769 0 : *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
770 : }
771 0 : return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK;
772 : }
773 :
774 : static pa_buffer_attr
775 0 : set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spec)
776 : {
777 : pa_buffer_attr battr;
778 0 : battr.maxlength = -1;
779 0 : battr.prebuf = -1;
780 0 : battr.tlength = latency_frames * WRAP(pa_frame_size)(sample_spec);
781 0 : battr.minreq = battr.tlength / 4;
782 0 : battr.fragsize = battr.minreq;
783 :
784 0 : LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",
785 : battr.maxlength, battr.tlength, battr.prebuf, battr.minreq, battr.fragsize);
786 :
787 0 : return battr;
788 : }
789 :
790 : static int
791 0 : pulse_stream_init(cubeb * context,
792 : cubeb_stream ** stream,
793 : char const * stream_name,
794 : cubeb_devid input_device,
795 : cubeb_stream_params * input_stream_params,
796 : cubeb_devid output_device,
797 : cubeb_stream_params * output_stream_params,
798 : unsigned int latency_frames,
799 : cubeb_data_callback data_callback,
800 : cubeb_state_callback state_callback,
801 : void * user_ptr)
802 : {
803 : cubeb_stream * stm;
804 : pa_buffer_attr battr;
805 : int r;
806 :
807 0 : assert(context);
808 :
809 : // If the connection failed for some reason, try to reconnect
810 0 : if (context->error == 1 && pulse_context_init(context) != 0) {
811 0 : return CUBEB_ERROR;
812 : }
813 :
814 0 : *stream = NULL;
815 :
816 0 : stm = calloc(1, sizeof(*stm));
817 0 : assert(stm);
818 :
819 0 : stm->context = context;
820 0 : stm->data_callback = data_callback;
821 0 : stm->state_callback = state_callback;
822 0 : stm->user_ptr = user_ptr;
823 0 : stm->volume = PULSE_NO_GAIN;
824 0 : stm->state = -1;
825 0 : assert(stm->shutdown == 0);
826 :
827 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
828 0 : if (output_stream_params) {
829 0 : r = create_pa_stream(stm, &stm->output_stream, output_stream_params, stream_name);
830 0 : if (r != CUBEB_OK) {
831 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
832 0 : pulse_stream_destroy(stm);
833 0 : return r;
834 : }
835 :
836 0 : stm->output_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->output_stream));
837 :
838 0 : WRAP(pa_stream_set_state_callback)(stm->output_stream, stream_state_callback, stm);
839 0 : WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm);
840 :
841 0 : battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
842 0 : WRAP(pa_stream_connect_playback)(stm->output_stream,
843 : (char const *) output_device,
844 : &battr,
845 : PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
846 : PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
847 : NULL, NULL);
848 : }
849 :
850 : // Set up input stream
851 0 : if (input_stream_params) {
852 0 : r = create_pa_stream(stm, &stm->input_stream, input_stream_params, stream_name);
853 0 : if (r != CUBEB_OK) {
854 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
855 0 : pulse_stream_destroy(stm);
856 0 : return r;
857 : }
858 :
859 0 : stm->input_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->input_stream));
860 :
861 0 : WRAP(pa_stream_set_state_callback)(stm->input_stream, stream_state_callback, stm);
862 0 : WRAP(pa_stream_set_read_callback)(stm->input_stream, stream_read_callback, stm);
863 :
864 0 : battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
865 0 : WRAP(pa_stream_connect_record)(stm->input_stream,
866 : (char const *) input_device,
867 : &battr,
868 : PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
869 : PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
870 : }
871 :
872 0 : r = wait_until_stream_ready(stm);
873 0 : if (r == 0) {
874 : /* force a timing update now, otherwise timing info does not become valid
875 : until some point after initialization has completed. */
876 0 : r = stream_update_timing_info(stm);
877 : }
878 :
879 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
880 :
881 0 : if (r != 0) {
882 0 : pulse_stream_destroy(stm);
883 0 : return CUBEB_ERROR;
884 : }
885 :
886 0 : if (g_cubeb_log_level) {
887 0 : if (output_stream_params){
888 : const pa_buffer_attr * output_att;
889 0 : output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
890 0 : LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",output_att->maxlength, output_att->tlength,
891 : output_att->prebuf, output_att->minreq, output_att->fragsize);
892 : }
893 :
894 0 : if (input_stream_params){
895 : const pa_buffer_attr * input_att;
896 0 : input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
897 0 : LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",input_att->maxlength, input_att->tlength,
898 : input_att->prebuf, input_att->minreq, input_att->fragsize);
899 : }
900 : }
901 :
902 0 : *stream = stm;
903 :
904 0 : return CUBEB_OK;
905 : }
906 :
907 : static void
908 0 : pulse_stream_destroy(cubeb_stream * stm)
909 : {
910 0 : stream_cork(stm, CORK);
911 :
912 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
913 0 : if (stm->output_stream) {
914 :
915 0 : if (stm->drain_timer) {
916 : /* there's no pa_rttime_free, so use this instead. */
917 0 : WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
918 : }
919 :
920 0 : WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
921 0 : WRAP(pa_stream_set_write_callback)(stm->output_stream, NULL, NULL);
922 0 : WRAP(pa_stream_disconnect)(stm->output_stream);
923 0 : WRAP(pa_stream_unref)(stm->output_stream);
924 : }
925 :
926 0 : if (stm->input_stream) {
927 0 : WRAP(pa_stream_set_state_callback)(stm->input_stream, NULL, NULL);
928 0 : WRAP(pa_stream_set_read_callback)(stm->input_stream, NULL, NULL);
929 0 : WRAP(pa_stream_disconnect)(stm->input_stream);
930 0 : WRAP(pa_stream_unref)(stm->input_stream);
931 : }
932 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
933 :
934 0 : free(stm);
935 0 : }
936 :
937 : static void
938 0 : pulse_defer_event_cb(pa_mainloop_api * a, void * userdata)
939 : {
940 : (void)a;
941 0 : cubeb_stream * stm = userdata;
942 0 : if (stm->shutdown) {
943 0 : return;
944 : }
945 0 : size_t writable_size = WRAP(pa_stream_writable_size)(stm->output_stream);
946 0 : trigger_user_callback(stm->output_stream, NULL, writable_size, stm);
947 : }
948 :
949 : static int
950 0 : pulse_stream_start(cubeb_stream * stm)
951 : {
952 0 : stm->shutdown = 0;
953 0 : stream_cork(stm, UNCORK | NOTIFY);
954 :
955 0 : if (stm->output_stream && !stm->input_stream) {
956 : /* On output only case need to manually call user cb once in order to make
957 : * things roll. This is done via a defer event in order to execute it
958 : * from PA server thread. */
959 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
960 0 : WRAP(pa_mainloop_api_once)(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
961 : pulse_defer_event_cb, stm);
962 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
963 : }
964 :
965 0 : return CUBEB_OK;
966 : }
967 :
968 : static int
969 0 : pulse_stream_stop(cubeb_stream * stm)
970 : {
971 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
972 0 : stm->shutdown = 1;
973 : // If draining is taking place wait to finish
974 0 : while (stm->drain_timer) {
975 0 : WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
976 : }
977 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
978 :
979 0 : stream_cork(stm, CORK | NOTIFY);
980 0 : return CUBEB_OK;
981 : }
982 :
983 : static int
984 0 : pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
985 : {
986 : int r, in_thread;
987 : pa_usec_t r_usec;
988 : uint64_t bytes;
989 :
990 0 : if (!stm || !stm->output_stream) {
991 0 : return CUBEB_ERROR;
992 : }
993 :
994 0 : in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
995 :
996 0 : if (!in_thread) {
997 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
998 : }
999 0 : r = WRAP(pa_stream_get_time)(stm->output_stream, &r_usec);
1000 0 : if (!in_thread) {
1001 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
1002 : }
1003 :
1004 0 : if (r != 0) {
1005 0 : return CUBEB_ERROR;
1006 : }
1007 :
1008 0 : bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->output_sample_spec);
1009 0 : *position = bytes / WRAP(pa_frame_size)(&stm->output_sample_spec);
1010 :
1011 0 : return CUBEB_OK;
1012 : }
1013 :
1014 : static int
1015 0 : pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
1016 : {
1017 : pa_usec_t r_usec;
1018 : int negative, r;
1019 :
1020 0 : if (!stm || !stm->output_stream) {
1021 0 : return CUBEB_ERROR;
1022 : }
1023 :
1024 0 : r = WRAP(pa_stream_get_latency)(stm->output_stream, &r_usec, &negative);
1025 0 : assert(!negative);
1026 0 : if (r) {
1027 0 : return CUBEB_ERROR;
1028 : }
1029 :
1030 0 : *latency = r_usec * stm->output_sample_spec.rate / PA_USEC_PER_SEC;
1031 0 : return CUBEB_OK;
1032 : }
1033 :
1034 : static void
1035 0 : volume_success(pa_context *c, int success, void *userdata)
1036 : {
1037 : (void)success;
1038 : (void)c;
1039 0 : cubeb_stream * stream = userdata;
1040 0 : assert(success);
1041 0 : WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
1042 0 : }
1043 :
1044 : static int
1045 0 : pulse_stream_set_volume(cubeb_stream * stm, float volume)
1046 : {
1047 : uint32_t index;
1048 : pa_operation * op;
1049 : pa_volume_t vol;
1050 : pa_cvolume cvol;
1051 : const pa_sample_spec * ss;
1052 :
1053 0 : if (!stm->output_stream) {
1054 0 : return CUBEB_ERROR;
1055 : }
1056 :
1057 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
1058 :
1059 : /* if the pulse daemon is configured to use flat volumes,
1060 : * apply our own gain instead of changing the input volume on the sink. */
1061 0 : if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) {
1062 0 : stm->volume = volume;
1063 : } else {
1064 0 : ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream);
1065 :
1066 0 : vol = WRAP(pa_sw_volume_from_linear)(volume);
1067 0 : WRAP(pa_cvolume_set)(&cvol, ss->channels, vol);
1068 :
1069 0 : index = WRAP(pa_stream_get_index)(stm->output_stream);
1070 :
1071 0 : op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
1072 : index, &cvol, volume_success,
1073 : stm);
1074 0 : if (op) {
1075 0 : operation_wait(stm->context, stm->output_stream, op);
1076 0 : WRAP(pa_operation_unref)(op);
1077 : }
1078 : }
1079 :
1080 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
1081 :
1082 0 : return CUBEB_OK;
1083 : }
1084 :
1085 : struct sink_input_info_result {
1086 : pa_cvolume * cvol;
1087 : pa_threaded_mainloop * mainloop;
1088 : };
1089 :
1090 : static void
1091 0 : sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u)
1092 : {
1093 0 : struct sink_input_info_result * r = u;
1094 0 : if (!eol) {
1095 0 : *r->cvol = i->volume;
1096 : }
1097 0 : WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0);
1098 0 : }
1099 :
1100 : static int
1101 0 : pulse_stream_set_panning(cubeb_stream * stm, float panning)
1102 : {
1103 : const pa_channel_map * map;
1104 : pa_cvolume cvol;
1105 : uint32_t index;
1106 : pa_operation * op;
1107 :
1108 0 : if (!stm->output_stream) {
1109 0 : return CUBEB_ERROR;
1110 : }
1111 :
1112 0 : WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
1113 :
1114 0 : map = WRAP(pa_stream_get_channel_map)(stm->output_stream);
1115 0 : if (!WRAP(pa_channel_map_can_balance)(map)) {
1116 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
1117 0 : return CUBEB_ERROR;
1118 : }
1119 :
1120 0 : index = WRAP(pa_stream_get_index)(stm->output_stream);
1121 :
1122 0 : struct sink_input_info_result r = { &cvol, stm->context->mainloop };
1123 0 : op = WRAP(pa_context_get_sink_input_info)(stm->context->context,
1124 : index,
1125 : sink_input_info_cb,
1126 : &r);
1127 0 : if (op) {
1128 0 : operation_wait(stm->context, stm->output_stream, op);
1129 0 : WRAP(pa_operation_unref)(op);
1130 : }
1131 :
1132 0 : WRAP(pa_cvolume_set_balance)(&cvol, map, panning);
1133 :
1134 0 : op = WRAP(pa_context_set_sink_input_volume)(stm->context->context,
1135 : index, &cvol, volume_success,
1136 : stm);
1137 0 : if (op) {
1138 0 : operation_wait(stm->context, stm->output_stream, op);
1139 0 : WRAP(pa_operation_unref)(op);
1140 : }
1141 :
1142 0 : WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
1143 :
1144 0 : return CUBEB_OK;
1145 : }
1146 :
1147 : typedef struct {
1148 : char * default_sink_name;
1149 : char * default_source_name;
1150 :
1151 : cubeb_device_info * devinfo;
1152 : uint32_t max;
1153 : uint32_t count;
1154 : cubeb * context;
1155 : } pulse_dev_list_data;
1156 :
1157 : static cubeb_device_fmt
1158 0 : pulse_format_to_cubeb_format(pa_sample_format_t format)
1159 : {
1160 0 : switch (format) {
1161 : case PA_SAMPLE_S16LE:
1162 0 : return CUBEB_DEVICE_FMT_S16LE;
1163 : case PA_SAMPLE_S16BE:
1164 0 : return CUBEB_DEVICE_FMT_S16BE;
1165 : case PA_SAMPLE_FLOAT32LE:
1166 0 : return CUBEB_DEVICE_FMT_F32LE;
1167 : case PA_SAMPLE_FLOAT32BE:
1168 0 : return CUBEB_DEVICE_FMT_F32BE;
1169 : default:
1170 0 : return CUBEB_DEVICE_FMT_F32NE;
1171 : }
1172 : }
1173 :
1174 : static void
1175 0 : pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data)
1176 : {
1177 0 : if (list_data->count == list_data->max) {
1178 0 : list_data->max += 8;
1179 0 : list_data->devinfo = realloc(list_data->devinfo,
1180 0 : sizeof(cubeb_device_info) * list_data->max);
1181 : }
1182 0 : }
1183 :
1184 : static cubeb_device_state
1185 0 : pulse_get_state_from_sink_port(pa_sink_port_info * info)
1186 : {
1187 0 : if (info != NULL) {
1188 : #if PA_CHECK_VERSION(2, 0, 0)
1189 0 : if (info->available == PA_PORT_AVAILABLE_NO)
1190 0 : return CUBEB_DEVICE_STATE_UNPLUGGED;
1191 : else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
1192 : #endif
1193 0 : return CUBEB_DEVICE_STATE_ENABLED;
1194 : }
1195 :
1196 0 : return CUBEB_DEVICE_STATE_ENABLED;
1197 : }
1198 :
1199 : static void
1200 0 : pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
1201 : int eol, void * user_data)
1202 : {
1203 0 : pulse_dev_list_data * list_data = user_data;
1204 : cubeb_device_info * devinfo;
1205 : const char * prop;
1206 :
1207 : (void)context;
1208 :
1209 0 : if (eol || info == NULL)
1210 0 : return;
1211 :
1212 0 : pulse_ensure_dev_list_data_list_size(list_data);
1213 0 : devinfo = &list_data->devinfo[list_data->count];
1214 0 : memset(devinfo, 0, sizeof(cubeb_device_info));
1215 :
1216 0 : devinfo->device_id = strdup(info->name);
1217 0 : devinfo->devid = (cubeb_devid) devinfo->device_id;
1218 0 : devinfo->friendly_name = strdup(info->description);
1219 0 : prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
1220 0 : if (prop)
1221 0 : devinfo->group_id = strdup(prop);
1222 0 : prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
1223 0 : if (prop)
1224 0 : devinfo->vendor_name = strdup(prop);
1225 :
1226 0 : devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
1227 0 : devinfo->state = pulse_get_state_from_sink_port(info->active_port);
1228 0 : devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ?
1229 0 : CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
1230 :
1231 0 : devinfo->format = CUBEB_DEVICE_FMT_ALL;
1232 0 : devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
1233 0 : devinfo->max_channels = info->channel_map.channels;
1234 0 : devinfo->min_rate = 1;
1235 0 : devinfo->max_rate = PA_RATE_MAX;
1236 0 : devinfo->default_rate = info->sample_spec.rate;
1237 :
1238 0 : devinfo->latency_lo = 0;
1239 0 : devinfo->latency_hi = 0;
1240 :
1241 0 : list_data->count += 1;
1242 :
1243 0 : WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
1244 : }
1245 :
1246 : static cubeb_device_state
1247 0 : pulse_get_state_from_source_port(pa_source_port_info * info)
1248 : {
1249 0 : if (info != NULL) {
1250 : #if PA_CHECK_VERSION(2, 0, 0)
1251 0 : if (info->available == PA_PORT_AVAILABLE_NO)
1252 0 : return CUBEB_DEVICE_STATE_UNPLUGGED;
1253 : else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */
1254 : #endif
1255 0 : return CUBEB_DEVICE_STATE_ENABLED;
1256 : }
1257 :
1258 0 : return CUBEB_DEVICE_STATE_ENABLED;
1259 : }
1260 :
1261 : static void
1262 0 : pulse_source_info_cb(pa_context * context, const pa_source_info * info,
1263 : int eol, void * user_data)
1264 : {
1265 0 : pulse_dev_list_data * list_data = user_data;
1266 : cubeb_device_info * devinfo;
1267 : const char * prop;
1268 :
1269 : (void)context;
1270 :
1271 0 : if (eol)
1272 0 : return;
1273 :
1274 0 : pulse_ensure_dev_list_data_list_size(list_data);
1275 0 : devinfo = &list_data->devinfo[list_data->count];
1276 0 : memset(devinfo, 0, sizeof(cubeb_device_info));
1277 :
1278 0 : devinfo->device_id = strdup(info->name);
1279 0 : devinfo->devid = (cubeb_devid) devinfo->device_id;
1280 0 : devinfo->friendly_name = strdup(info->description);
1281 0 : prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path");
1282 0 : if (prop)
1283 0 : devinfo->group_id = strdup(prop);
1284 0 : prop = WRAP(pa_proplist_gets)(info->proplist, "device.vendor.name");
1285 0 : if (prop)
1286 0 : devinfo->vendor_name = strdup(prop);
1287 :
1288 0 : devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
1289 0 : devinfo->state = pulse_get_state_from_source_port(info->active_port);
1290 0 : devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ?
1291 0 : CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
1292 :
1293 0 : devinfo->format = CUBEB_DEVICE_FMT_ALL;
1294 0 : devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format);
1295 0 : devinfo->max_channels = info->channel_map.channels;
1296 0 : devinfo->min_rate = 1;
1297 0 : devinfo->max_rate = PA_RATE_MAX;
1298 0 : devinfo->default_rate = info->sample_spec.rate;
1299 :
1300 0 : devinfo->latency_lo = 0;
1301 0 : devinfo->latency_hi = 0;
1302 :
1303 0 : list_data->count += 1;
1304 0 : WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
1305 : }
1306 :
1307 : static void
1308 0 : pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata)
1309 : {
1310 0 : pulse_dev_list_data * list_data = userdata;
1311 :
1312 : (void)c;
1313 :
1314 0 : free(list_data->default_sink_name);
1315 0 : free(list_data->default_source_name);
1316 0 : list_data->default_sink_name = strdup(i->default_sink_name);
1317 0 : list_data->default_source_name = strdup(i->default_source_name);
1318 :
1319 0 : WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0);
1320 0 : }
1321 :
1322 : static int
1323 0 : pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
1324 : cubeb_device_collection * collection)
1325 : {
1326 0 : pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context };
1327 : pa_operation * o;
1328 :
1329 0 : WRAP(pa_threaded_mainloop_lock)(context->mainloop);
1330 :
1331 0 : o = WRAP(pa_context_get_server_info)(context->context,
1332 : pulse_server_info_cb, &user_data);
1333 0 : if (o) {
1334 0 : operation_wait(context, NULL, o);
1335 0 : WRAP(pa_operation_unref)(o);
1336 : }
1337 :
1338 0 : if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
1339 0 : o = WRAP(pa_context_get_sink_info_list)(context->context,
1340 : pulse_sink_info_cb, &user_data);
1341 0 : if (o) {
1342 0 : operation_wait(context, NULL, o);
1343 0 : WRAP(pa_operation_unref)(o);
1344 : }
1345 : }
1346 :
1347 0 : if (type & CUBEB_DEVICE_TYPE_INPUT) {
1348 0 : o = WRAP(pa_context_get_source_info_list)(context->context,
1349 : pulse_source_info_cb, &user_data);
1350 0 : if (o) {
1351 0 : operation_wait(context, NULL, o);
1352 0 : WRAP(pa_operation_unref)(o);
1353 : }
1354 : }
1355 :
1356 0 : WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
1357 :
1358 0 : collection->device = user_data.devinfo;
1359 0 : collection->count = user_data.count;
1360 :
1361 0 : free(user_data.default_sink_name);
1362 0 : free(user_data.default_source_name);
1363 0 : return CUBEB_OK;
1364 : }
1365 :
1366 : static int
1367 0 : pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device)
1368 : {
1369 : #if PA_CHECK_VERSION(0, 9, 8)
1370 0 : *device = calloc(1, sizeof(cubeb_device));
1371 0 : if (*device == NULL)
1372 0 : return CUBEB_ERROR;
1373 :
1374 0 : if (stm->input_stream) {
1375 0 : const char * name = WRAP(pa_stream_get_device_name)(stm->input_stream);
1376 0 : (*device)->input_name = (name == NULL) ? NULL : strdup(name);
1377 : }
1378 :
1379 0 : if (stm->output_stream) {
1380 0 : const char * name = WRAP(pa_stream_get_device_name)(stm->output_stream);
1381 0 : (*device)->output_name = (name == NULL) ? NULL : strdup(name);
1382 : }
1383 :
1384 0 : return CUBEB_OK;
1385 : #else
1386 : return CUBEB_ERROR_NOT_SUPPORTED;
1387 : #endif
1388 : }
1389 :
1390 : static int
1391 0 : pulse_stream_device_destroy(cubeb_stream * stream,
1392 : cubeb_device * device)
1393 : {
1394 : (void)stream;
1395 0 : free(device->input_name);
1396 0 : free(device->output_name);
1397 0 : free(device);
1398 0 : return CUBEB_OK;
1399 : }
1400 :
1401 : static void
1402 0 : pulse_subscribe_callback(pa_context * ctx,
1403 : pa_subscription_event_type_t t,
1404 : uint32_t index, void * userdata)
1405 : {
1406 : (void)ctx;
1407 0 : cubeb * context = userdata;
1408 :
1409 0 : switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
1410 : case PA_SUBSCRIPTION_EVENT_SOURCE:
1411 : case PA_SUBSCRIPTION_EVENT_SINK:
1412 :
1413 0 : if (g_cubeb_log_level) {
1414 0 : if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
1415 0 : (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1416 0 : LOG("Removing sink index %d", index);
1417 0 : } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE &&
1418 0 : (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
1419 0 : LOG("Adding sink index %d", index);
1420 : }
1421 0 : if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
1422 0 : (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) {
1423 0 : LOG("Removing source index %d", index);
1424 0 : } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK &&
1425 0 : (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
1426 0 : LOG("Adding source index %d", index);
1427 : }
1428 : }
1429 :
1430 0 : if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
1431 0 : (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
1432 0 : context->collection_changed_callback(context, context->collection_changed_user_ptr);
1433 : }
1434 0 : break;
1435 : }
1436 0 : }
1437 :
1438 : static void
1439 0 : subscribe_success(pa_context *c, int success, void *userdata)
1440 : {
1441 : (void)c;
1442 0 : cubeb * context = userdata;
1443 0 : assert(success);
1444 0 : WRAP(pa_threaded_mainloop_signal)(context->mainloop, 0);
1445 0 : }
1446 :
1447 : static int
1448 0 : pulse_register_device_collection_changed(cubeb * context,
1449 : cubeb_device_type devtype,
1450 : cubeb_device_collection_changed_callback collection_changed_callback,
1451 : void * user_ptr)
1452 : {
1453 0 : context->collection_changed_callback = collection_changed_callback;
1454 0 : context->collection_changed_user_ptr = user_ptr;
1455 :
1456 0 : WRAP(pa_threaded_mainloop_lock)(context->mainloop);
1457 :
1458 : pa_subscription_mask_t mask;
1459 0 : if (context->collection_changed_callback == NULL) {
1460 : // Unregister subscription
1461 0 : WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL);
1462 0 : mask = PA_SUBSCRIPTION_MASK_NULL;
1463 : } else {
1464 0 : WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context);
1465 0 : if (devtype == CUBEB_DEVICE_TYPE_INPUT)
1466 0 : mask = PA_SUBSCRIPTION_MASK_SOURCE;
1467 0 : else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT)
1468 0 : mask = PA_SUBSCRIPTION_MASK_SINK;
1469 : else
1470 0 : mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE;
1471 : }
1472 :
1473 : pa_operation * o;
1474 0 : o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context);
1475 0 : if (o == NULL) {
1476 0 : LOG("Context subscribe failed");
1477 0 : return CUBEB_ERROR;
1478 : }
1479 0 : operation_wait(context, NULL, o);
1480 0 : WRAP(pa_operation_unref)(o);
1481 :
1482 0 : WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
1483 :
1484 0 : return CUBEB_OK;
1485 : }
1486 :
1487 : static struct cubeb_ops const pulse_ops = {
1488 : .init = pulse_init,
1489 : .get_backend_id = pulse_get_backend_id,
1490 : .get_max_channel_count = pulse_get_max_channel_count,
1491 : .get_min_latency = pulse_get_min_latency,
1492 : .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
1493 : .get_preferred_channel_layout = pulse_get_preferred_channel_layout,
1494 : .enumerate_devices = pulse_enumerate_devices,
1495 : .device_collection_destroy = cubeb_utils_default_device_collection_destroy,
1496 : .destroy = pulse_destroy,
1497 : .stream_init = pulse_stream_init,
1498 : .stream_destroy = pulse_stream_destroy,
1499 : .stream_start = pulse_stream_start,
1500 : .stream_stop = pulse_stream_stop,
1501 : .stream_get_position = pulse_stream_get_position,
1502 : .stream_get_latency = pulse_stream_get_latency,
1503 : .stream_set_volume = pulse_stream_set_volume,
1504 : .stream_set_panning = pulse_stream_set_panning,
1505 : .stream_get_current_device = pulse_stream_get_current_device,
1506 : .stream_device_destroy = pulse_stream_device_destroy,
1507 : .stream_register_device_changed_callback = NULL,
1508 : .register_device_collection_changed = pulse_register_device_collection_changed
1509 : };
|