diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index d87336120..8509d8ac4 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -904,6 +904,8 @@ static int build_pollfd(struct userdata *u) { /* Called from IO context */ static int suspend(struct userdata *u) { + const char *mod_name; + pa_assert(u); pa_assert(u->pcm_handle); @@ -914,6 +916,13 @@ static int suspend(struct userdata *u) { snd_pcm_close(u->pcm_handle); u->pcm_handle = NULL; + if ((mod_name = pa_proplist_gets(u->sink->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + pa_log_info("Disable ucm modifier %s", mod_name); + + if (snd_use_case_set(u->ucm_context->ucm->ucm_mgr, "_dismod", mod_name) < 0) + pa_log("Failed to disable ucm modifier %s", mod_name); + } + if (u->alsa_rtpoll_item) { pa_rtpoll_item_free(u->alsa_rtpoll_item); u->alsa_rtpoll_item = NULL; @@ -1036,12 +1045,20 @@ static int unsuspend(struct userdata *u) { pa_bool_t b, d; snd_pcm_uframes_t period_size, buffer_size; char *device_name = NULL; + const char *mod_name; pa_assert(u); pa_assert(!u->pcm_handle); pa_log_info("Trying resume..."); + if ((mod_name = pa_proplist_gets(u->sink->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + pa_log_info("Enable ucm modifier %s", mod_name); + + if (snd_use_case_set(u->ucm_context->ucm->ucm_mgr, "_enamod", mod_name) < 0) + pa_log("Failed to enable ucm modifier %s", mod_name); + } + if ((is_iec958(u) || is_hdmi(u)) && pa_sink_is_passthrough(u->sink)) { /* Need to open device in NONAUDIO mode */ int len = strlen(u->device_name) + 8; @@ -1977,7 +1994,7 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) { struct userdata *u = NULL; - const char *dev_id = NULL, *key; + const char *dev_id = NULL, *key, *mod_name; pa_sample_spec ss; uint32_t alternate_sample_rate; pa_channel_map map; @@ -2110,6 +2127,13 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca goto fail; } + if ((mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + if (snd_use_case_set(u->ucm_context->ucm->ucm_mgr, "_enamod", mod_name) < 0) + pa_log("Failed to enable ucm modifier %s", mod_name); + else + pa_log_debug("Enabled ucm modifier %s", mod_name); + } + if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping( dev_id, &u->device_name, diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index b544a82cb..295789277 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -836,6 +836,8 @@ static int build_pollfd(struct userdata *u) { /* Called from IO context */ static int suspend(struct userdata *u) { + const char *mod_name; + pa_assert(u); pa_assert(u->pcm_handle); @@ -845,6 +847,13 @@ static int suspend(struct userdata *u) { snd_pcm_close(u->pcm_handle); u->pcm_handle = NULL; + if ((mod_name = pa_proplist_gets(u->source->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + pa_log_info("Disable ucm modifier %s", mod_name); + + if (snd_use_case_set(u->ucm_context->ucm->ucm_mgr, "_dismod", mod_name) < 0) + pa_log("Failed to disable ucm modifier %s", mod_name); + } + if (u->alsa_rtpoll_item) { pa_rtpoll_item_free(u->alsa_rtpoll_item); u->alsa_rtpoll_item = NULL; @@ -949,12 +958,20 @@ static int unsuspend(struct userdata *u) { int err; pa_bool_t b, d; snd_pcm_uframes_t period_size, buffer_size; + const char *mod_name; pa_assert(u); pa_assert(!u->pcm_handle); pa_log_info("Trying resume..."); + if ((mod_name = pa_proplist_gets(u->source->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + pa_log_info("Enable ucm modifier %s", mod_name); + + if (snd_use_case_set(u->ucm_context->ucm->ucm_mgr, "_enamod", mod_name) < 0) + pa_log("Failed to enable ucm modifier %s", mod_name); + } + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK| SND_PCM_NO_AUTO_RESAMPLE| @@ -1714,7 +1731,7 @@ static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) { pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) { struct userdata *u = NULL; - const char *dev_id = NULL, *key; + const char *dev_id = NULL, *key, *mod_name; pa_sample_spec ss; uint32_t alternate_sample_rate; pa_channel_map map; @@ -1840,6 +1857,13 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p goto fail; } + if ((mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { + if (snd_use_case_set(u->ucm_context->ucm->ucm_mgr, "_enamod", mod_name) < 0) + pa_log("Failed to enable ucm modifier %s", mod_name); + else + pa_log_debug("Enabled ucm modifier %s", mod_name); + } + if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping( dev_id, &u->device_name, diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c index 0a63bff29..87ec29348 100644 --- a/src/modules/alsa/alsa-ucm.c +++ b/src/modules/alsa/alsa-ucm.c @@ -64,6 +64,7 @@ if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device)) (device)->playback_priority = (priority); \ if (PA_UCM_CAPTURE_PRIORITY_UNSET(device)) (device)->capture_priority = (priority); \ } while (0) +#define PA_UCM_IS_MODIFIER_MAPPING(m) ((pa_proplist_gets((m)->proplist, PA_ALSA_PROP_UCM_MODIFIER)) != NULL) struct ucm_items { const char *id; @@ -1033,6 +1034,50 @@ static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device * device->capture_mapping = m; } +static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) { + char *cur_desc; + const char *new_desc, *mod_name, *channel_str; + uint32_t channels = 0; + + pa_idxset_put(m->ucm_context.ucm_modifiers, modifier, NULL); + + new_desc = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_DESCRIPTION); + cur_desc = m->description; + if (cur_desc) + m->description = pa_sprintf_malloc("%s + %s", cur_desc, new_desc); + else + m->description = pa_xstrdup(new_desc); + pa_xfree(cur_desc); + + if (!m->description) + pa_xstrdup(""); + + /* Modifier sinks should not be routed to by default */ + m->priority = 0; + + mod_name = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_NAME); + pa_proplist_sets(m->proplist, PA_ALSA_PROP_UCM_MODIFIER, mod_name); + + /* save mapping to ucm modifier */ + if (m->direction == PA_ALSA_DIRECTION_OUTPUT) { + modifier->playback_mapping = m; + channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS); + } else { + modifier->capture_mapping = m; + channel_str = pa_proplist_gets(modifier->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS); + } + + if (channel_str) { + pa_assert_se(pa_atou(channel_str, &channels) == 0 && channels < PA_CHANNELS_MAX); + pa_log_debug("Got channel count %" PRIu32 " for modifier", channels); + } + + if (channels) + pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); + else + pa_channel_map_init(&m->channel_map); +} + static int ucm_create_mapping_direction( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, @@ -1087,6 +1132,51 @@ static int ucm_create_mapping_direction( return 0; } +static int ucm_create_mapping_for_modifier( + pa_alsa_ucm_config *ucm, + pa_alsa_profile_set *ps, + pa_alsa_profile *p, + pa_alsa_ucm_modifier *modifier, + const char *verb_name, + const char *mod_name, + const char *device_str, + bool is_sink) { + + pa_alsa_mapping *m; + char *mapping_name; + + mapping_name = pa_sprintf_malloc("Mapping %s: %s: %s", verb_name, device_str, is_sink ? "sink" : "source"); + + m = pa_alsa_mapping_get(ps, mapping_name); + if (!m) { + pa_log("no mapping for %s", mapping_name); + pa_xfree(mapping_name); + return -1; + } + pa_log_info("ucm mapping: %s modifier %s", mapping_name, mod_name); + pa_xfree(mapping_name); + + if (!m->ucm_context.ucm_devices && !m->ucm_context.ucm_modifiers) { /* new mapping */ + m->ucm_context.ucm_devices = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + m->ucm_context.ucm = ucm; + m->ucm_context.direction = is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT; + + m->device_strings = pa_xnew0(char*, 2); + m->device_strings[0] = pa_xstrdup(device_str); + m->direction = is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT; + /* Modifier sinks should not be routed to by default */ + m->priority = 0; + + ucm_add_mapping(p, m); + } else if (!m->ucm_context.ucm_modifiers) /* share pcm with device */ + m->ucm_context.ucm_modifiers = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + alsa_mapping_add_ucm_modifier(m, modifier); + + return 0; +} + static int ucm_create_mapping( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, @@ -1142,6 +1232,7 @@ static int ucm_create_profile( pa_alsa_profile *p; pa_alsa_ucm_device *dev; + pa_alsa_ucm_modifier *mod; int i = 0; const char *name, *sink, *source; char *verb_cmp, *c; @@ -1197,6 +1288,20 @@ static int ucm_create_profile( dev->input_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_INPUT); } + /* Now find modifiers that have their own PlaybackPCM and create + * separate sinks for them. */ + PA_LLIST_FOREACH(mod, verb->modifiers) { + name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME); + + sink = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SINK); + source = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_SOURCE); + + if (sink) + ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, sink, TRUE); + else if (source) + ucm_create_mapping_for_modifier(ucm, ps, p, mod, verb_name, name, source, FALSE); + } + pa_alsa_profile_dump(p); return 0; @@ -1234,23 +1339,23 @@ static void profile_finalize_probing(pa_alsa_profile *p) { uint32_t idx; PA_IDXSET_FOREACH(m, p->output_mappings, idx) { - if (!m->output_pcm) - continue; - 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 (!m->input_pcm) - continue; - if (p->supported) m->supported++; + if (!m->input_pcm) + continue; + snd_pcm_close(m->input_pcm); m->input_pcm = NULL; } @@ -1289,20 +1394,35 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * PA_HASHMAP_FOREACH(p, ps->profiles, state) { /* change verb */ pa_log_info("Set ucm verb to %s", p->name); + if ((snd_use_case_set(ucm->ucm_mgr, "_verb", p->name)) < 0) { pa_log("Failed to set verb %s", p->name); p->supported = FALSE; continue; } + PA_IDXSET_FOREACH(m, p->output_mappings, idx) { + if (PA_UCM_IS_MODIFIER_MAPPING(m)) { + /* Skip jack probing on modifier PCMs since we expect this to + * only be controlled on the main device/verb PCM. */ + continue; + } + m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK); if (!m->output_pcm) { p->supported = FALSE; break; } } + if (p->supported) { 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 + * only be controlled on the main device/verb PCM. */ + continue; + } + m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE); if (!m->input_pcm) { p->supported = FALSE; @@ -1319,10 +1439,12 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * pa_log_debug("Profile %s supported.", p->name); PA_IDXSET_FOREACH(m, p->output_mappings, idx) - ucm_mapping_jack_probe(m); + if (!PA_UCM_IS_MODIFIER_MAPPING(m)) + ucm_mapping_jack_probe(m); PA_IDXSET_FOREACH(m, p->input_mappings, idx) - ucm_mapping_jack_probe(m); + if (!PA_UCM_IS_MODIFIER_MAPPING(m)) + ucm_mapping_jack_probe(m); profile_finalize_probing(p); } @@ -1413,6 +1535,7 @@ void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) { void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) { pa_alsa_ucm_device *dev; + pa_alsa_ucm_modifier *mod; uint32_t idx; if (context->ucm_devices) { @@ -1428,6 +1551,13 @@ void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) { } if (context->ucm_modifiers) { + PA_IDXSET_FOREACH(mod, context->ucm_modifiers, idx) { + if (context->direction == PA_DIRECTION_OUTPUT) + mod->playback_mapping = NULL; + else + mod->capture_mapping = NULL; + } + pa_idxset_free(context->ucm_modifiers, NULL, NULL); } } diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h index 38fb2625a..4c5167f1c 100644 --- a/src/modules/alsa/alsa-ucm.h +++ b/src/modules/alsa/alsa-ucm.h @@ -73,6 +73,9 @@ /** For devices: Quality of Service */ #define PA_ALSA_PROP_UCM_QOS "alsa.ucm.qos" +/** For devices: The modifier (if any) that this device corresponds to */ +#define PA_ALSA_PROP_UCM_MODIFIER "alsa.ucm.modifier" + typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb; typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier; typedef struct pa_alsa_ucm_device pa_alsa_ucm_device; @@ -140,6 +143,10 @@ struct pa_alsa_ucm_modifier { pa_direction_t action_direction; char *media_role; + + /* Non-NULL if the modifier has its own PlaybackPCM/CapturePCM */ + pa_alsa_mapping *playback_mapping; + pa_alsa_mapping *capture_mapping; }; struct pa_alsa_ucm_verb {