PulseAudio has problems when the number of channels in a PCM device changes so that it is lower than it was at start-up. This happens, for example, when an HDMI sink device supports only stereo streams. ALSA assigns a default of eight channels to an HDMI output, allowing the output:hdmi-surround profile to be listed as "supported". However, in our case, later when the attached TV reports its ELD, it has only stereo channels. This invalidates the output:hdmi-surround profile as ALSA refuses to set the hardware parameters to the requested six channels. In our case the problem is highlighted by ALSA's defaults but a similar situation would arise if an HDMI sink supporting surround sound was swapping with a sink supporting only stereo. To deal with this, the ALSA module is modified to support profiles with dynamic availability. Firstly, neither profiles nor mappings are ever deleted. Secondly, we separate probing of mappings and paths from probing of profiles. The former is done once at start up and the latter can be done many times, in our case in response to changes in the ELD control. Signed-off-by: Bob Ham <bob.ham at collabora.com> --- src/modules/alsa/alsa-mixer.c | 349 +++++++++++++++--------------------- src/modules/alsa/alsa-mixer.h | 19 +- src/modules/alsa/alsa-sink.c | 13 +- src/modules/alsa/alsa-source.c | 13 +- src/modules/alsa/alsa-ucm.c | 118 ++++-------- src/modules/alsa/alsa-ucm.h | 1 + src/modules/alsa/alsa-util.c | 112 ++++++++++-- src/modules/alsa/alsa-util.h | 17 ++ src/modules/alsa/module-alsa-card.c | 93 +++++++++- src/pulsecore/card.c | 54 ++++-- src/pulsecore/card.h | 2 + 11 files changed, 432 insertions(+), 359 deletions(-) diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c index 1fe2a02..765fc9f 100644 --- a/src/modules/alsa/alsa-mixer.c +++ b/src/modules/alsa/alsa-mixer.c @@ -3031,6 +3031,16 @@ static void profile_set_add_path(pa_alsa_profile_set *ps, pa_alsa_path *path) { } } +pa_alsa_path_set *pa_alsa_path_set_new_empty(pa_alsa_direction_t direction) { + pa_alsa_path_set *ps; + + ps = pa_xnew0(pa_alsa_path_set, 1); + ps->direction = direction; + ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + return ps; +} + pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t direction, const char *paths_dir) { pa_alsa_path_set *ps; char **pn = NULL, **en = NULL, **ie; @@ -3045,9 +3055,7 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d if (m->direction != PA_ALSA_DIRECTION_ANY && m->direction != direction) return NULL; - ps = pa_xnew0(pa_alsa_path_set, 1); - ps->direction = direction; - ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + ps = pa_alsa_path_set_new_empty(direction); if (direction == PA_ALSA_DIRECTION_OUTPUT) pn = m->output_path_names; @@ -3461,8 +3469,10 @@ static void mapping_free(pa_alsa_mapping *m) { if (m->output_path_set) pa_alsa_path_set_free(m->output_path_set); - pa_assert(!m->input_pcm); - pa_assert(!m->output_pcm); + if (m->input_probe) + pa_xfree(m->input_probe); + if (m->output_probe) + pa_xfree(m->output_probe); pa_alsa_ucm_mapping_context_free(&m->ucm_context); @@ -3525,7 +3535,6 @@ pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name) m->profile_set = ps; m->exact_channels = true; m->name = pa_xstrdup(name); - pa_sample_spec_init(&m->sample_spec); pa_channel_map_init(&m->channel_map); m->proplist = pa_proplist_new(); @@ -3837,7 +3846,8 @@ static int profile_parse_skip_probe(pa_config_parser_state *state) { return -1; } - p->supported = b; + if ( (p->skip_probe = b) ) + p->available = true; return 0; } @@ -3956,38 +3966,19 @@ fail: return -1; } -static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, - pa_alsa_direction_t direction, pa_hashmap *used_paths) { +static pa_alsa_path_set *mapping_path_set_probe(pa_alsa_mapping *m, + snd_mixer_t *mixer_handle, + pa_alsa_direction_t direction, + pa_hashmap *used_paths) { + pa_alsa_path_set *ps; pa_alsa_path *p; void *state; - snd_pcm_t *pcm_handle; - pa_alsa_path_set *ps; - snd_mixer_t *mixer_handle; - - if (direction == PA_ALSA_DIRECTION_OUTPUT) { - if (m->output_path_set) - return; /* Already probed */ - m->output_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */ - pcm_handle = m->output_pcm; - } else { - if (m->input_path_set) - return; /* Already probed */ - m->input_path_set = ps = pa_alsa_path_set_new(m, direction, NULL); /* FIXME: Handle paths_dir */ - pcm_handle = m->input_pcm; - } + /* FIXME: Handle paths_dir */ + ps = pa_alsa_path_set_new(m, direction, NULL); if (!ps) - return; /* No paths */ - - pa_assert(pcm_handle); - - mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL); - if (!mixer_handle) { - /* Cannot open mixer, remove all entries */ - pa_hashmap_remove_all(ps->paths); - return; - } + return pa_alsa_path_set_new_empty(direction); PA_HASHMAP_FOREACH(p, ps->paths, state) { if (pa_alsa_path_probe(p, mixer_handle, m->profile_set->ignore_dB) < 0) { @@ -3998,14 +3989,40 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, path_set_condense(ps, mixer_handle); path_set_make_path_descriptions_unique(ps); - if (mixer_handle) - snd_mixer_close(mixer_handle); - PA_HASHMAP_FOREACH(p, ps->paths, state) pa_hashmap_put(used_paths, p, p); - pa_log_debug("Available mixer paths (after tidying):"); + pa_log_debug("Available mixer paths for mapping %s:%s (after tidying):", + direction == PA_ALSA_DIRECTION_INPUT ? "input" : "output", m->name); pa_alsa_path_set_dump(ps); + + return ps; +} + +static void mapping_paths_probe(pa_alsa_mapping *m, const char *dev_id, + int alsa_card_index, pa_hashmap *used_paths) { + + pa_assert(!m->input_path_set); + pa_assert(!m->output_path_set); + + snd_mixer_t *mixer_handle; + + mixer_handle = pa_alsa_open_mixer_by_template(m->device_strings, dev_id, NULL); + if (!mixer_handle) + mixer_handle = pa_alsa_open_mixer(alsa_card_index, NULL); + + if (!mixer_handle) { + pa_log_debug("Could not open mixer for card %i, creating empty path sets", + alsa_card_index); + m->input_path_set = pa_alsa_path_set_new_empty(PA_ALSA_DIRECTION_INPUT); + m->output_path_set = pa_alsa_path_set_new_empty(PA_ALSA_DIRECTION_OUTPUT); + return; + } + + m->input_path_set = mapping_path_set_probe(m, mixer_handle, PA_ALSA_DIRECTION_INPUT, used_paths); + m->output_path_set = mapping_path_set_probe(m, mixer_handle, PA_ALSA_DIRECTION_OUTPUT, used_paths); + + snd_mixer_close(mixer_handle); } static int mapping_verify(pa_alsa_mapping *m, const pa_channel_map *bonus) { @@ -4083,15 +4100,36 @@ void pa_alsa_mapping_dump(pa_alsa_mapping *m) { pa_assert(m); - pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, supported=%s, direction=%i", + pa_log_debug("Mapping %s (%s), priority=%u, channel_map=%s, direction=%i", m->name, pa_strnull(m->description), m->priority, pa_channel_map_snprint(cm, sizeof(cm), &m->channel_map), - pa_yes_no(m->supported), m->direction); } +void pa_alsa_mapping_pick_overrides(pa_alsa_mapping *mapping, + bool input, + pa_sample_spec *sample_spec) { + + pa_channel_map *channel_map; + + pa_assert(mapping); + pa_assert(sample_spec); + + if (mapping->sample_rate != 0) + sample_spec->rate = mapping->sample_rate; + + channel_map = input ? mapping->input_probe : mapping->output_probe; + + if (!channel_map) + return; + + sample_spec->channels = channel_map->channels; + if (pa_channel_map_valid(channel_map)) + pa_assert(pa_channel_map_compatible(channel_map, sample_spec)); +} + static void profile_set_add_auto_pair( pa_alsa_profile_set *ps, pa_alsa_mapping *m, /* output */ @@ -4208,9 +4246,6 @@ static int profile_verify(pa_alsa_profile *p) { } pa_idxset_put(p->output_mappings, m, NULL); - - if (p->supported) - m->supported++; } pa_xstrfreev(p->output_mapping_names); @@ -4244,9 +4279,6 @@ static int profile_verify(pa_alsa_profile *p) { } pa_idxset_put(p->input_mappings, m, NULL); - - if (p->supported) - m->supported++; } pa_xstrfreev(p->input_mapping_names); @@ -4297,13 +4329,13 @@ void pa_alsa_profile_dump(pa_alsa_profile *p) { pa_alsa_mapping *m; pa_assert(p); - pa_log_debug("Profile %s (%s), input=%s, output=%s priority=%u, supported=%s n_input_mappings=%u, n_output_mappings=%u", + pa_log_debug("Profile %s (%s), input=%s, output=%s priority=%u, available=%s n_input_mappings=%u, n_output_mappings=%u", p->name, pa_strnull(p->description), pa_strnull(p->input_name), pa_strnull(p->output_name), p->priority, - pa_yes_no(p->supported), + pa_yes_no(p->available), p->input_mappings ? pa_idxset_size(p->input_mappings) : 0, p->output_mappings ? pa_idxset_size(p->output_mappings) : 0); @@ -4439,83 +4471,26 @@ fail: return NULL; } -static void profile_finalize_probing(pa_alsa_profile *to_be_finalized, pa_alsa_profile *next) { - pa_alsa_mapping *m; - uint32_t idx; - - if (!to_be_finalized) - return; - - if (to_be_finalized->output_mappings) - PA_IDXSET_FOREACH(m, to_be_finalized->output_mappings, idx) { - - if (!m->output_pcm) - continue; +static void paths_drop_unused(pa_hashmap* h, pa_hashmap *keep); - if (to_be_finalized->supported) - m->supported++; - - /* If this mapping is also in the next profile, we won't close the - * pcm handle here, because it would get immediately reopened - * anyway. */ - if (next && next->output_mappings && pa_idxset_get_by_data(next->output_mappings, m, NULL)) - continue; - - snd_pcm_close(m->output_pcm); - m->output_pcm = NULL; - } - - if (to_be_finalized->input_mappings) - PA_IDXSET_FOREACH(m, to_be_finalized->input_mappings, idx) { - - if (!m->input_pcm) - continue; - - if (to_be_finalized->supported) - m->supported++; - - /* If this mapping is also in the next profile, we won't close the - * pcm handle here, because it would get immediately reopened - * anyway. */ - if (next && next->input_mappings && pa_idxset_get_by_data(next->input_mappings, m, NULL)) - continue; - - snd_pcm_close(m->input_pcm); - m->input_pcm = NULL; - } -} - -static snd_pcm_t* mapping_open_pcm(pa_alsa_mapping *m, - const pa_sample_spec *ss, - const char *dev_id, - bool exact_channels, - int mode, - unsigned default_n_fragments, - unsigned default_fragment_size_msec) { +/* This is called once on start up */ +void pa_alsa_profile_set_probe_paths(pa_alsa_profile_set *ps, const char *dev_id, int alsa_card_index) { + pa_hashmap *used_paths; + pa_alsa_mapping *m; + void *state; - snd_pcm_t* handle; - pa_sample_spec try_ss = *ss; - pa_channel_map try_map = m->channel_map; - snd_pcm_uframes_t try_period_size, try_buffer_size; + pa_assert(ps); + pa_assert(dev_id); - try_ss.channels = try_map.channels; + used_paths = pa_hashmap_new(pa_idxset_trivial_hash_func, + pa_idxset_trivial_compare_func); - try_period_size = - pa_usec_to_bytes(default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / - pa_frame_size(&try_ss); - try_buffer_size = default_n_fragments * try_period_size; + PA_HASHMAP_FOREACH(m, ps->mappings, state) + mapping_paths_probe(m, dev_id, alsa_card_index, used_paths); - handle = pa_alsa_open_by_template( - m->device_strings, dev_id, NULL, &try_ss, - &try_map, mode, &try_period_size, - &try_buffer_size, 0, NULL, NULL, exact_channels); - if (handle && !exact_channels && m->channel_map.channels != try_map.channels) { - char buf[PA_CHANNEL_MAP_SNPRINT_MAX]; - pa_log_debug("Channel map for mapping '%s' permanently changed to '%s'", m->name, - pa_channel_map_snprint(buf, sizeof(buf), &try_map)); - m->channel_map = try_map; - } - return handle; + paths_drop_unused(ps->input_paths, used_paths); + paths_drop_unused(ps->output_paths, used_paths); + pa_hashmap_free(used_paths); } static void paths_drop_unused(pa_hashmap* h, pa_hashmap *keep) { @@ -4553,30 +4528,43 @@ static int add_profiles_to_probe( return i; } -void pa_alsa_profile_set_probe( +void pa_alsa_profile_set_clean_probes(pa_alsa_profile_set *ps) { + pa_alsa_mapping *m; + void *state; + + PA_HASHMAP_FOREACH(m, ps->mappings, state) { + if (m->input_probe) { + pa_xfree(m->input_probe); + m->input_probe = NULL; + } + if (m->output_probe) { + pa_xfree(m->output_probe); + m->output_probe = NULL; + } + } +} + +/* This is called many times to reassess profiles */ +void pa_alsa_profile_set_probe_profiles( + pa_core *core, pa_alsa_profile_set *ps, - const char *dev_id, - const pa_sample_spec *ss, - unsigned default_n_fragments, - unsigned default_fragment_size_msec) { + const char *dev_id) { bool found_output = false, found_input = false; - pa_alsa_profile *p, *last = NULL; + pa_alsa_profile *p; pa_alsa_profile **pp, **probe_order; pa_alsa_mapping *m; - pa_hashmap *broken_inputs, *broken_outputs, *used_paths; + pa_hashmap *broken_inputs, *broken_outputs; + pa_assert(core); pa_assert(ps); pa_assert(dev_id); - pa_assert(ss); - if (ps->probed) - return; + pa_alsa_profile_set_clean_probes(ps); broken_inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); broken_outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - used_paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); pp = probe_order = pa_xnew0(pa_alsa_profile *, pa_hashmap_size(ps->profiles) + 1); pp += add_profiles_to_probe(pp, ps->profiles, false, false); @@ -4589,53 +4577,53 @@ void pa_alsa_profile_set_probe( p = *pp; /* Skip if fallback and already found something */ - if (found_input && p->fallback_input) - continue; - if (found_output && p->fallback_output) + if ((found_input && p->fallback_input) + || (found_output && p->fallback_output)) { + p->available = false; continue; + } - /* Skip if this is already marked that it is supported (i.e. from the config file) */ - if (!p->supported) { + /* Skip if this is marked in the config file to assume it's available */ + if (!p->skip_probe) { - profile_finalize_probing(last, p); - p->supported = true; + p->available = true; if (p->output_mappings) { PA_IDXSET_FOREACH(m, p->output_mappings, idx) { if (pa_hashmap_get(broken_outputs, m) == m) { pa_log_debug("Skipping profile %s - will not be able to open output:%s", p->name, m->name); - p->supported = false; + p->available = false; break; } } } - if (p->input_mappings && p->supported) { + if (p->input_mappings && p->available) { PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (pa_hashmap_get(broken_inputs, m) == m) { pa_log_debug("Skipping profile %s - will not be able to open input:%s", p->name, m->name); - p->supported = false; + p->available = false; break; } } } - if (p->supported) + if (p->available) pa_log_debug("Looking at profile %s", p->name); /* Check if we can open all new ones */ - if (p->output_mappings && p->supported) + if (p->output_mappings && p->available) PA_IDXSET_FOREACH(m, p->output_mappings, idx) { - if (m->output_pcm) + if (m->output_probe) continue; pa_log_debug("Checking for playback on %s (%s)", m->description, m->name); - if (!(m->output_pcm = mapping_open_pcm(m, ss, dev_id, m->exact_channels, - SND_PCM_STREAM_PLAYBACK, - default_n_fragments, - default_fragment_size_msec))) { - p->supported = false; + if (!(m->output_probe = pa_alsa_pcm_probe(core, &m->channel_map, + m->device_strings, dev_id, + SND_PCM_STREAM_PLAYBACK, + m->exact_channels))) { + p->available = false; if (pa_idxset_size(p->output_mappings) == 1 && ((!p->input_mappings) || pa_idxset_size(p->input_mappings) == 0)) { pa_log_debug("Caching failure to open output:%s", m->name); @@ -4645,18 +4633,18 @@ void pa_alsa_profile_set_probe( } } - if (p->input_mappings && p->supported) + if (p->input_mappings && p->available) PA_IDXSET_FOREACH(m, p->input_mappings, idx) { - if (m->input_pcm) + if (m->input_probe) continue; pa_log_debug("Checking for recording on %s (%s)", m->description, m->name); - if (!(m->input_pcm = mapping_open_pcm(m, ss, dev_id, m->exact_channels, - SND_PCM_STREAM_CAPTURE, - default_n_fragments, - default_fragment_size_msec))) { - p->supported = false; + if (!(m->input_probe = pa_alsa_pcm_probe(core, &m->channel_map, + m->device_strings, dev_id, + SND_PCM_STREAM_CAPTURE, + m->exact_channels))) { + p->available = false; if (pa_idxset_size(p->input_mappings) == 1 && ((!p->output_mappings) || pa_idxset_size(p->output_mappings) == 0)) { pa_log_debug("Caching failure to open input:%s", m->name); @@ -4666,42 +4654,16 @@ void pa_alsa_profile_set_probe( } } - last = p; - - if (!p->supported) - continue; } - pa_log_debug("Profile %s supported.", p->name); - - if (p->output_mappings) - PA_IDXSET_FOREACH(m, p->output_mappings, idx) - if (m->output_pcm) { - found_output |= !p->fallback_output; - mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths); - } - - if (p->input_mappings) - PA_IDXSET_FOREACH(m, p->input_mappings, idx) - if (m->input_pcm) { - found_input |= !p->fallback_input; - mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths); - } + if (p->available) + pa_log_debug("Profile %s available.", p->name); } /* Clean up */ - profile_finalize_probing(last, NULL); - - pa_alsa_profile_set_drop_unsupported(ps); - - paths_drop_unused(ps->input_paths, used_paths); - paths_drop_unused(ps->output_paths, used_paths); pa_hashmap_free(broken_inputs); pa_hashmap_free(broken_outputs); - pa_hashmap_free(used_paths); pa_xfree(probe_order); - - ps->probed = true; } void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) { @@ -4712,11 +4674,10 @@ void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) { pa_assert(ps); - pa_log_debug("Profile set %p, auto_profiles=%s, probed=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u", + pa_log_debug("Profile set %p, auto_profiles=%s, n_mappings=%u, n_profiles=%u, n_decibel_fixes=%u", (void*) ps, pa_yes_no(ps->auto_profiles), - pa_yes_no(ps->probed), pa_hashmap_size(ps->mappings), pa_hashmap_size(ps->profiles), pa_hashmap_size(ps->decibel_fixes)); @@ -4731,22 +4692,6 @@ void pa_alsa_profile_set_dump(pa_alsa_profile_set *ps) { pa_alsa_decibel_fix_dump(db_fix); } -void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *ps) { - pa_alsa_profile *p; - pa_alsa_mapping *m; - void *state; - - PA_HASHMAP_FOREACH(p, ps->profiles, state) { - if (!p->supported) - pa_hashmap_remove_and_free(ps->profiles, p->name); - } - - PA_HASHMAP_FOREACH(m, ps->mappings, state) { - if (m->supported <= 0) - pa_hashmap_remove_and_free(ps->mappings, m->name); - } -} - static pa_device_port* device_port_alsa_init(pa_hashmap *ports, /* card ports */ const char* name, const char* description, diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h index 4ebf192..78f234d 100644 --- a/src/modules/alsa/alsa-mixer.h +++ b/src/modules/alsa/alsa-mixer.h @@ -233,6 +233,7 @@ void pa_alsa_jack_dump(pa_alsa_jack *j); void pa_alsa_element_dump(pa_alsa_element *e); pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction); +pa_alsa_path_set *pa_alsa_path_set_new_empty(pa_alsa_direction_t direction); pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction); int pa_alsa_path_probe(pa_alsa_path *p, snd_mixer_t *m, bool ignore_dB); void pa_alsa_path_dump(pa_alsa_path *p); @@ -259,7 +260,7 @@ struct pa_alsa_mapping { /* These are copied over to the resultant sink/source */ pa_proplist *proplist; - pa_sample_spec sample_spec; + uint32_t sample_rate; pa_channel_map channel_map; char **device_strings; @@ -271,13 +272,11 @@ struct pa_alsa_mapping { pa_alsa_path_set *input_path_set; pa_alsa_path_set *output_path_set; - unsigned supported; bool exact_channels:1; bool fallback:1; - /* Temporarily used during probing */ - snd_pcm_t *input_pcm; - snd_pcm_t *output_pcm; + pa_channel_map *input_probe; + pa_channel_map *output_probe; pa_sink *sink; pa_source *source; @@ -296,7 +295,8 @@ struct pa_alsa_profile { char *input_name; char *output_name; - bool supported:1; + bool skip_probe:1; + bool available:1; bool fallback_input:1; bool fallback_output:1; @@ -332,19 +332,20 @@ struct pa_alsa_profile_set { bool auto_profiles; bool ignore_dB:1; - bool probed:1; }; void pa_alsa_mapping_dump(pa_alsa_mapping *m); void pa_alsa_profile_dump(pa_alsa_profile *p); void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix); pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name); +void pa_alsa_mapping_pick_overrides(pa_alsa_mapping *mapping, bool input, pa_sample_spec *sample_spec); pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus); -void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); +void pa_alsa_profile_set_probe_paths(pa_alsa_profile_set *ps, const char *dev_id, int alsa_card_index); +void pa_alsa_profile_set_probe_profiles(pa_core *core, pa_alsa_profile_set *ps, const char *dev_id); +void pa_alsa_profile_set_clean_probes(pa_alsa_profile_set *ps); void pa_alsa_profile_set_free(pa_alsa_profile_set *s); void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); -void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s); snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device); diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index 2fdebe0..2f0e0e5 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -2024,17 +2024,8 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca map = m->core->default_channel_map; /* Pick sample spec overrides from the mapping, if any */ - if (mapping) { - if (mapping->sample_spec.format != PA_SAMPLE_INVALID) - ss.format = mapping->sample_spec.format; - if (mapping->sample_spec.rate != 0) - ss.rate = mapping->sample_spec.rate; - if (mapping->sample_spec.channels != 0) { - ss.channels = mapping->sample_spec.channels; - if (pa_channel_map_valid(&mapping->channel_map)) - pa_assert(pa_channel_map_compatible(&mapping->channel_map, &ss)); - } - } + if (mapping) + pa_alsa_mapping_pick_overrides(mapping, false, &ss); /* Override with modargs if provided */ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 4683dfe..8138d56 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -1739,17 +1739,8 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p map = m->core->default_channel_map; /* Pick sample spec overrides from the mapping, if any */ - if (mapping) { - if (mapping->sample_spec.format != PA_SAMPLE_INVALID) - ss.format = mapping->sample_spec.format; - if (mapping->sample_spec.rate != 0) - ss.rate = mapping->sample_spec.rate; - if (mapping->sample_spec.channels != 0) { - ss.channels = mapping->sample_spec.channels; - if (pa_channel_map_valid(&mapping->channel_map)) - pa_assert(pa_channel_map_compatible(&mapping->channel_map, &ss)); - } - } + if (mapping) + pa_alsa_mapping_pick_overrides(mapping, true, &ss); /* Override with modargs if provided */ if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c index 42f3242..590f2c3 100644 --- a/src/modules/alsa/alsa-ucm.c +++ b/src/modules/alsa/alsa-ucm.c @@ -1205,7 +1205,7 @@ static int ucm_create_mapping_direction( ucm_add_mapping(p, m); if (rate) - m->sample_spec.rate = rate; + m->sample_rate = rate; pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); } @@ -1361,7 +1361,7 @@ static int ucm_create_profile( p->output_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); p->input_mappings = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - p->supported = true; + p->available = true; pa_hashmap_put(ps->profiles, p->name, p); /* TODO: get profile priority from ucm info or policy management */ @@ -1448,69 +1448,13 @@ static int ucm_create_profile( return 0; } -static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { - snd_pcm_t* pcm; - pa_sample_spec try_ss = ucm->core->default_sample_spec; - pa_channel_map try_map; - snd_pcm_uframes_t try_period_size, try_buffer_size; - bool exact_channels = m->channel_map.channels > 0; - - if (exact_channels) { - try_map = m->channel_map; - try_ss.channels = try_map.channels; - } else - pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA); - - try_period_size = - pa_usec_to_bytes(ucm->core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / - pa_frame_size(&try_ss); - try_buffer_size = ucm->core->default_n_fragments * try_period_size; - - pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss, - &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels); - - if (pcm && !exact_channels) - m->channel_map = try_map; - - return pcm; -} - -static void profile_finalize_probing(pa_alsa_profile *p) { - pa_alsa_mapping *m; - uint32_t idx; - - PA_IDXSET_FOREACH(m, p->output_mappings, idx) { - if (p->supported) - m->supported++; - - if (!m->output_pcm) - continue; - - snd_pcm_close(m->output_pcm); - m->output_pcm = NULL; - } - - PA_IDXSET_FOREACH(m, p->input_mappings, idx) { - if (p->supported) - m->supported++; - - if (!m->input_pcm) - continue; - - snd_pcm_close(m->input_pcm); - m->input_pcm = NULL; - } -} - static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { - snd_pcm_t *pcm_handle; snd_mixer_t *mixer_handle; pa_alsa_ucm_mapping_context *context = &m->ucm_context; pa_alsa_ucm_device *dev; uint32_t idx; - pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm; - mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL); + mixer_handle = pa_alsa_open_mixer_by_device_string(m->device_strings[0], NULL); if (!mixer_handle) return; @@ -1525,7 +1469,22 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { snd_mixer_close(mixer_handle); } -static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { +static void profile_set_probe_jacks(pa_alsa_profile_set *ps) { + pa_alsa_mapping *m; + void *state; + + PA_HASHMAP_FOREACH(m, ps->mappings, state) + if (!PA_UCM_IS_MODIFIER_MAPPING(m)) + ucm_mapping_jack_probe(m); +} + +static pa_channel_map* ucm_mapping_probe(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { + char *template[] = { m->device_strings[0], NULL }; + return pa_alsa_pcm_probe(ucm->core, &m->channel_map, template, "", mode, + m->channel_map.channels > 0); +} + +void pa_alsa_ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { void *state; pa_alsa_profile *p; pa_alsa_mapping *m; @@ -1537,7 +1496,7 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) { pa_log("Failed to set verb %s", p->name); - p->supported = false; + p->available = false; continue; } @@ -1548,14 +1507,14 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * continue; } - m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK); - if (!m->output_pcm) { - p->supported = false; + m->output_probe = ucm_mapping_probe(ucm, m, SND_PCM_STREAM_PLAYBACK); + if (!m->output_probe) { + p->available = false; break; } } - if (p->supported) { + if (p->available) { PA_IDXSET_FOREACH(m, p->input_mappings, idx) { if (PA_UCM_IS_MODIFIER_MAPPING(m)) { /* Skip jack probing on modifier PCMs since we expect this to @@ -1563,36 +1522,20 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * continue; } - m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE); - if (!m->input_pcm) { - p->supported = false; + m->input_probe = ucm_mapping_probe(ucm, m, SND_PCM_STREAM_CAPTURE); + if (!m->input_probe) { + p->available = false; break; } } } - if (!p->supported) { - profile_finalize_probing(p); - continue; - } - - pa_log_debug("Profile %s supported.", p->name); - - PA_IDXSET_FOREACH(m, p->output_mappings, idx) - if (!PA_UCM_IS_MODIFIER_MAPPING(m)) - ucm_mapping_jack_probe(m); - - PA_IDXSET_FOREACH(m, p->input_mappings, idx) - if (!PA_UCM_IS_MODIFIER_MAPPING(m)) - ucm_mapping_jack_probe(m); - - profile_finalize_probing(p); + if (p->available) + pa_log_debug("Profile %s supported.", p->name); } /* restore ucm state */ snd_use_case_set(ucm->ucm_mgr, "_verb", SND_USE_CASE_VERB_INACTIVE); - - pa_alsa_profile_set_drop_unsupported(ps); } pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) { @@ -1619,8 +1562,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha ucm_create_profile(ucm, ps, verb, verb_name, verb_desc); } - ucm_probe_profile_set(ucm, ps); - ps->probed = true; + profile_set_probe_jacks(ps); return ps; } diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h index 0930303..773d44b 100644 --- a/src/modules/alsa/alsa-ucm.h +++ b/src/modules/alsa/alsa-ucm.h @@ -98,6 +98,7 @@ typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context; int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index); pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); +void pa_alsa_ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps); int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile); int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index a4bb449..adf6bb9 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -1591,33 +1591,16 @@ static int prepare_mixer(snd_mixer_t *mixer, const char *dev) { } snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device) { - int err; snd_mixer_t *m; char *md; - snd_pcm_info_t* info; - snd_pcm_info_alloca(&info); - - if ((err = snd_mixer_open(&m, 0)) < 0) { - pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); - return NULL; - } - /* Then, try by card index */ md = pa_sprintf_malloc("hw:%i", alsa_card_index); - if (prepare_mixer(m, md) >= 0) { - - if (ctl_device) - *ctl_device = md; - else - pa_xfree(md); - return m; - } + m = pa_alsa_open_mixer_by_device_string(md, ctl_device); pa_xfree(md); - snd_mixer_close(m); - return NULL; + return m; } snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { @@ -1671,6 +1654,65 @@ snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { return NULL; } +snd_mixer_t *pa_alsa_open_mixer_by_device_string(const char *device, char **dev) { + int err; + snd_mixer_t *mixer; + + if ((err = snd_mixer_open(&mixer, 0)) < 0) { + pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); + return NULL; + } + + if (prepare_mixer(mixer, device) >= 0) { + if (dev) + *dev = pa_xstrdup(device); + + return mixer; + } + + /* We need to behave in the same way as pa_alsa_open_by_device_string */ + if (!pa_startswith(device, "plug:") && !pa_startswith(device, "plughw:")) { + char *t; + + t = pa_sprintf_malloc("plug:%s", device); + + err = prepare_mixer(mixer, t); + + if (dev && err >= 0) + *dev = t; + else + pa_xfree(t); + + if (err >= 0) + return mixer; + } + + snd_mixer_close(mixer); + return NULL; +} + +snd_mixer_t *pa_alsa_open_mixer_by_template(char **template, const char *dev_id, + char **dev) { + + char **i; + snd_mixer_t *mixer; + + for (i = template; *i; i++) { + char *d; + + d = pa_replace(*i, "%f", dev_id); + + mixer = pa_alsa_open_mixer_by_device_string(d, dev); + + pa_xfree(d); + + if (mixer) + return mixer; + } + + return NULL; +} + int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { /* The ELD format is specific to HDA Intel sound cards and defined in the @@ -1719,3 +1761,35 @@ int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { return 0; } + +pa_channel_map* pa_alsa_pcm_probe(pa_core *core, + pa_channel_map *map, + char **template, + const char *dev_id, + snd_pcm_stream_t mode, + bool exact_channels) { + + snd_pcm_t* handle; + pa_sample_spec try_ss = core->default_sample_spec; + pa_channel_map try_map = *map; + snd_pcm_uframes_t try_period_size, try_buffer_size; + + try_ss.channels = try_map.channels; + + try_period_size = + pa_usec_to_bytes(core->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) + / pa_frame_size(&try_ss); + try_buffer_size = core->default_n_fragments * try_period_size; + + handle = pa_alsa_open_by_template( + template, dev_id, NULL, &try_ss, + &try_map, mode, &try_period_size, + &try_buffer_size, 0, NULL, NULL, exact_channels); + + if (!handle) + return NULL; + + snd_pcm_close(handle); + + return pa_xmemdup(&try_map, sizeof(pa_channel_map)); +} diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 8345a0b..f35c821 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -144,6 +144,15 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device); +snd_mixer_t *pa_alsa_open_mixer_by_device_string( + const char *device, + char **dev); /* modified at return */ + +snd_mixer_t *pa_alsa_open_mixer_by_template( + char **template, + const char *dev_id, + char **dev); /* modified at return */ + typedef struct pa_hdmi_eld pa_hdmi_eld; struct pa_hdmi_eld { char monitor_name[17]; @@ -151,4 +160,12 @@ struct pa_hdmi_eld { int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld); +pa_channel_map* pa_alsa_pcm_probe(pa_core *core, + pa_channel_map *map, + char **template, + const char *dev_id, + snd_pcm_stream_t mode, + bool exact_channels); + + #endif diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c index 286cfc9..5a8b1ca 100644 --- a/src/modules/alsa/module-alsa-card.c +++ b/src/modules/alsa/module-alsa-card.c @@ -143,6 +143,7 @@ static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) { cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data)); cp->priority = ap->priority; + cp->available = ap->available ? PA_AVAILABLE_YES : PA_AVAILABLE_NO; cp->input_name = pa_xstrdup(ap->input_name); cp->output_name = pa_xstrdup(ap->output_name); @@ -428,6 +429,80 @@ static pa_device_port* find_port_with_eld_device(pa_hashmap *ports, int device) return NULL; } +static void suspend_mappings(pa_idxset *mappings, bool suspend) { + pa_alsa_mapping *am; + uint32_t idx; + + if (!mappings) + return; + + PA_IDXSET_FOREACH(am, mappings, idx) { + if (!am->sink) + continue; + + if (pa_sink_suspend(am->sink, suspend, PA_SUSPEND_INTERNAL)) + pa_log_warn("Could not %ssuspend sink '%s' for mapping '%s'", + suspend ? "" : "un", am->sink->name, am->name); + } +} + +static void probe_profile_set(struct userdata *u) { + if (u->use_ucm) + pa_alsa_ucm_probe_profile_set(&u->ucm, u->profile_set); + else + pa_alsa_profile_set_probe_profiles(u->core, u->profile_set, u->device_id); +} + +static void reassess_profiles(struct userdata *u) { + pa_core *core; + pa_card *c; + struct profile_data *current; + pa_card_profile *cp; + void *state; + + pa_assert(core = u->core); + pa_assert(c = u->card); + + /* Suspend the current profile's sinks so we can re-probe */ + current = PA_CARD_PROFILE_DATA(c->active_profile); + pa_log_debug("Suspending profile '%s' to re-probe", current->profile->name); + suspend_mappings(current->profile->input_mappings, true); + suspend_mappings(current->profile->output_mappings, true); + + /* Re-probe all profiles */ + pa_log_debug("Re-probing all profiles for card '%s'", c->name); + pa_alsa_profile_set_clean_probes(u->profile_set); + probe_profile_set(u); + pa_alsa_profile_set_dump(u->profile_set); + + /* Update each pa_card_profile's availability from its pa_alsa_profile */ + PA_HASHMAP_FOREACH(cp, c->profiles, state) { + struct profile_data *d; + + d = PA_CARD_PROFILE_DATA(cp); + if (d->profile) + pa_card_profile_set_available(cp, d->profile->available ? PA_AVAILABLE_YES + : PA_AVAILABLE_NO); + } + + /* Unsuspend if the current profile is still available, + * otherwise pick a new profile */ + if (current->profile->available) { + pa_log_debug("Profile '%s' still available, unsuspending", current->profile->name); + suspend_mappings(current->profile->output_mappings, false); + suspend_mappings(current->profile->input_mappings, false); + } else { + pa_card_profile *new_profile; + + new_profile = pa_card_pick_profile(c); + pa_card_set_profile(c, new_profile, false); + + pa_log_debug("Picked new profile '%s' to replace old, unavailable profile '%s'", + new_profile->name, current->profile->name); + } +} + + static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { struct userdata *u = snd_mixer_elem_get_callback_private(melem); snd_hctl_elem_t *elem = snd_mixer_elem_get_private(melem); @@ -440,6 +515,8 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { if (mask == SND_CTL_EVENT_MASK_REMOVE) return 0; + pa_log_debug("ELD changed on ALSA card '%s'", u->card->name); + p = find_port_with_eld_device(u->card->ports, device); if (p == NULL) { pa_log_error("Invalid device changed in ALSA: %d", device); @@ -458,8 +535,13 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { pa_proplist_sets(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME, eld.monitor_name); } - if (changed && mask != 0) - pa_subscription_post(u->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, u->card->index); + if (mask != 0) { + if (changed) + pa_subscription_post(u->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, u->card->index); + + // Reassess availability of profiles as channel count may have changed + reassess_profiles(u); + } return 0; } @@ -489,6 +571,7 @@ static void init_eld_ctls(struct userdata *u) { melem = pa_alsa_mixer_find(u->mixer_handle, "ELD", device); if (melem) { + pa_log_debug("ELD device found for port %s, setting callback.", port->name); snd_mixer_elem_set_callback(melem, hdmi_eld_changed); snd_mixer_elem_set_callback_private(melem, u); hdmi_eld_changed(melem, 0); @@ -718,6 +801,10 @@ int pa__init(pa_module *m) { u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map); pa_xfree(fn); + + if (u->profile_set) + pa_alsa_profile_set_probe_paths(u->profile_set, u->device_id, + u->alsa_card_index); } if (!u->profile_set) @@ -725,7 +812,7 @@ int pa__init(pa_module *m) { u->profile_set->ignore_dB = ignore_dB; - pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); + probe_profile_set(u); pa_alsa_profile_set_dump(u->profile_set); pa_card_new_data_init(&data); diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c index b6cbbf7..ad956d1 100644 --- a/src/pulsecore/card.c +++ b/src/pulsecore/card.c @@ -119,6 +119,35 @@ void pa_card_new_data_done(pa_card_new_data *data) { pa_xfree(data->active_profile); } +static pa_card_profile *pick_profile(pa_hashmap *profiles, bool availability_check) { + pa_card_profile *profile; + pa_card_profile *picked = NULL; + void *state; + + PA_HASHMAP_FOREACH(profile, profiles, state) { + if (availability_check && profile->available == PA_AVAILABLE_NO) + continue; + + if (!picked || profile->priority > picked->priority) + picked = profile; + } + + return picked; +} + +pa_card_profile *pa_card_pick_profile(pa_card *c) { + pa_card_profile *picked; + + picked = pick_profile(c->profiles, true); + + /* If all profiles are not available, then we still need to pick one */ + if (!picked) + picked = pick_profile(c->profiles, false); + + pa_assert(picked); + return picked; +} + pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) { pa_card *c; const char *name; @@ -173,22 +202,8 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) { if ((c->active_profile = pa_hashmap_get(c->profiles, data->active_profile))) c->save_profile = data->save_profile; - if (!c->active_profile) { - PA_HASHMAP_FOREACH(profile, c->profiles, state) { - if (profile->available == PA_AVAILABLE_NO) - continue; - - if (!c->active_profile || profile->priority > c->active_profile->priority) - c->active_profile = profile; - } - /* If all profiles are not available, then we still need to pick one */ - if (!c->active_profile) { - PA_HASHMAP_FOREACH(profile, c->profiles, state) - if (!c->active_profile || profile->priority > c->active_profile->priority) - c->active_profile = profile; - } - pa_assert(c->active_profile); - } + if (!c->active_profile) + c->active_profile = pa_card_pick_profile(c); pa_device_init_description(c->proplist, c); pa_device_init_icon(c->proplist, true); @@ -283,6 +298,13 @@ int pa_card_set_profile(pa_card *c, pa_card_profile *profile, bool save) { return -PA_ERR_NOTIMPLEMENTED; } + if (profile->available == PA_AVAILABLE_NO) { + pa_log_warn("Profile '%s' is not available", profile->name); + return -PA_ERR_INVALID; + } + pa_log_debug("Setting card '%s' profile to available profile '%s'", + c->name, profile->name); + if (c->active_profile == profile) { if (save && !c->save_profile) { update_port_preferred_profile(c); diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h index 30bfc0e..6d9c650 100644 --- a/src/pulsecore/card.h +++ b/src/pulsecore/card.h @@ -107,6 +107,8 @@ typedef struct pa_card_new_data { pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra); void pa_card_profile_free(pa_card_profile *c); +pa_card_profile *pa_card_pick_profile(pa_card *c); + /* The profile's available status has changed */ void pa_card_profile_set_available(pa_card_profile *c, pa_available_t available); -- 2.1.4