mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
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:
parent
368366b88d
commit
5e368b1ad6
3 changed files with 561 additions and 522 deletions
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue