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