diff --git a/spa/include/spa/param/props.h b/spa/include/spa/param/props.h index e204a1e66..bd7a9fd22 100644 --- a/spa/include/spa/param/props.h +++ b/spa/include/spa/param/props.h @@ -86,6 +86,9 @@ enum spa_prop { SPA_PROP_monitorVolumes, /**< a volume array, one volume per * channel (Array of Float) */ SPA_PROP_latencyOffsetNsec, /**< delay adjustment */ + SPA_PROP_softMute, /**< mute (Bool) */ + SPA_PROP_softVolumes, /**< a volume array, one volume per + * channel (Array of Float) */ SPA_PROP_START_Video = 0x20000, /**< video related properties */ SPA_PROP_brightness, diff --git a/spa/include/spa/param/type-info.h b/spa/include/spa/param/type-info.h index 114bf371e..84e3c245a 100644 --- a/spa/include/spa/param/type-info.h +++ b/spa/include/spa/param/type-info.h @@ -68,8 +68,8 @@ static const struct spa_type_info spa_type_param[] = { #include #include -static const struct spa_type_info spa_type_prop_channel_volume[] = { - { SPA_PROP_START, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "channelVolumes", NULL, }, +static const struct spa_type_info spa_type_prop_float_array[] = { + { SPA_PROP_START, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "floatArray", NULL, }, { 0, 0, NULL, NULL }, }; @@ -78,11 +78,6 @@ static const struct spa_type_info spa_type_prop_channel_map[] = { { 0, 0, NULL, NULL }, }; -static const struct spa_type_info spa_type_prop_monitor_volume[] = { - { SPA_PROP_START, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "monitorVolumes", NULL, }, - { 0, 0, NULL, NULL }, -}; - static const struct spa_type_info spa_type_props[] = { { SPA_PROP_START, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE, spa_type_param, }, { SPA_PROP_unknown, SPA_TYPE_None, SPA_TYPE_INFO_PROPS_BASE "unknown", NULL }, @@ -108,13 +103,15 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_patternType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "patternType", NULL }, { SPA_PROP_ditherType, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "ditherType", NULL }, { SPA_PROP_truncate, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "truncate", NULL }, - { SPA_PROP_channelVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelVolumes", spa_type_prop_channel_volume }, + { SPA_PROP_channelVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelVolumes", spa_type_prop_float_array }, { SPA_PROP_volumeBase, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeBase", NULL }, { SPA_PROP_volumeStep, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "volumeStep", NULL }, { SPA_PROP_channelMap, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "channelMap", spa_type_prop_channel_map }, { SPA_PROP_monitorMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "monitorMute", NULL }, - { SPA_PROP_monitorVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "monitorVolumes", spa_type_prop_monitor_volume }, + { SPA_PROP_monitorVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "monitorVolumes", spa_type_prop_float_array }, { SPA_PROP_latencyOffsetNsec, SPA_TYPE_Long, SPA_TYPE_INFO_PROPS_BASE "latencyOffsetNsec", NULL }, + { SPA_PROP_softMute, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "softMute", NULL }, + { SPA_PROP_softVolumes, SPA_TYPE_Array, SPA_TYPE_INFO_PROPS_BASE "softVolumes", spa_type_prop_float_array }, { SPA_PROP_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, { SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index a52faa450..683ba84c7 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -405,11 +405,14 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id, if (dev != NULL) { uint32_t channels = dev->format.channels; float volumes[channels]; + float soft_volumes[channels]; bool mute; acp_device_get_mute(dev, &mute); spa_zero(volumes); + spa_zero(soft_volumes); acp_device_get_volume(dev, volumes, channels); + acp_device_get_soft_volume(dev, soft_volumes, channels); spa_pod_builder_prop(b, SPA_PARAM_ROUTE_device, 0); spa_pod_builder_int(b, dev->index); @@ -437,6 +440,10 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id, spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id, channels, dev->format.map); + spa_pod_builder_prop(b, SPA_PROP_softVolumes, 0); + spa_pod_builder_array(b, sizeof(float), SPA_TYPE_Float, + channels, soft_volumes); + spa_pod_builder_pop(b, &f[1]); } spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); @@ -791,16 +798,16 @@ static void on_volume_changed(void *data, struct acp_device *dev) struct spa_pod_frame f[1]; uint32_t n_volume = dev->format.channels; float volume[n_volume]; - float mon_volume[n_volume]; + float soft_volume[n_volume]; spa_log_info(this->log, "device %s volume changed", dev->name); this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->params[IDX_Route].user++; spa_zero(volume); - spa_zero(mon_volume); - acp_device_get_soft_volume(dev, volume, n_volume); - acp_device_get_volume(dev, mon_volume, n_volume); + spa_zero(soft_volume); + acp_device_get_volume(dev, volume, n_volume); + acp_device_get_soft_volume(dev, soft_volume, n_volume); spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_push_object(&b, &f[0], @@ -812,11 +819,11 @@ static void on_volume_changed(void *data, struct acp_device *dev) SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, n_volume, volume), - SPA_PROP_monitorVolumes, SPA_POD_Array(sizeof(float), - SPA_TYPE_Float, n_volume, mon_volume), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, dev->format.channels, - dev->format.map)); + dev->format.map), + SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, n_volume, soft_volume)); event = spa_pod_builder_pop(&b, &f[0]); spa_device_emit_event(&this->hooks, event); diff --git a/spa/plugins/audioconvert/channelmix.c b/spa/plugins/audioconvert/channelmix.c index a7538315c..715a4cf69 100644 --- a/spa/plugins/audioconvert/channelmix.c +++ b/spa/plugins/audioconvert/channelmix.c @@ -59,26 +59,39 @@ struct impl; #define DEFAULT_MUTE false #define DEFAULT_VOLUME 1.0f +struct volumes { + bool mute; + uint32_t n_volumes; + float volumes[SPA_AUDIO_MAX_CHANNELS]; +}; + +static void init_volumes(struct volumes *vol) +{ + uint32_t i; + vol->mute = DEFAULT_MUTE; + vol->n_volumes = 0; + for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) + vol->volumes[i] = DEFAULT_VOLUME; +} + struct props { float volume; - bool mute; - uint32_t n_channel_volumes; - float channel_volumes[SPA_AUDIO_MAX_CHANNELS]; uint32_t n_channels; uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS]; + struct volumes channel; + struct volumes soft; + unsigned int have_soft_volume:1; }; static void props_reset(struct props *props) { uint32_t i; - props->mute = DEFAULT_MUTE; props->volume = DEFAULT_VOLUME; - props->n_channel_volumes = 0; - for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) - props->channel_volumes[i] = 1.0f; props->n_channels = 0; for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + init_volumes(&props->channel); + init_volumes(&props->soft); } struct buffer { @@ -212,9 +225,26 @@ static uint64_t default_mask(uint32_t channels) } return mask; } -static int remap_volumes(struct impl *this, const struct spa_audio_info *info) + +static void fix_volumes(struct volumes *vols, uint32_t channels) { float s; + uint32_t i; + if (vols->n_volumes > 0) { + s = 0.0f; + for (i = 0; i < vols->n_volumes; i++) + s += vols->volumes[i]; + s /= vols->n_volumes; + } else { + s = 1.0f; + } + vols->n_volumes = channels; + for (i = 0; i < vols->n_volumes; i++) + vols->volumes[i] = s; +} + +static int remap_volumes(struct impl *this, const struct spa_audio_info *info) +{ struct props *p = &this->props; uint32_t i, j, target = info->info.raw.channels; @@ -226,7 +256,8 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) continue; if (i != j) { SPA_SWAP(p->channel_map[i], p->channel_map[j]); - SPA_SWAP(p->channel_volumes[i], p->channel_volumes[j]); + SPA_SWAP(p->channel.volumes[i], p->channel.volumes[j]); + SPA_SWAP(p->soft.volumes[i], p->soft.volumes[j]); } break; } @@ -235,23 +266,32 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info) for (i = 0; i < p->n_channels; i++) p->channel_map[i] = info->info.raw.position[i]; - if (target == 0 || p->n_channel_volumes == target) + if (target == 0) return 0; + if (p->channel.n_volumes != target) + fix_volumes(&p->channel, target); + if (p->soft.n_volumes != target) + fix_volumes(&p->soft, target); - if (p->n_channel_volumes > 0) { - s = 0.0f; - for (i = 0; i < p->n_channel_volumes; i++) - s += p->channel_volumes[i]; - s /= p->n_channel_volumes; - } else { - s = 1.0f; - } - p->n_channel_volumes = target; - for (i = 0; i < p->n_channel_volumes; i++) - p->channel_volumes[i] = s; return 1; } +static void set_volume(struct impl *this) +{ + struct volumes *vol; + + if (this->mix.set_volume == NULL) + return; + + if (this->props.have_soft_volume) + vol = &this->props.soft; + else + vol = &this->props.channel; + + channelmix_set_volume(&this->mix, this->props.volume, vol->mute, + vol->n_volumes, vol->volumes); +} + static int setup_convert(struct impl *this, enum spa_direction direction, const struct spa_audio_info *info) @@ -310,9 +350,7 @@ static int setup_convert(struct impl *this, return res; remap_volumes(this, src_info); - - channelmix_set_volume(&this->mix, this->props.volume, this->props.mute, - this->props.n_channel_volumes, this->props.channel_volumes); + set_volume(this); emit_props_changed(this); @@ -365,7 +403,7 @@ static int impl_node_enum_params(void *object, int seq, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), SPA_PROP_INFO_name, SPA_POD_String("Mute"), - SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mute)); + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->channel.mute)); break; case 2: param = spa_pod_builder_add_object(&b, @@ -383,6 +421,14 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN), SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); break; + case 4: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_softVolumes), + SPA_PROP_INFO_name, SPA_POD_String("Soft Volumes"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 10.0), + SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); + break; default: return 0; } @@ -397,15 +443,19 @@ static int impl_node_enum_params(void *object, int seq, param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, id, SPA_PROP_volume, SPA_POD_Float(p->volume), - SPA_PROP_mute, SPA_POD_Bool(p->mute), + SPA_PROP_mute, SPA_POD_Bool(p->channel.mute), SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, - p->n_channel_volumes, - p->channel_volumes), + p->channel.n_volumes, + p->channel.volumes), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, p->n_channels, - p->channel_map)); + p->channel_map), + SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, + p->soft.n_volumes, + p->soft.volumes)); break; default: return 0; @@ -434,6 +484,8 @@ static int apply_props(struct impl *this, const struct spa_pod *param) struct props *p = &this->props; int changed = 0; + p->have_soft_volume = false; + SPA_POD_OBJECT_FOREACH(obj, prop) { switch (prop->key) { case SPA_PROP_volume: @@ -441,12 +493,12 @@ static int apply_props(struct impl *this, const struct spa_pod *param) changed++; break; case SPA_PROP_mute: - if (spa_pod_get_bool(&prop->value, &p->mute) == 0) + if (spa_pod_get_bool(&prop->value, &p->channel.mute) == 0) changed++; break; case SPA_PROP_channelVolumes: - if ((p->n_channel_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, - p->channel_volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) + if ((p->channel.n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) changed++; break; case SPA_PROP_channelMap: @@ -454,14 +506,24 @@ static int apply_props(struct impl *this, const struct spa_pod *param) p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) changed++; break; + case SPA_PROP_softMute: + if (spa_pod_get_bool(&prop->value, &p->soft.mute) == 0) + changed++; + break; + case SPA_PROP_softVolumes: + if ((p->soft.n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, + p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) { + changed++; + p->have_soft_volume = true; + } + break; default: break; } } - if (changed && this->mix.set_volume) { + if (changed) { remap_volumes(this, &GET_IN_PORT(this, 0)->format); - channelmix_set_volume(&this->mix, p->volume, p->mute, - p->n_channel_volumes, p->channel_volumes); + set_volume(this); } return changed; } @@ -479,9 +541,7 @@ static int apply_midi(struct impl *this, const struct spa_pod *value) return 0; p->volume = val[2] / 127.0; - if (this->mix.set_volume) - channelmix_set_volume(&this->mix, p->volume, p->mute, - p->n_channel_volumes, p->channel_volumes); + set_volume(this); return 1; } @@ -1299,7 +1359,8 @@ impl_init(const struct spa_handle_factory *factory, if (strcmp(k, SPA_KEY_AUDIO_POSITION) == 0) this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); } - this->props.n_channel_volumes = this->props.n_channels; + this->props.channel.n_volumes = this->props.n_channels; + this->props.soft.n_volumes = this->props.n_channels; this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 6e2df8a04..e67da5521 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -253,6 +253,8 @@ static void emit_volume(struct impl *this, struct node *node) spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), + SPA_TYPE_Float, node->n_channels, node->volumes), + SPA_PROP_softVolumes, SPA_POD_Array(sizeof(float), SPA_TYPE_Float, node->n_channels, node->soft_volumes), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, node->n_channels, node->channels)); diff --git a/src/daemon/media-session.d/alsa-monitor.conf b/src/daemon/media-session.d/alsa-monitor.conf index d749e7b9a..904d86614 100644 --- a/src/daemon/media-session.d/alsa-monitor.conf +++ b/src/daemon/media-session.d/alsa-monitor.conf @@ -95,13 +95,15 @@ rules = [ #audio.format = "S16LE" #audio.rate = 44100 #audio.position = "FL,FR" + #session.suspend-timeout-seconds = 5 # 0 disables suspend + #monitor.channel-volumes = false + #api.alsa.period-size = 1024 #api.alsa.headroom = 0 #api.alsa.start-delay = 0 #api.alsa.disable-mmap = false #api.alsa.disable-batch = false #api.alsa.use-chmap = false - #session.suspend-timeout-seconds = 5 # 0 disables suspend } } } diff --git a/src/daemon/media-session.d/bluez-monitor.conf b/src/daemon/media-session.d/bluez-monitor.conf index e483378bf..901c8bd56 100644 --- a/src/daemon/media-session.d/bluez-monitor.conf +++ b/src/daemon/media-session.d/bluez-monitor.conf @@ -105,6 +105,7 @@ rules = [ #channelmix.normalize = false #channelmix.mix-lfe = false #session.suspend-timeout-seconds = 5 # 0 disables suspend + #monitor.channel-volumes = false # A2DP source role, "input" or "playback" # Defaults to "playback", playing stream to speakers