]> code.delx.au - pulseaudio/blobdiff - src/modules/alsa/alsa-ucm.c
alsa: Support ALSA without a use case manager
[pulseaudio] / src / modules / alsa / alsa-ucm.c
index 7b08b1b1a1c49d4b6af50f1b3d8169f2f097bba6..be3ac74dcf10a6a18ae7f27cafb032fe0275771f 100644 (file)
@@ -64,6 +64,9 @@
         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)
+
+#ifdef HAVE_ALSA_UCM
 
 struct ucm_items {
     const char *id;
@@ -246,7 +249,12 @@ static int ucm_get_device_property(
         }
     }
 
-    pa_assert(device->playback_channels || device->capture_channels);
+    if (device->playback_channels == 0 && device->capture_channels == 0) {
+        pa_log_warn("UCM file does not specify 'PlaybackChannels' or 'CaptureChannels'"
+                    "for device %s, assuming stereo duplex.", device_name);
+        device->playback_channels = 2;
+        device->capture_channels = 2;
+    }
 
     /* get priority of device */
     if (device->playback_channels) { /* sink device */
@@ -445,10 +453,10 @@ static void add_media_role(const char *name, pa_alsa_ucm_device *list, const cha
 static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
     char *sub = NULL, *tmp;
 
-    *is_sink = FALSE;
+    *is_sink = false;
 
     if (pa_startswith(mod_name, "Play")) {
-        *is_sink = TRUE;
+        *is_sink = true;
         sub = pa_xstrdup(mod_name + 4);
     } else if (pa_startswith(mod_name, "Capture"))
         sub = pa_xstrdup(mod_name + 7);
@@ -470,7 +478,7 @@ static char *modifier_name_to_role(const char *mod_name, bool *is_sink) {
 
 static void ucm_set_media_roles(pa_alsa_ucm_modifier *modifier, pa_alsa_ucm_device *list, const char *mod_name) {
     int i;
-    bool is_sink = FALSE;
+    bool is_sink = false;
     char *sub = NULL;
     const char *role_name;
 
@@ -519,8 +527,7 @@ static void append_lost_relationship(pa_alsa_ucm_device *dev) {
     }
 }
 
-int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index)
-{
+int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
     char *card_name;
     const char **verb_list;
     int num_verbs, i, err = 0;
@@ -534,7 +541,7 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index)
 
     err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name);
     if (err < 0) {
-        pa_log("UCM not available for card %s", card_name);
+        pa_log_info("UCM not available for card %s", card_name);
         goto ucm_mgr_fail;
     }
 
@@ -682,7 +689,15 @@ static void ucm_add_port_combination(
 
     port = pa_hashmap_get(ports, name);
     if (!port) {
-        port = pa_device_port_new(core, pa_strna(name), desc, 0);
+        pa_device_port_new_data port_data;
+
+        pa_device_port_new_data_init(&port_data);
+        pa_device_port_new_data_set_name(&port_data, name);
+        pa_device_port_new_data_set_description(&port_data, desc);
+        pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
+
+        port = pa_device_port_new(core, &port_data, 0);
+        pa_device_port_new_data_done(&port_data);
         pa_assert(port);
 
         pa_hashmap_put(ports, port->name, port);
@@ -691,10 +706,6 @@ static void ucm_add_port_combination(
     }
 
     port->priority = priority;
-    if (is_sink)
-        port->is_output = TRUE;
-    else
-        port->is_input = TRUE;
 
     pa_xfree(name);
     pa_xfree(desc);
@@ -720,7 +731,7 @@ static int ucm_port_contains(const char *port_name, const char *dev_name, bool i
     int len;
 
     if (!port_name || !dev_name)
-        return FALSE;
+        return false;
 
     port_name += is_sink ? strlen(PA_UCM_PRE_TAG_OUTPUT) : strlen(PA_UCM_PRE_TAG_INPUT);
 
@@ -1033,6 +1044,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 +1142,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,
@@ -1105,13 +1205,34 @@ static int ucm_create_mapping(
     }
 
     if (sink)
-        ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, TRUE);
+        ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, sink, true);
     if (ret == 0 && source)
-        ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, FALSE);
+        ret = ucm_create_mapping_direction(ucm, ps, p, device, verb_name, device_name, source, false);
 
     return ret;
 }
 
+static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, const char *dev_name, const char *pre_tag) {
+    pa_alsa_jack *j;
+    char *name = pa_sprintf_malloc("%s%s", pre_tag, dev_name);
+
+    PA_LLIST_FOREACH(j, ucm->jacks)
+        if (pa_streq(j->name, name))
+            goto out;
+
+    j = pa_xnew0(pa_alsa_jack, 1);
+    j->state_unplugged = PA_AVAILABLE_NO;
+    j->state_plugged = PA_AVAILABLE_YES;
+    j->name = pa_xstrdup(name);
+    j->alsa_name = pa_sprintf_malloc("%s Jack", dev_name);
+
+    PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j);
+
+out:
+    pa_xfree(name);
+    return j;
+}
+
 static int ucm_create_profile(
         pa_alsa_ucm_config *ucm,
         pa_alsa_profile_set *ps,
@@ -1121,6 +1242,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;
@@ -1140,7 +1262,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->supported = true;
     pa_hashmap_put(ps->profiles, p->name, p);
 
     /* TODO: get profile priority from ucm info or policy management */
@@ -1169,6 +1291,25 @@ static int ucm_create_profile(
         source = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_SOURCE);
 
         ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source);
+
+        if (sink)
+            dev->output_jack = ucm_get_jack(ucm, name, PA_UCM_PRE_TAG_OUTPUT);
+        if (source)
+            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);
@@ -1208,28 +1349,52 @@ 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;
     }
 }
 
+static void ucm_mapping_jack_probe(pa_alsa_mapping *m) {
+    snd_pcm_t *pcm_handle;
+    snd_mixer_t *mixer_handle;
+    snd_hctl_t *hctl_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, &hctl_handle);
+    if (!mixer_handle || !hctl_handle)
+        return;
+
+    PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
+        pa_alsa_jack *jack;
+        jack = m->direction == PA_ALSA_DIRECTION_OUTPUT ? dev->output_jack : dev->input_jack;
+        pa_assert (jack);
+        jack->has_control = pa_alsa_find_jack(hctl_handle, jack->alsa_name) != NULL;
+        pa_log_info("UCM jack %s has_control=%d", jack->name, jack->has_control);
+    }
+
+    snd_mixer_close(mixer_handle);
+}
+
 static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) {
     void *state;
     pa_alsa_profile *p;
@@ -1239,23 +1404,38 @@ 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;
+            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;
+                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;
+                    p->supported = false;
                     break;
                 }
             }
@@ -1268,6 +1448,14 @@ 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)
+            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);
     }
 
@@ -1298,11 +1486,11 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha
             continue;
         }
 
-           ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
+        ucm_create_profile(ucm, ps, verb, verb_name, verb_desc);
     }
 
     ucm_probe_profile_set(ucm, ps);
-    ps->probed = TRUE;
+    ps->probed = true;
 
     return ps;
 }
@@ -1315,9 +1503,9 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
         PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
         pa_proplist_free(di->proplist);
         if (di->conflicting_devices)
-            pa_idxset_free(di->conflicting_devices, NULL, NULL);
+            pa_idxset_free(di->conflicting_devices, NULL);
         if (di->supported_devices)
-            pa_idxset_free(di->supported_devices, NULL, NULL);
+            pa_idxset_free(di->supported_devices, NULL);
         pa_xfree(di);
     }
 
@@ -1337,11 +1525,18 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
 
 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
     pa_alsa_ucm_verb *vi, *vn;
+    pa_alsa_jack *ji, *jn;
 
     PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
         PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
         free_verb(vi);
     }
+    PA_LLIST_FOREACH_SAFE(ji, jn, ucm->jacks) {
+        PA_LLIST_REMOVE(pa_alsa_jack, ucm->jacks, ji);
+        pa_xfree(ji->alsa_name);
+        pa_xfree(ji->name);
+        pa_xfree(ji);
+    }
     if (ucm->ucm_mgr) {
         snd_use_case_mgr_close(ucm->ucm_mgr);
         ucm->ucm_mgr = NULL;
@@ -1350,6 +1545,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) {
@@ -1361,10 +1557,122 @@ void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
                 dev->capture_mapping = NULL;
         }
 
-        pa_idxset_free(context->ucm_devices, NULL, NULL);
+        pa_idxset_free(context->ucm_devices, NULL);
     }
 
     if (context->ucm_modifiers) {
-        pa_idxset_free(context->ucm_modifiers, NULL, NULL);
+        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);
+    }
+}
+
+/* Enable the modifier when the first stream with matched role starts */
+void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
+    pa_alsa_ucm_modifier *mod;
+
+    if (!ucm->active_verb)
+        return;
+
+    PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
+        if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
+            if (mod->enabled_counter == 0) {
+                const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
+
+                pa_log_info("Enable ucm modifier %s", mod_name);
+                if (snd_use_case_set(ucm->ucm_mgr, "_enamod", mod_name) < 0) {
+                    pa_log("Failed to enable ucm modifier %s", mod_name);
+                }
+            }
+
+            mod->enabled_counter++;
+            break;
+        }
+    }
+}
+
+/* Disable the modifier when the last stream with matched role ends */
+void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
+    pa_alsa_ucm_modifier *mod;
+
+    if (!ucm->active_verb)
+        return;
+
+    PA_LLIST_FOREACH(mod, ucm->active_verb->modifiers) {
+        if ((mod->action_direction == dir) && (pa_streq(mod->media_role, role))) {
+
+            mod->enabled_counter--;
+            if (mod->enabled_counter == 0) {
+                const char *mod_name = pa_proplist_gets(mod->proplist, PA_ALSA_PROP_UCM_NAME);
+
+                pa_log_info("Disable ucm modifier %s", mod_name);
+                if (snd_use_case_set(ucm->ucm_mgr, "_dismod", mod_name) < 0) {
+                    pa_log("Failed to disable ucm modifier %s", mod_name);
+                }
+            }
+
+            break;
+        }
     }
 }
+
+#else /* HAVE_ALSA_UCM */
+
+/* Dummy functions for systems without UCM support */
+
+int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
+        pa_log_info("UCM not available.");
+        return -1;
+}
+
+pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map) {
+    return NULL;
+}
+
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
+    return -1;
+}
+
+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) {
+    return -1;
+}
+
+void pa_alsa_ucm_add_ports(
+        pa_hashmap **hash,
+        pa_proplist *proplist,
+        pa_alsa_ucm_mapping_context *context,
+        bool is_sink,
+        pa_card *card) {
+}
+
+void pa_alsa_ucm_add_ports_combination(
+        pa_hashmap *hash,
+        pa_alsa_ucm_mapping_context *context,
+        bool is_sink,
+        pa_hashmap *ports,
+        pa_card_profile *cp,
+        pa_core *core) {
+}
+
+int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink) {
+    return -1;
+}
+
+void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
+}
+
+void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context) {
+}
+
+void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
+}
+
+void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) {
+}
+
+#endif