acp: add option to switch profile and ports

Enable an option to switch to the next best profile and port when
the current one becomes unavailable.
This commit is contained in:
Wim Taymans 2020-09-08 16:39:47 +02:00
parent 368366b88d
commit 5e368b1ad6
3 changed files with 561 additions and 522 deletions

View file

@ -215,7 +215,8 @@ static void add_profiles(pa_card *impl)
} }
} }
static pa_available_t calc_port_state(pa_device_port *p, pa_card *impl) { static pa_available_t calc_port_state(pa_device_port *p, pa_card *impl)
{
void *state; void *state;
pa_alsa_jack *jack; pa_alsa_jack *jack;
pa_available_t pa = PA_AVAILABLE_UNKNOWN; pa_available_t pa = PA_AVAILABLE_UNKNOWN;
@ -398,7 +399,7 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask)
* *
* If there are no output ports at all, but the profile contains at least * If there are no output ports at all, but the profile contains at least
* one sink, then the output is considered to be available. */ * one sink, then the output is considered to be available. */
if (impl->card.active_profile_index != (uint32_t)-1) if (impl->card.active_profile_index != ACP_INVALID_INDEX)
active_available = impl->card.profiles[impl->card.active_profile_index]->available; active_available = impl->card.profiles[impl->card.active_profile_index]->available;
PA_HASHMAP_FOREACH(profile, impl->profiles, state) { PA_HASHMAP_FOREACH(profile, impl->profiles, state) {
@ -416,7 +417,6 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask)
if (port->port.direction == ACP_DIRECTION_CAPTURE) { if (port->port.direction == ACP_DIRECTION_CAPTURE) {
has_input_port = true; has_input_port = true;
if (port->port.available != ACP_AVAILABLE_NO) if (port->port.available != ACP_AVAILABLE_NO)
found_available_input_port = true; found_available_input_port = true;
} else { } else {
@ -427,7 +427,8 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask)
} }
} }
if ((has_input_port && !found_available_input_port) || (has_output_port && !found_available_output_port)) if ((has_input_port && !found_available_input_port) ||
(has_output_port && !found_available_output_port))
available = ACP_AVAILABLE_NO; available = ACP_AVAILABLE_NO;
if (has_input_port && !has_output_port && found_available_input_port) if (has_input_port && !has_output_port && found_available_input_port)
@ -446,7 +447,7 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask)
profile_set_available(impl, profile->profile.index, available); profile_set_available(impl, profile->profile.index, available);
} }
if (impl->card.active_profile_index != (uint32_t)-1) if (impl->card.active_profile_index != ACP_INVALID_INDEX)
profile_set_available(impl, impl->card.active_profile_index, active_available); profile_set_available(impl, impl->card.active_profile_index, active_available);
return 0; return 0;
@ -570,7 +571,6 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
return 0; return 0;
} }
static void init_eld_ctls(pa_card *impl) static void init_eld_ctls(pa_card *impl)
{ {
void *state; void *state;
@ -619,37 +619,37 @@ static void init_eld_ctls(pa_card *impl)
} }
} }
static int choose_profile(pa_card *impl, const char *profile) uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *name)
{ {
uint32_t i; uint32_t i;
int32_t best_alt = -1, best = -1; uint32_t best_alt = ACP_INVALID_INDEX, best = ACP_INVALID_INDEX;
struct acp_card_profile **profiles = impl->card.profiles; struct acp_card_profile **profiles = card->profiles;
for (i = 0; i < impl->card.n_profiles; i++) { for (i = 0; i < card->n_profiles; i++) {
struct acp_card_profile *p = profiles[i]; struct acp_card_profile *p = profiles[i];
if (profile) { if (name) {
if (strcmp(profile, p->name)) if (strcmp(name, p->name))
best = i; best = i;
continue; continue;
} }
if (p->available == ACP_AVAILABLE_NO) { if (p->available != ACP_AVAILABLE_NO) {
if (best_alt == -1 || p->priority > profiles[best_alt]->priority) if (best == ACP_INVALID_INDEX || p->priority > profiles[best]->priority)
best_alt = i;
} else {
if (best == -1 || p->priority > profiles[best]->priority)
best = i; best = i;
} else {
if (best_alt == ACP_INVALID_INDEX || p->priority > profiles[best_alt]->priority)
best_alt = i;
} }
} }
if (best == -1) if (best == ACP_INVALID_INDEX)
best = best_alt; best = best_alt;
if (best == -1) if (best == ACP_INVALID_INDEX)
return -ENOENT; best = 0;
return best;
return acp_card_set_profile(&impl->card, best);
} }
static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element, bool ignore_dB) { static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element, bool ignore_dB)
{
const char *mdev; const char *mdev;
pa_alsa_mapping *mapping = dev->mapping; pa_alsa_mapping *mapping = dev->mapping;
@ -671,7 +671,6 @@ static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element,
} }
if (element) { if (element) {
if (!(dev->mixer_path = pa_alsa_path_synthesize(element, dev->direction))) if (!(dev->mixer_path = pa_alsa_path_synthesize(element, dev->direction)))
goto fail; goto fail;
@ -681,11 +680,9 @@ static void find_mixer(pa_card *impl, pa_alsa_device *dev, const char *element,
pa_log_debug("Probed mixer path %s:", dev->mixer_path->name); pa_log_debug("Probed mixer path %s:", dev->mixer_path->name);
pa_alsa_path_dump(dev->mixer_path); pa_alsa_path_dump(dev->mixer_path);
} }
return; return;
fail: fail:
if (dev->mixer_path) { if (dev->mixer_path) {
pa_alsa_path_free(dev->mixer_path); pa_alsa_path_free(dev->mixer_path);
dev->mixer_path = NULL; dev->mixer_path = NULL;
@ -716,9 +713,10 @@ static int read_volume(pa_alsa_device *dev)
pa_card *impl = dev->card; pa_card *impl = dev->card;
pa_cvolume r; pa_cvolume r;
uint32_t i; uint32_t i;
int res;
if (pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r) < 0) if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0)
return -1; return res;
/* Shift down by the base volume, so that 0dB becomes maximum volume */ /* Shift down by the base volume, so that 0dB becomes maximum volume */
pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume); pa_sw_cvolume_multiply_scalar(&r, &r, dev->base_volume);
@ -796,9 +794,10 @@ static int read_mute(pa_alsa_device *dev)
{ {
pa_card *impl = dev->card; pa_card *impl = dev->card;
bool mute; bool mute;
int res;
if (pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute) < 0) if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0)
return -1; return res;
if (mute == dev->muted) if (mute == dev->muted)
return 0; return 0;
@ -818,13 +817,15 @@ static void set_mute(pa_alsa_device *dev, bool mute)
pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute); pa_alsa_path_set_mute(dev->mixer_path, dev->mixer_handle, mute);
} }
static void mixer_volume_init(pa_alsa_device *dev) { static void mixer_volume_init(pa_alsa_device *dev)
{
pa_assert(dev); pa_assert(dev);
if (!dev->mixer_path || !dev->mixer_path->has_volume) { if (!dev->mixer_path || !dev->mixer_path->has_volume) {
dev->read_volume = NULL; dev->read_volume = NULL;
dev->set_volume = NULL; dev->set_volume = NULL;
pa_log_info("Driver does not support hardware volume control, falling back to software volume control."); pa_log_info("Driver does not support hardware volume control, "
"falling back to software volume control.");
dev->base_volume = PA_VOLUME_NORM; dev->base_volume = PA_VOLUME_NORM;
dev->n_volume_steps = PA_VOLUME_NORM+1; dev->n_volume_steps = PA_VOLUME_NORM+1;
dev->device.flags &= ~ACP_DEVICE_HW_VOLUME; dev->device.flags &= ~ACP_DEVICE_HW_VOLUME;
@ -877,7 +878,9 @@ static void mixer_volume_init(pa_alsa_device *dev) {
} }
static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) { static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB)
{
int res;
bool need_mixer_callback = false; bool need_mixer_callback = false;
/* This code is before the u->mixer_handle check, because if the UCM /* This code is before the u->mixer_handle check, because if the UCM
@ -885,8 +888,9 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) {
* will be NULL, but the UCM device enable sequence will still need to be * will be NULL, but the UCM device enable sequence will still need to be
* executed. */ * executed. */
if (dev->active_port && dev->ucm_context) { if (dev->active_port && dev->ucm_context) {
if (pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port, dev->direction == PA_ALSA_DIRECTION_OUTPUT) < 0) if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port,
return -1; dev->direction == PA_ALSA_DIRECTION_OUTPUT)) < 0)
return res;
} }
if (!dev->mixer_handle) if (!dev->mixer_handle)
@ -898,7 +902,6 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) {
/* We have a list of supported paths, so let's activate the /* We have a list of supported paths, so let's activate the
* one that has been chosen as active */ * one that has been chosen as active */
data = PA_DEVICE_PORT_DATA(dev->active_port); data = PA_DEVICE_PORT_DATA(dev->active_port);
dev->mixer_path = data->path; dev->mixer_path = data->path;
@ -915,14 +918,13 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) {
} }
} }
} else { } else {
if (!dev->mixer_path && dev->mixer_path_set) if (!dev->mixer_path && dev->mixer_path_set)
dev->mixer_path = pa_hashmap_first(dev->mixer_path_set->paths); dev->mixer_path = pa_hashmap_first(dev->mixer_path_set->paths);
if (dev->mixer_path) { if (dev->mixer_path) {
/* Hmm, we have only a single path, then let's activate it */ /* Hmm, we have only a single path, then let's activate it */
pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings,
pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings, dev->mixer_handle, dev->muted); dev->mixer_handle, dev->muted);
} else } else
return 0; return 0;
} }
@ -949,7 +951,6 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) {
else else
pa_alsa_path_set_callback(dev->mixer_path, dev->mixer_handle, mixer_callback, dev); pa_alsa_path_set_callback(dev->mixer_path, dev->mixer_handle, mixer_callback, dev);
} }
return 0; return 0;
} }
@ -959,39 +960,17 @@ static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_devic
if (dev->active_port) { if (dev->active_port) {
dev->active_port->port.flags &= ~ACP_PORT_ACTIVE; dev->active_port->port.flags &= ~ACP_PORT_ACTIVE;
dev->active_port = NULL; dev->active_port = NULL;
dev->device.active_port_index = ACP_INVALID_INDEX;
} }
return 0; return 0;
} }
static pa_device_port *find_best_port(pa_hashmap *ports)
{
void *state;
pa_device_port *p, *best = NULL, *alt = NULL;
if (!ports)
return NULL;
/* First run: skip unavailable ports */
PA_HASHMAP_FOREACH(p, ports, state) {
if (!alt || p->port.priority > alt->port.priority)
alt = p;
if (p->port.available == ACP_AVAILABLE_NO)
continue;
if (!best || p->port.priority > best->port.priority)
best = p;
}
if (!best)
best = alt;
return best;
}
static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev) static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev)
{ {
const char *mod_name; const char *mod_name;
bool ignore_dB = false; bool ignore_dB = false;
uint32_t port_index;
int res;
if (impl->use_ucm && if (impl->use_ucm &&
(mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) { (mod_name = pa_proplist_gets(mapping->proplist, PA_ALSA_PROP_UCM_MODIFIER))) {
@ -1008,12 +987,14 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device
find_mixer(impl, dev, NULL, ignore_dB); find_mixer(impl, dev, NULL, ignore_dB);
dev->active_port = find_best_port(dev->ports); port_index = acp_device_find_best_port_index(&dev->device, NULL);
dev->active_port = (pa_device_port*)dev->device.ports[port_index];
if (dev->active_port) if (dev->active_port)
dev->active_port->port.flags |= ACP_PORT_ACTIVE; dev->active_port->port.flags |= ACP_PORT_ACTIVE;
if (setup_mixer(impl, dev, ignore_dB) < 0) if ((res = setup_mixer(impl, dev, ignore_dB)) < 0)
return -1; return res;
if (dev->read_volume) if (dev->read_volume)
dev->read_volume(dev); dev->read_volume(dev);
@ -1035,7 +1016,7 @@ int acp_card_set_profile(struct acp_card *card, uint32_t new_index)
if (new_index >= card->n_profiles) if (new_index >= card->n_profiles)
return -EINVAL; return -EINVAL;
op = old_index != (uint32_t)-1 ? (pa_alsa_profile*)profiles[old_index] : NULL; op = old_index != ACP_INVALID_INDEX ? (pa_alsa_profile*)profiles[old_index] : NULL;
np = (pa_alsa_profile*)profiles[new_index]; np = (pa_alsa_profile*)profiles[new_index];
if (op && op->output_mappings) { if (op && op->output_mappings) {
@ -1106,6 +1087,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
const char *s, *profile_set = NULL, *profile = NULL; const char *s, *profile_set = NULL, *profile = NULL;
char device_id[16]; char device_id[16];
bool ignore_dB = false; bool ignore_dB = false;
uint32_t profile_index;
impl = calloc(1, sizeof(*impl)); impl = calloc(1, sizeof(*impl));
if (impl == NULL) if (impl == NULL)
@ -1119,7 +1101,7 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
card = &impl->card; card = &impl->card;
card->index = index; card->index = index;
card->active_profile_index = (uint32_t)-1; card->active_profile_index = ACP_INVALID_INDEX;
impl->use_ucm = true; impl->use_ucm = true;
@ -1191,7 +1173,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props)
init_jacks(impl); init_jacks(impl);
choose_profile(impl, profile); profile_index = acp_card_find_best_profile_index(&impl->card, profile);
acp_card_set_profile(&impl->card, profile_index);
init_eld_ctls(impl); init_eld_ctls(impl);
@ -1309,8 +1292,35 @@ static void sync_mixer(pa_alsa_device *d, pa_device_port *port)
d->set_volume(d, &d->real_volume); d->set_volume(d, &d->real_volume);
} }
/* Called from IO context */
uint32_t acp_device_find_best_port_index(struct acp_device *dev, const char *name)
{
uint32_t i;
uint32_t best_alt = ACP_INVALID_INDEX, best = ACP_INVALID_INDEX;
struct acp_port **ports = dev->ports;
for (i = 0; i < dev->n_ports; i++) {
struct acp_port *p = ports[i];
if (name) {
if (strcmp(name, p->name))
best = i;
continue;
}
if (p->available != ACP_AVAILABLE_NO) {
if (best == ACP_INVALID_INDEX || p->priority > ports[best]->priority)
best = i;
} else {
if (best_alt == ACP_INVALID_INDEX || p->priority > ports[best_alt]->priority)
best_alt = i;
}
}
if (best == ACP_INVALID_INDEX)
best = best_alt;
if (best == ACP_INVALID_INDEX)
best = 0;
return best;
}
int acp_device_set_port(struct acp_device *dev, uint32_t port_index) int acp_device_set_port(struct acp_device *dev, uint32_t port_index)
{ {

View file

@ -42,6 +42,8 @@ extern "C" {
#define ACP_PRINTF_FUNC(fmt, arg1) #define ACP_PRINTF_FUNC(fmt, arg1)
#endif #endif
#define ACP_INVALID_INDEX ((uint32_t)-1)
struct acp_dict_item { struct acp_dict_item {
const char *key; const char *key;
const char *value; const char *value;
@ -201,8 +203,9 @@ struct acp_device {
float base_volume; float base_volume;
float volume_step; float volume_step;
struct acp_port **ports;
uint32_t n_ports; uint32_t n_ports;
uint32_t active_port_index;
struct acp_port **ports;
}; };
struct acp_card_profile { struct acp_card_profile {
@ -227,8 +230,8 @@ struct acp_card {
struct acp_dict props; struct acp_dict props;
uint32_t n_profiles; uint32_t n_profiles;
struct acp_card_profile **profiles;
uint32_t active_profile_index; uint32_t active_profile_index;
struct acp_card_profile **profiles;
uint32_t n_devices; uint32_t n_devices;
struct acp_device **devices; struct acp_device **devices;
@ -252,8 +255,10 @@ int acp_card_poll_descriptors_revents(struct acp_card *card, struct pollfd *pfds
unsigned int nfds, unsigned short *revents); unsigned int nfds, unsigned short *revents);
int acp_card_handle_events(struct acp_card *card); int acp_card_handle_events(struct acp_card *card);
uint32_t acp_card_find_best_profile_index(struct acp_card *card, const char *name);
int acp_card_set_profile(struct acp_card *card, uint32_t profile_index); int acp_card_set_profile(struct acp_card *card, uint32_t profile_index);
uint32_t acp_device_find_best_port_index(struct acp_device *dev, const char *name);
int acp_device_set_port(struct acp_device *dev, uint32_t port_index); int acp_device_set_port(struct acp_device *dev, uint32_t port_index);
int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume); int acp_device_set_volume(struct acp_device *dev, const float *volume, uint32_t n_volume);

View file

@ -52,15 +52,21 @@
#define MAX_POLL 16 #define MAX_POLL 16
static const char default_device[] = "hw:0"; #define DEFAULT_DEVICE "hw:0"
#define DEFAULT_AUTO_PROFILE true
#define DEFAULT_AUTO_PORT true
struct props { struct props {
char device[64]; char device[64];
bool auto_profile;
bool auto_port;
}; };
static void reset_props(struct props *props) static void reset_props(struct props *props)
{ {
strncpy(props->device, default_device, 64); strncpy(props->device, DEFAULT_DEVICE, 64);
props->auto_profile = DEFAULT_AUTO_PROFILE;
props->auto_port = DEFAULT_AUTO_PORT;
} }
struct impl { struct impl {
@ -661,6 +667,11 @@ static void card_profile_available(void *data, uint32_t index,
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false); emit_info(this, false);
if (this->props.auto_profile && available == ACP_AVAILABLE_NO) {
index = acp_card_find_best_profile_index(card, NULL);
acp_card_set_profile(card, index);
}
} }
static void card_port_changed(void *data, uint32_t old_index, uint32_t new_index) static void card_port_changed(void *data, uint32_t old_index, uint32_t new_index)
@ -689,6 +700,19 @@ static void card_port_available(void *data, uint32_t index,
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL; this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false); emit_info(this, false);
if (this->props.auto_port && available == ACP_AVAILABLE_NO) {
uint32_t i, index;
for (i = 0; i < p->n_devices; i++) {
struct acp_device *d = p->devices[i];
if (!(d->flags & ACP_DEVICE_ACTIVE))
continue;
index = acp_device_find_best_port_index(d, NULL);
acp_device_set_port(d, index);
}
}
} }
static void on_volume_changed(void *data, struct acp_device *dev) static void on_volume_changed(void *data, struct acp_device *dev)