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;
pa_alsa_jack *jack;
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
* 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;
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) {
has_input_port = true;
if (port->port.available != ACP_AVAILABLE_NO)
found_available_input_port = true;
} 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;
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);
}
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);
return 0;
@ -570,7 +571,6 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
return 0;
}
static void init_eld_ctls(pa_card *impl)
{
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;
int32_t best_alt = -1, best = -1;
struct acp_card_profile **profiles = impl->card.profiles;
uint32_t best_alt = ACP_INVALID_INDEX, best = ACP_INVALID_INDEX;
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];
if (profile) {
if (strcmp(profile, p->name))
if (name) {
if (strcmp(name, p->name))
best = i;
continue;
}
if (p->available == ACP_AVAILABLE_NO) {
if (best_alt == -1 || p->priority > profiles[best_alt]->priority)
best_alt = i;
} else {
if (best == -1 || p->priority > profiles[best]->priority)
if (p->available != ACP_AVAILABLE_NO) {
if (best == ACP_INVALID_INDEX || p->priority > profiles[best]->priority)
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;
if (best == -1)
return -ENOENT;
return acp_card_set_profile(&impl->card, best);
if (best == ACP_INVALID_INDEX)
best = 0;
return 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;
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 (!(dev->mixer_path = pa_alsa_path_synthesize(element, dev->direction)))
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_alsa_path_dump(dev->mixer_path);
}
return;
fail:
if (dev->mixer_path) {
pa_alsa_path_free(dev->mixer_path);
dev->mixer_path = NULL;
@ -716,9 +713,10 @@ static int read_volume(pa_alsa_device *dev)
pa_card *impl = dev->card;
pa_cvolume r;
uint32_t i;
int res;
if (pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r) < 0)
return -1;
if ((res = pa_alsa_path_get_volume(dev->mixer_path, dev->mixer_handle, &dev->mapping->channel_map, &r)) < 0)
return res;
/* Shift down by the base volume, so that 0dB becomes maximum 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;
bool mute;
int res;
if (pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute) < 0)
return -1;
if ((res = pa_alsa_path_get_mute(dev->mixer_path, dev->mixer_handle, &mute)) < 0)
return res;
if (mute == dev->muted)
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);
}
static void mixer_volume_init(pa_alsa_device *dev) {
static void mixer_volume_init(pa_alsa_device *dev)
{
pa_assert(dev);
if (!dev->mixer_path || !dev->mixer_path->has_volume) {
dev->read_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->n_volume_steps = PA_VOLUME_NORM+1;
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;
/* 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
* executed. */
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)
return -1;
if ((res = pa_alsa_ucm_set_port(dev->ucm_context, dev->active_port,
dev->direction == PA_ALSA_DIRECTION_OUTPUT)) < 0)
return res;
}
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
* one that has been chosen as active */
data = PA_DEVICE_PORT_DATA(dev->active_port);
dev->mixer_path = data->path;
@ -915,14 +918,13 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) {
}
}
} else {
if (!dev->mixer_path && dev->mixer_path_set)
dev->mixer_path = pa_hashmap_first(dev->mixer_path_set->paths);
if (dev->mixer_path) {
/* Hmm, we have only a single path, then let's activate it */
pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings, dev->mixer_handle, dev->muted);
pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings,
dev->mixer_handle, dev->muted);
} else
return 0;
}
@ -949,7 +951,6 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) {
else
pa_alsa_path_set_callback(dev->mixer_path, dev->mixer_handle, mixer_callback, dev);
}
return 0;
}
@ -959,39 +960,17 @@ static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_devic
if (dev->active_port) {
dev->active_port->port.flags &= ~ACP_PORT_ACTIVE;
dev->active_port = NULL;
dev->device.active_port_index = ACP_INVALID_INDEX;
}
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)
{
const char *mod_name;
bool ignore_dB = false;
uint32_t port_index;
int res;
if (impl->use_ucm &&
(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);
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)
dev->active_port->port.flags |= ACP_PORT_ACTIVE;
if (setup_mixer(impl, dev, ignore_dB) < 0)
return -1;
if ((res = setup_mixer(impl, dev, ignore_dB)) < 0)
return res;
if (dev->read_volume)
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)
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];
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;
char device_id[16];
bool ignore_dB = false;
uint32_t profile_index;
impl = calloc(1, sizeof(*impl));
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->index = index;
card->active_profile_index = (uint32_t)-1;
card->active_profile_index = ACP_INVALID_INDEX;
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);
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);
@ -1309,8 +1292,35 @@ static void sync_mixer(pa_alsa_device *d, pa_device_port *port)
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)
{

View file

@ -42,6 +42,8 @@ extern "C" {
#define ACP_PRINTF_FUNC(fmt, arg1)
#endif
#define ACP_INVALID_INDEX ((uint32_t)-1)
struct acp_dict_item {
const char *key;
const char *value;
@ -201,8 +203,9 @@ struct acp_device {
float base_volume;
float volume_step;
struct acp_port **ports;
uint32_t n_ports;
uint32_t active_port_index;
struct acp_port **ports;
};
struct acp_card_profile {
@ -227,8 +230,8 @@ struct acp_card {
struct acp_dict props;
uint32_t n_profiles;
struct acp_card_profile **profiles;
uint32_t active_profile_index;
struct acp_card_profile **profiles;
uint32_t n_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);
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);
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_volume(struct acp_device *dev, const float *volume, uint32_t n_volume);

View file

@ -52,15 +52,21 @@
#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 {
char device[64];
bool auto_profile;
bool auto_port;
};
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 {
@ -661,6 +667,11 @@ static void card_profile_available(void *data, uint32_t index,
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
this->params[IDX_EnumProfile].flags ^= SPA_PARAM_INFO_SERIAL;
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)
@ -689,6 +700,19 @@ static void card_port_available(void *data, uint32_t index,
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
this->params[IDX_EnumRoute].flags ^= SPA_PARAM_INFO_SERIAL;
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)