pulseaudio/src/modules/alsa/module-alsa-card.c
Feng Wei 3a92bda554 alsa: Catch role matched streams to enable/disable modifier
In UCM basic functions, we only assign intended roles from modifier
to sink/source, but we don't have a chance to set the ucm modifiers.
Here we amend the functions so that when roled stream starts or
stops, we have the following results:
1. stream will be routed to sink/source specified in modifier by
   module-intended-roles
2. After that, modifier will be enabled or disabled.
3. when multiple streams with matched roles of modifier start, only
   the first one will enable the modifier, and when they end, the
   last one will disable the modifier.

Signed-off-by: Feng Wei <wei.feng@freescale.com>
Signed-off-by: Arun Raghavan <arun.raghavan@collabora.co.uk>
2012-07-18 10:53:29 +05:30

779 lines
24 KiB
C

/***
This file is part of PulseAudio.
Copyright 2009 Lennart Poettering
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <pulse/xmalloc.h>
#include <pulsecore/core-util.h>
#include <pulsecore/i18n.h>
#include <pulsecore/modargs.h>
#include <pulsecore/queue.h>
#include <modules/reserve-wrap.h>
#ifdef HAVE_UDEV
#include <modules/udev-util.h>
#endif
#include "alsa-util.h"
#include "alsa-ucm.h"
#include "alsa-sink.h"
#include "alsa-source.h"
#include "module-alsa-card-symdef.h"
PA_MODULE_AUTHOR("Lennart Poettering");
PA_MODULE_DESCRIPTION("ALSA Card");
PA_MODULE_VERSION(PACKAGE_VERSION);
PA_MODULE_LOAD_ONCE(FALSE);
PA_MODULE_USAGE(
"name=<name for the card/sink/source, to be prefixed> "
"card_name=<name for the card> "
"card_properties=<properties for the card> "
"sink_name=<name for the sink> "
"sink_properties=<properties for the sink> "
"source_name=<name for the source> "
"source_properties=<properties for the source> "
"namereg_fail=<when false attempt to synthesise new names if they are already taken> "
"device_id=<ALSA card index> "
"format=<sample format> "
"rate=<sample rate> "
"fragments=<number of fragments> "
"fragment_size=<fragment size> "
"mmap=<enable memory mapping?> "
"tsched=<enable system timer based scheduling mode?> "
"tsched_buffer_size=<buffer size when using timer based scheduling> "
"tsched_buffer_watermark=<lower fill watermark> "
"profile=<profile name> "
"fixed_latency_range=<disable latency range changes on underrun?> "
"ignore_dB=<ignore dB information from the device?> "
"deferred_volume=<Synchronize software and hardware volume changes to avoid momentary jumps?> "
"profile_set=<profile set configuration file> "
"paths_dir=<directory containing the path configuration files> "
"use_ucm=<load use case manager> "
);
static const char* const valid_modargs[] = {
"name",
"card_name",
"card_properties",
"sink_name",
"sink_properties",
"source_name",
"source_properties",
"namereg_fail",
"device_id",
"format",
"rate",
"fragments",
"fragment_size",
"mmap",
"tsched",
"tsched_buffer_size",
"tsched_buffer_watermark",
"fixed_latency_range",
"profile",
"ignore_dB",
"deferred_volume",
"profile_set",
"paths_dir",
"use_ucm",
NULL
};
#define DEFAULT_DEVICE_ID "0"
struct userdata {
pa_core *core;
pa_module *module;
char *device_id;
int alsa_card_index;
snd_mixer_t *mixer_handle;
snd_hctl_t *hctl_handle;
pa_hashmap *jacks;
pa_alsa_fdlist *mixer_fdl;
pa_card *card;
pa_modargs *modargs;
pa_alsa_profile_set *profile_set;
/* ucm stuffs */
pa_bool_t use_ucm;
pa_alsa_ucm_config ucm;
/* hooks for modifier action */
pa_hook_slot
*sink_input_put_hook_slot,
*source_output_put_hook_slot,
*sink_input_unlink_hook_slot,
*source_output_unlink_hook_slot;
};
struct profile_data {
pa_alsa_profile *profile;
};
static void add_profiles(struct userdata *u, pa_hashmap *h, pa_hashmap *ports) {
pa_alsa_profile *ap;
void *state;
pa_assert(u);
pa_assert(h);
PA_HASHMAP_FOREACH(ap, u->profile_set->profiles, state) {
struct profile_data *d;
pa_card_profile *cp;
pa_alsa_mapping *m;
uint32_t idx;
cp = pa_card_profile_new(ap->name, ap->description, sizeof(struct profile_data));
cp->priority = ap->priority;
if (ap->output_mappings) {
cp->n_sinks = pa_idxset_size(ap->output_mappings);
PA_IDXSET_FOREACH(m, ap->output_mappings, idx) {
if (u->use_ucm)
pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, TRUE, ports, cp, u->core);
else
pa_alsa_path_set_add_ports(m->output_path_set, cp, ports, NULL, u->core);
if (m->channel_map.channels > cp->max_sink_channels)
cp->max_sink_channels = m->channel_map.channels;
}
}
if (ap->input_mappings) {
cp->n_sources = pa_idxset_size(ap->input_mappings);
PA_IDXSET_FOREACH(m, ap->input_mappings, idx) {
if (u->use_ucm)
pa_alsa_ucm_add_ports_combination(NULL, &m->ucm_context, FALSE, ports, cp, u->core);
else
pa_alsa_path_set_add_ports(m->input_path_set, cp, ports, NULL, u->core);
if (m->channel_map.channels > cp->max_source_channels)
cp->max_source_channels = m->channel_map.channels;
}
}
d = PA_CARD_PROFILE_DATA(cp);
d->profile = ap;
pa_hashmap_put(h, cp->name, cp);
}
}
static void add_disabled_profile(pa_hashmap *profiles) {
pa_card_profile *p;
struct profile_data *d;
p = pa_card_profile_new("off", _("Off"), sizeof(struct profile_data));
d = PA_CARD_PROFILE_DATA(p);
d->profile = NULL;
pa_hashmap_put(profiles, p->name, p);
}
static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
struct userdata *u;
struct profile_data *nd, *od;
uint32_t idx;
pa_alsa_mapping *am;
pa_queue *sink_inputs = NULL, *source_outputs = NULL;
pa_assert(c);
pa_assert(new_profile);
pa_assert_se(u = c->userdata);
nd = PA_CARD_PROFILE_DATA(new_profile);
od = PA_CARD_PROFILE_DATA(c->active_profile);
if (od->profile && od->profile->output_mappings)
PA_IDXSET_FOREACH(am, od->profile->output_mappings, idx) {
if (!am->sink)
continue;
if (nd->profile &&
nd->profile->output_mappings &&
pa_idxset_get_by_data(nd->profile->output_mappings, am, NULL))
continue;
sink_inputs = pa_sink_move_all_start(am->sink, sink_inputs);
pa_alsa_sink_free(am->sink);
am->sink = NULL;
}
if (od->profile && od->profile->input_mappings)
PA_IDXSET_FOREACH(am, od->profile->input_mappings, idx) {
if (!am->source)
continue;
if (nd->profile &&
nd->profile->input_mappings &&
pa_idxset_get_by_data(nd->profile->input_mappings, am, NULL))
continue;
source_outputs = pa_source_move_all_start(am->source, source_outputs);
pa_alsa_source_free(am->source);
am->source = NULL;
}
/* if UCM is available for this card then update the verb */
if (u->use_ucm) {
if (pa_alsa_ucm_set_profile(&u->ucm, nd->profile ? nd->profile->name : NULL,
od->profile ? od->profile->name : NULL) < 0)
return -1;
}
if (nd->profile && nd->profile->output_mappings)
PA_IDXSET_FOREACH(am, nd->profile->output_mappings, idx) {
if (!am->sink)
am->sink = pa_alsa_sink_new(c->module, u->modargs, __FILE__, c, am);
if (sink_inputs && am->sink) {
pa_sink_move_all_finish(am->sink, sink_inputs, FALSE);
sink_inputs = NULL;
}
}
if (nd->profile && nd->profile->input_mappings)
PA_IDXSET_FOREACH(am, nd->profile->input_mappings, idx) {
if (!am->source)
am->source = pa_alsa_source_new(c->module, u->modargs, __FILE__, c, am);
if (source_outputs && am->source) {
pa_source_move_all_finish(am->source, source_outputs, FALSE);
source_outputs = NULL;
}
}
if (sink_inputs)
pa_sink_move_all_fail(sink_inputs);
if (source_outputs)
pa_source_move_all_fail(source_outputs);
return 0;
}
static void init_profile(struct userdata *u) {
uint32_t idx;
pa_alsa_mapping *am;
struct profile_data *d;
pa_alsa_ucm_config *ucm = &u->ucm;
pa_assert(u);
d = PA_CARD_PROFILE_DATA(u->card->active_profile);
if (d->profile && u->use_ucm) {
/* Set initial verb */
if (pa_alsa_ucm_set_profile(ucm, d->profile->name, NULL) < 0) {
pa_log("Failed to set ucm profile %s", d->profile->name);
return;
}
}
if (d->profile && d->profile->output_mappings)
PA_IDXSET_FOREACH(am, d->profile->output_mappings, idx)
am->sink = pa_alsa_sink_new(u->module, u->modargs, __FILE__, u->card, am);
if (d->profile && d->profile->input_mappings)
PA_IDXSET_FOREACH(am, d->profile->input_mappings, idx)
am->source = pa_alsa_source_new(u->module, u->modargs, __FILE__, u->card, am);
}
static void report_port_state(pa_device_port *p, struct userdata *u)
{
void *state;
pa_alsa_jack *jack;
pa_port_available_t pa = PA_PORT_AVAILABLE_UNKNOWN;
pa_device_port *port;
PA_HASHMAP_FOREACH(jack, u->jacks, state) {
pa_port_available_t cpa;
if (u->use_ucm)
port = pa_hashmap_get(u->card->ports, jack->name);
else {
if (jack->path)
port = jack->path->port;
else
continue;
}
if (p != port)
continue;
cpa = jack->plugged_in ? jack->state_plugged : jack->state_unplugged;
/* "Yes" and "no" trumphs "unknown" if we have more than one jack */
if (cpa == PA_PORT_AVAILABLE_UNKNOWN)
continue;
if ((cpa == PA_PORT_AVAILABLE_NO && pa == PA_PORT_AVAILABLE_YES) ||
(pa == PA_PORT_AVAILABLE_NO && cpa == PA_PORT_AVAILABLE_YES))
pa_log_warn("Availability of port '%s' is inconsistent!", p->name);
else
pa = cpa;
}
pa_device_port_set_available(p, pa);
}
static int report_jack_state(snd_hctl_elem_t *elem, unsigned int mask)
{
struct userdata *u = snd_hctl_elem_get_callback_private(elem);
snd_ctl_elem_value_t *elem_value;
pa_bool_t plugged_in;
void *state;
pa_alsa_jack *jack;
pa_device_port *port;
pa_assert(u);
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return 0;
snd_ctl_elem_value_alloca(&elem_value);
if (snd_hctl_elem_read(elem, elem_value) < 0) {
pa_log_warn("Failed to read jack detection from '%s'", pa_strnull(snd_hctl_elem_get_name(elem)));
return 0;
}
plugged_in = !!snd_ctl_elem_value_get_boolean(elem_value, 0);
pa_log_debug("Jack '%s' is now %s", pa_strnull(snd_hctl_elem_get_name(elem)), plugged_in ? "plugged in" : "unplugged");
PA_HASHMAP_FOREACH(jack, u->jacks, state)
if (jack->hctl_elem == elem) {
jack->plugged_in = plugged_in;
if (u->use_ucm) {
pa_assert(u->card->ports);
port = pa_hashmap_get(u->card->ports, jack->name);
pa_assert(port);
}
else {
pa_assert(jack->path && jack->path->port);
port = jack->path->port;
}
report_port_state(port, u);
}
return 0;
}
static void init_jacks(struct userdata *u) {
void *state;
pa_alsa_path* path;
pa_alsa_jack* jack;
u->jacks = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
if (u->use_ucm) {
PA_LLIST_FOREACH(jack, u->ucm.jacks)
if (jack->has_control)
pa_hashmap_put(u->jacks, jack, jack);
} else {
/* See if we have any jacks */
if (u->profile_set->output_paths)
PA_HASHMAP_FOREACH(path, u->profile_set->output_paths, state)
PA_LLIST_FOREACH(jack, path->jacks)
if (jack->has_control)
pa_hashmap_put(u->jacks, jack, jack);
if (u->profile_set->input_paths)
PA_HASHMAP_FOREACH(path, u->profile_set->input_paths, state)
PA_LLIST_FOREACH(jack, path->jacks)
if (jack->has_control)
pa_hashmap_put(u->jacks, jack, jack);
}
pa_log_debug("Found %d jacks.", pa_hashmap_size(u->jacks));
if (pa_hashmap_size(u->jacks) == 0)
return;
u->mixer_fdl = pa_alsa_fdlist_new();
u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL, &u->hctl_handle);
if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, NULL, u->hctl_handle, u->core->mainloop) >= 0) {
PA_HASHMAP_FOREACH(jack, u->jacks, state) {
jack->hctl_elem = pa_alsa_find_jack(u->hctl_handle, jack->alsa_name);
if (!jack->hctl_elem) {
pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name);
jack->has_control = FALSE;
continue;
}
snd_hctl_elem_set_callback_private(jack->hctl_elem, u);
snd_hctl_elem_set_callback(jack->hctl_elem, report_jack_state);
report_jack_state(jack->hctl_elem, 0);
}
} else
pa_log("Failed to open hctl/mixer for jack detection");
}
static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) {
char *t;
const char *n;
pa_assert(data);
pa_assert(ma);
pa_assert(device_id);
if ((n = pa_modargs_get_value(ma, "card_name", NULL))) {
pa_card_new_data_set_name(data, n);
data->namereg_fail = TRUE;
return;
}
if ((n = pa_modargs_get_value(ma, "name", NULL)))
data->namereg_fail = TRUE;
else {
n = device_id;
data->namereg_fail = FALSE;
}
t = pa_sprintf_malloc("alsa_card.%s", n);
pa_card_new_data_set_name(data, t);
pa_xfree(t);
}
static pa_hook_result_t sink_input_put_hook_callback(pa_core *c, pa_sink_input *sink_input, struct userdata *u) {
const char *role;
pa_sink *sink = sink_input->sink;
pa_assert(sink);
role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE);
/* new sink input linked to sink of this card */
if (role && sink->card == u->card)
pa_alsa_ucm_roled_stream_begin(&u->ucm, role, PA_DIRECTION_OUTPUT);
return PA_HOOK_OK;
}
static pa_hook_result_t source_output_put_hook_callback(pa_core *c, pa_source_output *source_output, struct userdata *u) {
const char *role;
pa_source *source = source_output->source;
pa_assert(source);
role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE);
/* new source output linked to source of this card */
if (role && source->card == u->card)
pa_alsa_ucm_roled_stream_begin(&u->ucm, role, PA_DIRECTION_INPUT);
return PA_HOOK_OK;
}
static pa_hook_result_t sink_input_unlink_hook_callback(pa_core *c, pa_sink_input *sink_input, struct userdata *u) {
const char *role;
pa_sink *sink = sink_input->sink;
pa_assert(sink);
role = pa_proplist_gets(sink_input->proplist, PA_PROP_MEDIA_ROLE);
/* new sink input unlinked from sink of this card */
if (role && sink->card == u->card)
pa_alsa_ucm_roled_stream_end(&u->ucm, role, PA_DIRECTION_OUTPUT);
return PA_HOOK_OK;
}
static pa_hook_result_t source_output_unlink_hook_callback(pa_core *c, pa_source_output *source_output, struct userdata *u) {
const char *role;
pa_source *source = source_output->source;
pa_assert(source);
role = pa_proplist_gets(source_output->proplist, PA_PROP_MEDIA_ROLE);
/* new source output unlinked from source of this card */
if (role && source->card == u->card)
pa_alsa_ucm_roled_stream_end(&u->ucm, role, PA_DIRECTION_INPUT);
return PA_HOOK_OK;
}
int pa__init(pa_module *m) {
pa_card_new_data data;
pa_modargs *ma;
pa_bool_t ignore_dB = FALSE;
struct userdata *u;
pa_reserve_wrapper *reserve = NULL;
const char *description;
const char *profile = NULL;
char *fn = NULL;
pa_bool_t namereg_fail = FALSE;
pa_alsa_refcnt_inc();
pa_assert(m);
if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
pa_log("Failed to parse module arguments");
goto fail;
}
if (pa_modargs_get_value_boolean(ma, "ignore_dB", &ignore_dB) < 0) {
pa_log("Failed to parse ignore_dB argument.");
goto fail;
}
m->userdata = u = pa_xnew0(struct userdata, 1);
u->core = m->core;
u->module = m;
u->device_id = pa_xstrdup(pa_modargs_get_value(ma, "device_id", DEFAULT_DEVICE_ID));
u->modargs = ma;
u->use_ucm = TRUE;
u->ucm.core = m->core;
if ((u->alsa_card_index = snd_card_get_index(u->device_id)) < 0) {
pa_log("Card '%s' doesn't exist: %s", u->device_id, pa_alsa_strerror(u->alsa_card_index));
goto fail;
}
if (!pa_in_system_mode()) {
char *rname;
if ((rname = pa_alsa_get_reserve_name(u->device_id))) {
reserve = pa_reserve_wrapper_get(m->core, rname);
pa_xfree(rname);
if (!reserve)
goto fail;
}
}
pa_modargs_get_value_boolean(ma, "use_ucm", &u->use_ucm);
if (u->use_ucm && !pa_alsa_ucm_query_profiles(&u->ucm, u->alsa_card_index)) {
pa_log_info("Found UCM profiles");
u->profile_set = pa_alsa_ucm_add_profile_set(&u->ucm, &u->core->default_channel_map);
/* hook start of sink input/source output to enable modifiers */
/* A little bit later than module-role-cork */
u->sink_input_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_LATE+10,
(pa_hook_cb_t) sink_input_put_hook_callback, u);
u->source_output_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_LATE+10,
(pa_hook_cb_t) source_output_put_hook_callback, u);
/* hook end of sink input/source output to disable modifiers */
/* A little bit later than module-role-cork */
u->sink_input_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], PA_HOOK_LATE+10,
(pa_hook_cb_t) sink_input_unlink_hook_callback, u);
u->source_output_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], PA_HOOK_LATE+10,
(pa_hook_cb_t) source_output_unlink_hook_callback, u);
}
else {
u->use_ucm = FALSE;
#ifdef HAVE_UDEV
fn = pa_udev_get_property(u->alsa_card_index, "PULSE_PROFILE_SET");
#endif
if (pa_modargs_get_value(ma, "profile_set", NULL)) {
pa_xfree(fn);
fn = pa_xstrdup(pa_modargs_get_value(ma, "profile_set", NULL));
}
u->profile_set = pa_alsa_profile_set_new(fn, &u->core->default_channel_map);
pa_xfree(fn);
}
u->profile_set->ignore_dB = ignore_dB;
if (!u->profile_set)
goto fail;
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);
pa_alsa_profile_set_dump(u->profile_set);
pa_card_new_data_init(&data);
data.driver = __FILE__;
data.module = m;
pa_alsa_init_proplist_card(m->core, data.proplist, u->alsa_card_index);
pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_id);
pa_alsa_init_description(data.proplist);
set_card_name(&data, ma, u->device_id);
/* We need to give pa_modargs_get_value_boolean() a pointer to a local
* variable instead of using &data.namereg_fail directly, because
* data.namereg_fail is a bitfield and taking the address of a bitfield
* variable is impossible. */
namereg_fail = data.namereg_fail;
if (pa_modargs_get_value_boolean(ma, "namereg_fail", &namereg_fail) < 0) {
pa_log("Failed to parse namereg_fail argument.");
pa_card_new_data_done(&data);
goto fail;
}
data.namereg_fail = namereg_fail;
if (reserve)
if ((description = pa_proplist_gets(data.proplist, PA_PROP_DEVICE_DESCRIPTION)))
pa_reserve_wrapper_set_application_device_name(reserve, description);
add_profiles(u, data.profiles, data.ports);
if (pa_hashmap_isempty(data.profiles)) {
pa_log("Failed to find a working profile.");
pa_card_new_data_done(&data);
goto fail;
}
add_disabled_profile(data.profiles);
if (pa_modargs_get_proplist(ma, "card_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
pa_log("Invalid properties");
pa_card_new_data_done(&data);
goto fail;
}
if ((profile = pa_modargs_get_value(ma, "profile", NULL)))
pa_card_new_data_set_profile(&data, profile);
u->card = pa_card_new(m->core, &data);
pa_card_new_data_done(&data);
if (!u->card)
goto fail;
u->card->userdata = u;
u->card->set_profile = card_set_profile;
init_profile(u);
init_jacks(u);
if (reserve)
pa_reserve_wrapper_unref(reserve);
if (!pa_hashmap_isempty(u->profile_set->decibel_fixes))
pa_log_warn("Card %s uses decibel fixes (i.e. overrides the decibel information for some alsa volume elements). "
"Please note that this feature is meant just as a help for figuring out the correct decibel values. "
"PulseAudio is not the correct place to maintain the decibel mappings! The fixed decibel values "
"should be sent to ALSA developers so that they can fix the driver. If it turns out that this feature "
"is abused (i.e. fixes are not pushed to ALSA), the decibel fix feature may be removed in some future "
"PulseAudio version.", u->card->name);
return 0;
fail:
if (reserve)
pa_reserve_wrapper_unref(reserve);
pa__done(m);
return -1;
}
int pa__get_n_used(pa_module *m) {
struct userdata *u;
int n = 0;
uint32_t idx;
pa_sink *sink;
pa_source *source;
pa_assert(m);
pa_assert_se(u = m->userdata);
pa_assert(u->card);
PA_IDXSET_FOREACH(sink, u->card->sinks, idx)
n += pa_sink_linked_by(sink);
PA_IDXSET_FOREACH(source, u->card->sources, idx)
n += pa_source_linked_by(source);
return n;
}
void pa__done(pa_module*m) {
struct userdata *u;
pa_assert(m);
if (!(u = m->userdata))
goto finish;
if (u->sink_input_put_hook_slot)
pa_hook_slot_free(u->sink_input_put_hook_slot);
if (u->sink_input_unlink_hook_slot)
pa_hook_slot_free(u->sink_input_unlink_hook_slot);
if (u->source_output_put_hook_slot)
pa_hook_slot_free(u->source_output_put_hook_slot);
if (u->source_output_unlink_hook_slot)
pa_hook_slot_free(u->source_output_unlink_hook_slot);
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
if (u->mixer_handle)
snd_mixer_close(u->mixer_handle);
if (u->jacks)
pa_hashmap_free(u->jacks, NULL, NULL);
if (u->card && u->card->sinks) {
pa_sink *s;
while ((s = pa_idxset_steal_first(u->card->sinks, NULL)))
pa_alsa_sink_free(s);
}
if (u->card && u->card->sources) {
pa_source *s;
while ((s = pa_idxset_steal_first(u->card->sources, NULL)))
pa_alsa_source_free(s);
}
if (u->card)
pa_card_free(u->card);
if (u->modargs)
pa_modargs_free(u->modargs);
if (u->profile_set)
pa_alsa_profile_set_free(u->profile_set);
pa_alsa_ucm_free(&u->ucm);
pa_xfree(u->device_id);
pa_xfree(u);
finish:
pa_alsa_refcnt_dec();
}