diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_pulse.c')
-rw-r--r-- | media/libcubeb/src/cubeb_pulse.c | 488 |
1 files changed, 359 insertions, 129 deletions
diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index 4f474452d..846ba426a 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -7,12 +7,14 @@ #undef NDEBUG #include <assert.h> #include <dlfcn.h> -#include <stdlib.h> #include <pulse/pulseaudio.h> +#include <stdio.h> +#include <stdlib.h> #include <string.h> -#include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include <stdio.h> +#include "cubeb/cubeb.h" +#include "cubeb_mixer.h" +#include "cubeb_strings.h" #ifdef DISABLE_LIBPULSE_DLOPEN #define WRAP(x) x @@ -20,13 +22,14 @@ #define WRAP(x) cubeb_##x #define LIBPULSE_API_VISIT(X) \ X(pa_channel_map_can_balance) \ - X(pa_channel_map_init_auto) \ + X(pa_channel_map_init) \ X(pa_context_connect) \ X(pa_context_disconnect) \ X(pa_context_drain) \ X(pa_context_get_server_info) \ X(pa_context_get_sink_info_by_name) \ X(pa_context_get_sink_info_list) \ + X(pa_context_get_sink_input_info) \ X(pa_context_get_source_info_list) \ X(pa_context_get_state) \ X(pa_context_new) \ @@ -81,33 +84,50 @@ X(pa_context_set_subscribe_callback) \ X(pa_context_subscribe) \ X(pa_mainloop_api_once) \ + X(pa_get_library_version) \ + X(pa_channel_map_init_auto) \ #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; LIBPULSE_API_VISIT(MAKE_TYPEDEF); #undef MAKE_TYPEDEF #endif +#if PA_CHECK_VERSION(2, 0, 0) +static int has_pulse_v2 = 0; +#endif + static struct cubeb_ops const pulse_ops; +struct cubeb_default_sink_info { + pa_channel_map channel_map; + uint32_t sample_spec_rate; + pa_sink_flags_t flags; +}; + struct cubeb { struct cubeb_ops const * ops; void * libpulse; pa_threaded_mainloop * mainloop; pa_context * context; - pa_sink_info * default_sink_info; + struct cubeb_default_sink_info * default_sink_info; char * context_name; int error; - cubeb_device_collection_changed_callback collection_changed_callback; - void * collection_changed_user_ptr; + cubeb_device_collection_changed_callback output_collection_changed_callback; + void * output_collection_changed_user_ptr; + cubeb_device_collection_changed_callback input_collection_changed_callback; + void * input_collection_changed_user_ptr; + cubeb_strings * device_ids; }; struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ cubeb * context; + void * user_ptr; + /**/ pa_stream * output_stream; pa_stream * input_stream; cubeb_data_callback data_callback; cubeb_state_callback state_callback; - void * user_ptr; pa_time_event * drain_timer; pa_sample_spec output_sample_spec; pa_sample_spec input_sample_spec; @@ -124,6 +144,24 @@ enum cork_state { NOTIFY = 1 << 1 }; +static int +intern_device_id(cubeb * ctx, char const ** id) +{ + char const * interned; + + assert(ctx); + assert(id); + + interned = cubeb_strings_intern(ctx->device_ids, *id); + if (!interned) { + return CUBEB_ERROR; + } + + *id = interned; + + return CUBEB_OK; +} + static void sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u) { @@ -131,8 +169,10 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi cubeb * ctx = u; if (!eol) { free(ctx->default_sink_info); - ctx->default_sink_info = malloc(sizeof(pa_sink_info)); - memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info)); + ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info)); + memcpy(&ctx->default_sink_info->channel_map, &info->channel_map, sizeof(pa_channel_map)); + ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate; + ctx->default_sink_info->flags = info->flags; } WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0); } @@ -140,7 +180,11 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi static void server_info_callback(pa_context * context, const pa_server_info * info, void * u) { - WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); + pa_operation * o; + o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); + if (o) { + WRAP(pa_operation_unref)(o); + } } static void @@ -467,12 +511,81 @@ stream_update_timing_info(cubeb_stream * stm) return r; } +static pa_channel_position_t +cubeb_channel_to_pa_channel(cubeb_channel channel) +{ + switch (channel) { + case CHANNEL_FRONT_LEFT: + return PA_CHANNEL_POSITION_FRONT_LEFT; + case CHANNEL_FRONT_RIGHT: + return PA_CHANNEL_POSITION_FRONT_RIGHT; + case CHANNEL_FRONT_CENTER: + return PA_CHANNEL_POSITION_FRONT_CENTER; + case CHANNEL_LOW_FREQUENCY: + return PA_CHANNEL_POSITION_LFE; + case CHANNEL_BACK_LEFT: + return PA_CHANNEL_POSITION_REAR_LEFT; + case CHANNEL_BACK_RIGHT: + return PA_CHANNEL_POSITION_REAR_RIGHT; + case CHANNEL_FRONT_LEFT_OF_CENTER: + return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + case CHANNEL_FRONT_RIGHT_OF_CENTER: + return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + case CHANNEL_BACK_CENTER: + return PA_CHANNEL_POSITION_REAR_CENTER; + case CHANNEL_SIDE_LEFT: + return PA_CHANNEL_POSITION_SIDE_LEFT; + case CHANNEL_SIDE_RIGHT: + return PA_CHANNEL_POSITION_SIDE_RIGHT; + case CHANNEL_TOP_CENTER: + return PA_CHANNEL_POSITION_TOP_CENTER; + case CHANNEL_TOP_FRONT_LEFT: + return PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + case CHANNEL_TOP_FRONT_CENTER: + return PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + case CHANNEL_TOP_FRONT_RIGHT: + return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + case CHANNEL_TOP_BACK_LEFT: + return PA_CHANNEL_POSITION_TOP_REAR_LEFT; + case CHANNEL_TOP_BACK_CENTER: + return PA_CHANNEL_POSITION_TOP_REAR_CENTER; + case CHANNEL_TOP_BACK_RIGHT: + return PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + default: + return PA_CHANNEL_POSITION_INVALID; + } +} + +static void +layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm) +{ + assert(cm && layout != CUBEB_LAYOUT_UNDEFINED); + + WRAP(pa_channel_map_init)(cm); + + uint32_t channels = 0; + cubeb_channel_layout channelMap = layout; + for (uint32_t i = 0 ; channelMap != 0; ++i) { + uint32_t channel = (channelMap & 1) << i; + if (channel != 0) { + cm->map[channels] = cubeb_channel_to_pa_channel(channel); + channels++; + } + channelMap = channelMap >> 1; + } + unsigned int channels_from_layout = cubeb_channel_layout_nb_channels(layout); + assert(channels_from_layout <= UINT8_MAX); + cm->channels = (uint8_t) channels_from_layout; +} + static void pulse_context_destroy(cubeb * ctx); static void pulse_destroy(cubeb * ctx); static int pulse_context_init(cubeb * ctx) { + int r; + if (ctx->context) { assert(ctx->error == 1); pulse_context_destroy(ctx); @@ -486,9 +599,9 @@ pulse_context_init(cubeb * ctx) WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx); WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); + r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); - if (wait_until_context_ready(ctx) != 0) { + if (r < 0 || wait_until_context_ready(ctx) != 0) { WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); pulse_context_destroy(ctx); ctx->context = NULL; @@ -502,18 +615,25 @@ pulse_context_init(cubeb * ctx) return 0; } +static int pulse_subscribe_notifications(cubeb * context, + pa_subscription_mask_t mask); + /*static*/ int pulse_init(cubeb ** context, char const * context_name) { void * libpulse = NULL; cubeb * ctx; + pa_operation * o; *context = NULL; #ifndef DISABLE_LIBPULSE_DLOPEN libpulse = dlopen("libpulse.so.0", RTLD_LAZY); if (!libpulse) { - return CUBEB_ERROR; + libpulse = dlopen("libpulse.so", RTLD_LAZY); + if (!libpulse) { + return CUBEB_ERROR; + } } #define LOAD(x) { \ @@ -528,11 +648,20 @@ pulse_init(cubeb ** context, char const * context_name) #undef LOAD #endif +#if PA_CHECK_VERSION(2, 0, 0) + const char* version = WRAP(pa_get_library_version)(); + has_pulse_v2 = strtol(version, NULL, 10) >= 2; +#endif + ctx = calloc(1, sizeof(*ctx)); assert(ctx); ctx->ops = &pulse_ops; ctx->libpulse = libpulse; + if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { + pulse_destroy(ctx); + return CUBEB_ERROR; + } ctx->mainloop = WRAP(pa_threaded_mainloop_new)(); ctx->default_sink_info = NULL; @@ -545,10 +674,20 @@ pulse_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } + /* server_info_callback performs a second async query, which is + responsible for initializing default_sink_info and signalling the + mainloop to end the wait. */ WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx); + o = WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx); + if (o) { + operation_wait(ctx, NULL, o); + WRAP(pa_operation_unref)(o); + } WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + /* Update `default_sink_info` when the default device changes. */ + pulse_subscribe_notifications(ctx, PA_SUBSCRIPTION_MASK_SERVER); + *context = ctx; return CUBEB_OK; @@ -567,11 +706,8 @@ pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) (void)ctx; assert(ctx && max_channels); - WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - while (!ctx->default_sink_info) { - WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); - } - WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + if (!ctx->default_sink_info) + return CUBEB_ERROR; *max_channels = ctx->default_sink_info->channel_map.channels; @@ -584,13 +720,10 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) assert(ctx && rate); (void)ctx; - WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); - while (!ctx->default_sink_info) { - WRAP(pa_threaded_mainloop_wait)(ctx->mainloop); - } - WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); + if (!ctx->default_sink_info) + return CUBEB_ERROR; - *rate = ctx->default_sink_info->sample_spec.rate; + *rate = ctx->default_sink_info->sample_spec_rate; return CUBEB_OK; } @@ -625,9 +758,7 @@ pulse_context_destroy(cubeb * ctx) static void pulse_destroy(cubeb * ctx) { - if (ctx->context_name) { - free(ctx->context_name); - } + free(ctx->context_name); if (ctx->context) { pulse_context_destroy(ctx); } @@ -637,12 +768,14 @@ pulse_destroy(cubeb * ctx) WRAP(pa_threaded_mainloop_free)(ctx->mainloop); } + if (ctx->device_ids) { + cubeb_strings_destroy(ctx->device_ids); + } + if (ctx->libpulse) { dlclose(ctx->libpulse); } - if (ctx->default_sink_info) { - free(ctx->default_sink_info); - } + free(ctx->default_sink_info); free(ctx); } @@ -665,6 +798,25 @@ to_pulse_format(cubeb_sample_format format) } } +static cubeb_channel_layout +pulse_default_layout_for_channels(uint32_t ch) +{ + assert (ch > 0 && ch <= 8); + switch (ch) { + case 1: return CUBEB_LAYOUT_MONO; + case 2: return CUBEB_LAYOUT_STEREO; + case 3: return CUBEB_LAYOUT_3F; + case 4: return CUBEB_LAYOUT_QUAD; + case 5: return CUBEB_LAYOUT_3F2; + case 6: return CUBEB_LAYOUT_3F_LFE | + CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT; + case 7: return CUBEB_LAYOUT_3F3R_LFE; + case 8: return CUBEB_LAYOUT_3F4_LFE; + } + // Never get here! + return CUBEB_LAYOUT_UNDEFINED; +} + static int create_pa_stream(cubeb_stream * stm, pa_stream ** pa_stm, @@ -672,15 +824,39 @@ create_pa_stream(cubeb_stream * stm, char const * stream_name) { assert(stm && stream_params); + assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm && + (stream_params->layout == CUBEB_LAYOUT_UNDEFINED || + (stream_params->layout != CUBEB_LAYOUT_UNDEFINED && + cubeb_channel_layout_nb_channels(stream_params->layout) == stream_params->channels)))); + if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { + return CUBEB_ERROR_NOT_SUPPORTED; + } *pa_stm = NULL; pa_sample_spec ss; ss.format = to_pulse_format(stream_params->format); if (ss.format == PA_SAMPLE_INVALID) return CUBEB_ERROR_INVALID_FORMAT; ss.rate = stream_params->rate; - ss.channels = stream_params->channels; - - *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + if (stream_params->channels > UINT8_MAX) + return CUBEB_ERROR_INVALID_FORMAT; + ss.channels = (uint8_t) stream_params->channels; + + if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) { + pa_channel_map cm; + if (stream_params->channels <= 8 && + !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels, PA_CHANNEL_MAP_DEFAULT)) { + LOG("Layout undefined and PulseAudio's default layout has not been configured, guess one."); + layout_to_channel_map(pulse_default_layout_for_channels(stream_params->channels), &cm); + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); + } else { + LOG("Layout undefined, PulseAudio will use its default."); + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); + } + } else { + pa_channel_map cm; + layout_to_channel_map(stream_params->layout, &cm); + *pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); + } return (*pa_stm == NULL) ? CUBEB_ERROR : CUBEB_OK; } @@ -753,7 +929,7 @@ pulse_stream_init(cubeb * context, battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec); WRAP(pa_stream_connect_playback)(stm->output_stream, - output_device, + (char const *) output_device, &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY, @@ -776,7 +952,7 @@ pulse_stream_init(cubeb * context, battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec); WRAP(pa_stream_connect_record)(stm->input_stream, - input_device, + (char const *) input_device, &battr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY); @@ -796,7 +972,7 @@ pulse_stream_init(cubeb * context, return CUBEB_ERROR; } - if (g_log_level) { + if (g_cubeb_log_level) { if (output_stream_params){ const pa_buffer_attr * output_att; output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); @@ -813,6 +989,7 @@ pulse_stream_init(cubeb * context, } *stream = stm; + LOG("Cubeb stream (%p) init successful.", *stream); return CUBEB_OK; } @@ -844,6 +1021,7 @@ pulse_stream_destroy(cubeb_stream * stm) } WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + LOG("Cubeb stream (%p) destroyed successfully.", stm); free(stm); } @@ -875,6 +1053,7 @@ pulse_stream_start(cubeb_stream * stm) WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); } + LOG("Cubeb stream (%p) started successfully.", stm); return CUBEB_OK; } @@ -890,6 +1069,7 @@ pulse_stream_stop(cubeb_stream * stm) WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); stream_cork(stm, CORK | NOTIFY); + LOG("Cubeb stream (%p) stopped successfully.", stm); return CUBEB_OK; } @@ -962,6 +1142,7 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) pa_volume_t vol; pa_cvolume cvol; const pa_sample_spec * ss; + cubeb * ctx; if (!stm->output_stream) { return CUBEB_ERROR; @@ -969,13 +1150,11 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); - while (!stm->context->default_sink_info) { - WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop); - } - /* if the pulse daemon is configured to use flat volumes, * apply our own gain instead of changing the input volume on the sink. */ - if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) { + ctx = stm->context; + if (ctx->default_sink_info && + (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) { stm->volume = volume; } else { ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream); @@ -985,46 +1164,40 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) index = WRAP(pa_stream_get_index)(stm->output_stream); - op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, + op = WRAP(pa_context_set_sink_input_volume)(ctx->context, index, &cvol, volume_success, stm); if (op) { - operation_wait(stm->context, stm->output_stream, op); + operation_wait(ctx, stm->output_stream, op); WRAP(pa_operation_unref)(op); } } - WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); return CUBEB_OK; } -static int -pulse_stream_set_panning(cubeb_stream * stream, float panning) -{ - const pa_channel_map * map; - pa_cvolume vol; - - if (!stream->output_stream) { - return CUBEB_ERROR; - } - - map = WRAP(pa_stream_get_channel_map)(stream->output_stream); +struct sink_input_info_result { + pa_cvolume * cvol; + pa_threaded_mainloop * mainloop; +}; - if (!WRAP(pa_channel_map_can_balance)(map)) { - return CUBEB_ERROR; +static void +sink_input_info_cb(pa_context * c, pa_sink_input_info const * i, int eol, void * u) +{ + struct sink_input_info_result * r = u; + if (!eol) { + *r->cvol = i->volume; } - - WRAP(pa_cvolume_set_balance)(&vol, map, panning); - - return CUBEB_OK; + WRAP(pa_threaded_mainloop_signal)(r->mainloop, 0); } typedef struct { char * default_sink_name; char * default_source_name; - cubeb_device_info ** devinfo; + cubeb_device_info * devinfo; uint32_t max; uint32_t count; cubeb * context; @@ -1053,7 +1226,7 @@ pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data) if (list_data->count == list_data->max) { list_data->max += 8; list_data->devinfo = realloc(list_data->devinfo, - sizeof(cubeb_device_info *) * list_data->max); + sizeof(cubeb_device_info) * list_data->max); } } @@ -1062,33 +1235,47 @@ pulse_get_state_from_sink_port(pa_sink_port_info * info) { if (info != NULL) { #if PA_CHECK_VERSION(2, 0, 0) - if (info->available == PA_PORT_AVAILABLE_NO) + if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO) return CUBEB_DEVICE_STATE_UNPLUGGED; else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ #endif return CUBEB_DEVICE_STATE_ENABLED; } - return CUBEB_DEVICE_STATE_DISABLED; + return CUBEB_DEVICE_STATE_ENABLED; } static void pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, - int eol, void * user_data) + int eol, void * user_data) { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - const char * prop; + char const * prop = NULL; + char const * device_id = NULL; (void)context; - if (eol || info == NULL) + if (eol) { + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + return; + } + + if (info == NULL) return; - devinfo = calloc(1, sizeof(cubeb_device_info)); + device_id = info->name; + if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { + assert(NULL); + return; + } + + pulse_ensure_dev_list_data_list_size(list_data); + devinfo = &list_data->devinfo[list_data->count]; + memset(devinfo, 0, sizeof(cubeb_device_info)); - devinfo->device_id = strdup(info->name); - devinfo->devid = devinfo->device_id; + devinfo->device_id = device_id; + devinfo->devid = (cubeb_devid) devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); if (prop) @@ -1099,7 +1286,8 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT; devinfo->state = pulse_get_state_from_sink_port(info->active_port); - devinfo->preferred = strcmp(info->name, list_data->default_sink_name) == 0; + devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ? + CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); @@ -1111,10 +1299,7 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->latency_lo = 0; devinfo->latency_hi = 0; - pulse_ensure_dev_list_data_list_size (list_data); - list_data->devinfo[list_data->count++] = devinfo; - - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + list_data->count += 1; } static cubeb_device_state @@ -1122,14 +1307,14 @@ pulse_get_state_from_source_port(pa_source_port_info * info) { if (info != NULL) { #if PA_CHECK_VERSION(2, 0, 0) - if (info->available == PA_PORT_AVAILABLE_NO) + if (has_pulse_v2 && info->available == PA_PORT_AVAILABLE_NO) return CUBEB_DEVICE_STATE_UNPLUGGED; else /*if (info->available == PA_PORT_AVAILABLE_YES) + UNKNOWN */ #endif return CUBEB_DEVICE_STATE_ENABLED; } - return CUBEB_DEVICE_STATE_DISABLED; + return CUBEB_DEVICE_STATE_ENABLED; } static void @@ -1138,17 +1323,28 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - const char * prop; + char const * prop = NULL; + char const * device_id = NULL; (void)context; - if (eol) + if (eol) { + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + return; + } + + device_id = info->name; + if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { + assert(NULL); return; + } - devinfo = calloc(1, sizeof(cubeb_device_info)); + pulse_ensure_dev_list_data_list_size(list_data); + devinfo = &list_data->devinfo[list_data->count]; + memset(devinfo, 0, sizeof(cubeb_device_info)); - devinfo->device_id = strdup(info->name); - devinfo->devid = devinfo->device_id; + devinfo->device_id = device_id; + devinfo->devid = (cubeb_devid) devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); if (prop) @@ -1159,7 +1355,8 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->type = CUBEB_DEVICE_TYPE_INPUT; devinfo->state = pulse_get_state_from_source_port(info->active_port); - devinfo->preferred = strcmp(info->name, list_data->default_source_name) == 0; + devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ? + CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); @@ -1171,10 +1368,7 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->latency_lo = 0; devinfo->latency_hi = 0; - pulse_ensure_dev_list_data_list_size (list_data); - list_data->devinfo[list_data->count++] = devinfo; - - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); + list_data->count += 1; } static void @@ -1186,19 +1380,20 @@ pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata) free(list_data->default_sink_name); free(list_data->default_source_name); - list_data->default_sink_name = strdup(i->default_sink_name); - list_data->default_source_name = strdup(i->default_source_name); + list_data->default_sink_name = + i->default_sink_name ? strdup(i->default_sink_name) : NULL; + list_data->default_source_name = + i->default_source_name ? strdup(i->default_source_name) : NULL; WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static int pulse_enumerate_devices(cubeb * context, cubeb_device_type type, - cubeb_device_collection ** collection) + cubeb_device_collection * collection) { pulse_dev_list_data user_data = { NULL, NULL, NULL, 0, 0, context }; pa_operation * o; - uint32_t i; WRAP(pa_threaded_mainloop_lock)(context->mainloop); @@ -1229,15 +1424,26 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type, WRAP(pa_threaded_mainloop_unlock)(context->mainloop); - *collection = malloc(sizeof(cubeb_device_collection) + - sizeof(cubeb_device_info *) * (user_data.count > 0 ? user_data.count - 1 : 0)); - (*collection)->count = user_data.count; - for (i = 0; i < user_data.count; i++) - (*collection)->device[i] = user_data.devinfo[i]; + collection->device = user_data.devinfo; + collection->count = user_data.count; free(user_data.default_sink_name); free(user_data.default_source_name); - free(user_data.devinfo); + return CUBEB_OK; +} + +static int +pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection) +{ + size_t n; + + for (n = 0; n < collection->count; n++) { + free((void *) collection->device[n].friendly_name); + free((void *) collection->device[n].vendor_name); + free((void *) collection->device[n].group_id); + } + + free(collection->device); return CUBEB_OK; } @@ -1285,29 +1491,40 @@ pulse_subscribe_callback(pa_context * ctx, cubeb * context = userdata; switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + case PA_SUBSCRIPTION_EVENT_SERVER: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { + LOG("Server changed %d", index); + WRAP(pa_context_get_server_info)(context->context, server_info_callback, context); + } + break; case PA_SUBSCRIPTION_EVENT_SOURCE: case PA_SUBSCRIPTION_EVENT_SINK: - if (g_log_level) { + if (g_cubeb_log_level) { if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - LOG("Removing sink index %d", index); + LOG("Removing source index %d", index); } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - LOG("Adding sink index %d", index); + LOG("Adding source index %d", index); } if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { - LOG("Removing source index %d", index); + LOG("Removing sink index %d", index); } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - LOG("Adding source index %d", index); + LOG("Adding sink index %d", index); } } if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE || (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { - context->collection_changed_callback(context, context->collection_changed_user_ptr); + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { + context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr); + } + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr); + } } break; } @@ -1323,34 +1540,15 @@ subscribe_success(pa_context *c, int success, void *userdata) } static int -pulse_register_device_collection_changed(cubeb * context, - cubeb_device_type devtype, - cubeb_device_collection_changed_callback collection_changed_callback, - void * user_ptr) -{ - context->collection_changed_callback = collection_changed_callback; - context->collection_changed_user_ptr = user_ptr; - +pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) { WRAP(pa_threaded_mainloop_lock)(context->mainloop); - pa_subscription_mask_t mask; - if (context->collection_changed_callback == NULL) { - // Unregister subscription - WRAP(pa_context_set_subscribe_callback)(context->context, NULL, NULL); - mask = PA_SUBSCRIPTION_MASK_NULL; - } else { - WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); - if (devtype == CUBEB_DEVICE_TYPE_INPUT) - mask = PA_SUBSCRIPTION_MASK_SOURCE; - else if (devtype == CUBEB_DEVICE_TYPE_OUTPUT) - mask = PA_SUBSCRIPTION_MASK_SINK; - else - mask = PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE; - } + WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); pa_operation * o; o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context); if (o == NULL) { + WRAP(pa_threaded_mainloop_unlock)(context->mainloop); LOG("Context subscribe failed"); return CUBEB_ERROR; } @@ -1362,6 +1560,37 @@ pulse_register_device_collection_changed(cubeb * context, return CUBEB_OK; } +static int +pulse_register_device_collection_changed(cubeb * context, + cubeb_device_type devtype, + cubeb_device_collection_changed_callback collection_changed_callback, + void * user_ptr) +{ + if (devtype & CUBEB_DEVICE_TYPE_INPUT) { + context->input_collection_changed_callback = collection_changed_callback; + context->input_collection_changed_user_ptr = user_ptr; + } + if (devtype & CUBEB_DEVICE_TYPE_OUTPUT) { + context->output_collection_changed_callback = collection_changed_callback; + context->output_collection_changed_user_ptr = user_ptr; + } + + pa_subscription_mask_t mask = PA_SUBSCRIPTION_MASK_NULL; + if (context->input_collection_changed_callback) { + /* Input added or removed */ + mask |= PA_SUBSCRIPTION_MASK_SOURCE; + } + if (context->output_collection_changed_callback) { + /* Output added or removed */ + mask |= PA_SUBSCRIPTION_MASK_SINK; + } + /* Default device changed, this is always registered in order to update the + * `default_sink_info` when the default device changes. */ + mask |= PA_SUBSCRIPTION_MASK_SERVER; + + return pulse_subscribe_notifications(context, mask); +} + static struct cubeb_ops const pulse_ops = { .init = pulse_init, .get_backend_id = pulse_get_backend_id, @@ -1369,15 +1598,16 @@ static struct cubeb_ops const pulse_ops = { .get_min_latency = pulse_get_min_latency, .get_preferred_sample_rate = pulse_get_preferred_sample_rate, .enumerate_devices = pulse_enumerate_devices, + .device_collection_destroy = pulse_device_collection_destroy, .destroy = pulse_destroy, .stream_init = pulse_stream_init, .stream_destroy = pulse_stream_destroy, .stream_start = pulse_stream_start, .stream_stop = pulse_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = pulse_stream_get_position, .stream_get_latency = pulse_stream_get_latency, .stream_set_volume = pulse_stream_set_volume, - .stream_set_panning = pulse_stream_set_panning, .stream_get_current_device = pulse_stream_get_current_device, .stream_device_destroy = pulse_stream_device_destroy, .stream_register_device_changed_callback = NULL, |