spa: improve volume handling

Make a new softVolume property that contains only the soft volume
to apply.

In the case of HW/SW volume, we pass the real volume in the
channelVolume and the leftover volume in softVolume. We don't
use the monitorVolume for this anymore because it is a completely
separate volume handled by the merger node.

This way, channelVolume always represents the effective volume
set on routes, channelmix and merger and only the softVolume (when
available) is applied as software volume by channelmix.

This makes things map a bit better to what is actually happening with
the real volume and leftover software volumes after applying the
hardware volumes in the device.

With this change, the volume on the monitor is not affected by the
sink volume anymore and we can use the monitorVolume for this later.

This also means that the monitor volume in pavucontrol of the sinks
does not change when the sink volume changes. PulseAudio is inconsistent
here: If the volume is HW, the monitor volume is not affected, if the
volume is SW, it is. In PipeWire there is an option in merger to
let the volume affect the monitor with monitor.channel-volumes = true.
This commit is contained in:
Wim Taymans 2021-04-29 12:27:55 +02:00
parent 48a6cc2575
commit 5bf2144438
7 changed files with 129 additions and 56 deletions

View file

@ -86,6 +86,9 @@ enum spa_prop {
SPA_PROP_monitorVolumes, /**< a volume array, one volume per SPA_PROP_monitorVolumes, /**< a volume array, one volume per
* channel (Array of Float) */ * channel (Array of Float) */
SPA_PROP_latencyOffsetNsec, /**< delay adjustment */ 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_START_Video = 0x20000, /**< video related properties */
SPA_PROP_brightness, SPA_PROP_brightness,

View file

@ -68,8 +68,8 @@ static const struct spa_type_info spa_type_param[] = {
#include <spa/param/video/type-info.h> #include <spa/param/video/type-info.h>
#include <spa/param/bluetooth/type-info.h> #include <spa/param/bluetooth/type-info.h>
static const struct spa_type_info spa_type_prop_channel_volume[] = { static const struct spa_type_info spa_type_prop_float_array[] = {
{ SPA_PROP_START, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "channelVolumes", NULL, }, { SPA_PROP_START, SPA_TYPE_Float, SPA_TYPE_INFO_BASE "floatArray", NULL, },
{ 0, 0, NULL, NULL }, { 0, 0, NULL, NULL },
}; };
@ -78,11 +78,6 @@ static const struct spa_type_info spa_type_prop_channel_map[] = {
{ 0, 0, NULL, NULL }, { 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[] = { 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_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 }, { 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_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_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_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_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_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_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_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_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_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL },
{ SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, { SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL },

View file

@ -405,11 +405,14 @@ static struct spa_pod *build_route(struct spa_pod_builder *b, uint32_t id,
if (dev != NULL) { if (dev != NULL) {
uint32_t channels = dev->format.channels; uint32_t channels = dev->format.channels;
float volumes[channels]; float volumes[channels];
float soft_volumes[channels];
bool mute; bool mute;
acp_device_get_mute(dev, &mute); acp_device_get_mute(dev, &mute);
spa_zero(volumes); spa_zero(volumes);
spa_zero(soft_volumes);
acp_device_get_volume(dev, volumes, channels); 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_prop(b, SPA_PARAM_ROUTE_device, 0);
spa_pod_builder_int(b, dev->index); 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, spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id,
channels, dev->format.map); 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_pop(b, &f[1]);
} }
spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); 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]; struct spa_pod_frame f[1];
uint32_t n_volume = dev->format.channels; uint32_t n_volume = dev->format.channels;
float volume[n_volume]; 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); spa_log_info(this->log, "device %s volume changed", dev->name);
this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS; this->info.change_mask |= SPA_DEVICE_CHANGE_MASK_PARAMS;
this->params[IDX_Route].user++; this->params[IDX_Route].user++;
spa_zero(volume); spa_zero(volume);
spa_zero(mon_volume); spa_zero(soft_volume);
acp_device_get_soft_volume(dev, volume, n_volume); acp_device_get_volume(dev, volume, n_volume);
acp_device_get_volume(dev, mon_volume, n_volume); acp_device_get_soft_volume(dev, soft_volume, n_volume);
spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_init(&b, buffer, sizeof(buffer));
spa_pod_builder_push_object(&b, &f[0], 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_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props,
SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float),
SPA_TYPE_Float, n_volume, volume), 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_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, dev->format.channels, 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]); event = spa_pod_builder_pop(&b, &f[0]);
spa_device_emit_event(&this->hooks, event); spa_device_emit_event(&this->hooks, event);

View file

@ -59,26 +59,39 @@ struct impl;
#define DEFAULT_MUTE false #define DEFAULT_MUTE false
#define DEFAULT_VOLUME 1.0f #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 { struct props {
float volume; float volume;
bool mute;
uint32_t n_channel_volumes;
float channel_volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_channels; uint32_t n_channels;
uint32_t channel_map[SPA_AUDIO_MAX_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) static void props_reset(struct props *props)
{ {
uint32_t i; uint32_t i;
props->mute = DEFAULT_MUTE;
props->volume = DEFAULT_VOLUME; 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; props->n_channels = 0;
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN; props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN;
init_volumes(&props->channel);
init_volumes(&props->soft);
} }
struct buffer { struct buffer {
@ -212,9 +225,26 @@ static uint64_t default_mask(uint32_t channels)
} }
return mask; 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; 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; struct props *p = &this->props;
uint32_t i, j, target = info->info.raw.channels; 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; continue;
if (i != j) { if (i != j) {
SPA_SWAP(p->channel_map[i], p->channel_map[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; 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++) for (i = 0; i < p->n_channels; i++)
p->channel_map[i] = info->info.raw.position[i]; p->channel_map[i] = info->info.raw.position[i];
if (target == 0 || p->n_channel_volumes == target) if (target == 0)
return 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; 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, static int setup_convert(struct impl *this,
enum spa_direction direction, enum spa_direction direction,
const struct spa_audio_info *info) const struct spa_audio_info *info)
@ -310,9 +350,7 @@ static int setup_convert(struct impl *this,
return res; return res;
remap_volumes(this, src_info); remap_volumes(this, src_info);
set_volume(this);
channelmix_set_volume(&this->mix, this->props.volume, this->props.mute,
this->props.n_channel_volumes, this->props.channel_volumes);
emit_props_changed(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_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute), SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute),
SPA_PROP_INFO_name, SPA_POD_String("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; break;
case 2: case 2:
param = spa_pod_builder_add_object(&b, 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_type, SPA_POD_Id(SPA_AUDIO_CHANNEL_UNKNOWN),
SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array)); SPA_PROP_INFO_container, SPA_POD_Id(SPA_TYPE_Array));
break; 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: default:
return 0; return 0;
} }
@ -397,15 +443,19 @@ static int impl_node_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b, param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id, SPA_TYPE_OBJECT_Props, id,
SPA_PROP_volume, SPA_POD_Float(p->volume), 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_PROP_channelVolumes, SPA_POD_Array(sizeof(float),
SPA_TYPE_Float, SPA_TYPE_Float,
p->n_channel_volumes, p->channel.n_volumes,
p->channel_volumes), p->channel.volumes),
SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, SPA_TYPE_Id,
p->n_channels, 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; break;
default: default:
return 0; return 0;
@ -434,6 +484,8 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
struct props *p = &this->props; struct props *p = &this->props;
int changed = 0; int changed = 0;
p->have_soft_volume = false;
SPA_POD_OBJECT_FOREACH(obj, prop) { SPA_POD_OBJECT_FOREACH(obj, prop) {
switch (prop->key) { switch (prop->key) {
case SPA_PROP_volume: case SPA_PROP_volume:
@ -441,12 +493,12 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
changed++; changed++;
break; break;
case SPA_PROP_mute: 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++; changed++;
break; break;
case SPA_PROP_channelVolumes: case SPA_PROP_channelVolumes:
if ((p->n_channel_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, if ((p->channel.n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
p->channel_volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0)
changed++; changed++;
break; break;
case SPA_PROP_channelMap: 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) p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0)
changed++; changed++;
break; 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: default:
break; break;
} }
} }
if (changed && this->mix.set_volume) { if (changed) {
remap_volumes(this, &GET_IN_PORT(this, 0)->format); remap_volumes(this, &GET_IN_PORT(this, 0)->format);
channelmix_set_volume(&this->mix, p->volume, p->mute, set_volume(this);
p->n_channel_volumes, p->channel_volumes);
} }
return changed; return changed;
} }
@ -479,9 +541,7 @@ static int apply_midi(struct impl *this, const struct spa_pod *value)
return 0; return 0;
p->volume = val[2] / 127.0; p->volume = val[2] / 127.0;
if (this->mix.set_volume) set_volume(this);
channelmix_set_volume(&this->mix, p->volume, p->mute,
p->n_channel_volumes, p->channel_volumes);
return 1; return 1;
} }
@ -1299,7 +1359,8 @@ impl_init(const struct spa_handle_factory *factory,
if (strcmp(k, SPA_KEY_AUDIO_POSITION) == 0) if (strcmp(k, SPA_KEY_AUDIO_POSITION) == 0)
this->props.n_channels = parse_position(this->props.channel_map, s, strlen(s)); 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( this->node.iface = SPA_INTERFACE_INIT(
SPA_TYPE_INTERFACE_Node, SPA_TYPE_INTERFACE_Node,

View file

@ -253,6 +253,8 @@ static void emit_volume(struct impl *this, struct node *node)
spa_pod_builder_add_object(&b, spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props, SPA_TYPE_OBJECT_Props, SPA_EVENT_DEVICE_Props,
SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float), 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_TYPE_Float, node->n_channels, node->soft_volumes),
SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t), SPA_PROP_channelMap, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, node->n_channels, node->channels)); SPA_TYPE_Id, node->n_channels, node->channels));

View file

@ -95,13 +95,15 @@ rules = [
#audio.format = "S16LE" #audio.format = "S16LE"
#audio.rate = 44100 #audio.rate = 44100
#audio.position = "FL,FR" #audio.position = "FL,FR"
#session.suspend-timeout-seconds = 5 # 0 disables suspend
#monitor.channel-volumes = false
#api.alsa.period-size = 1024 #api.alsa.period-size = 1024
#api.alsa.headroom = 0 #api.alsa.headroom = 0
#api.alsa.start-delay = 0 #api.alsa.start-delay = 0
#api.alsa.disable-mmap = false #api.alsa.disable-mmap = false
#api.alsa.disable-batch = false #api.alsa.disable-batch = false
#api.alsa.use-chmap = false #api.alsa.use-chmap = false
#session.suspend-timeout-seconds = 5 # 0 disables suspend
} }
} }
} }

View file

@ -105,6 +105,7 @@ rules = [
#channelmix.normalize = false #channelmix.normalize = false
#channelmix.mix-lfe = false #channelmix.mix-lfe = false
#session.suspend-timeout-seconds = 5 # 0 disables suspend #session.suspend-timeout-seconds = 5 # 0 disables suspend
#monitor.channel-volumes = false
# A2DP source role, "input" or "playback" # A2DP source role, "input" or "playback"
# Defaults to "playback", playing stream to speakers # Defaults to "playback", playing stream to speakers