services/pipewire: consider device volume step when sending updates

Previously a hardcoded 0.0001 offset was used to determine if a volume
change was significant enough to send to a device, however some
devices have a much more granular step size, which caused future
volume updates to be blocked.

This change replaces the hardcoded offset with the volumeStep device
route property which should be large enough for the device to work with.

Fixes #279
This commit is contained in:
outfoxxed 2025-10-01 00:29:45 -07:00
parent f5ca8453c0
commit 522d126d1b
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
3 changed files with 39 additions and 23 deletions

View file

@ -4,6 +4,7 @@
## Bug Fixes ## Bug Fixes
- Fixed volumes getting stuck on change for pipewire devices with few volume steps.
- Fixed a crash when running out of disk space to write log files. - Fixed a crash when running out of disk space to write log files.
- Fixed a rare crash when disconnecting a monitor. - Fixed a rare crash when disconnecting a monitor.
- Fixed build issues preventing cross compilation from working. - Fixed build issues preventing cross compilation from working.

View file

@ -304,6 +304,8 @@ void PwNodeBoundAudio::updateVolumeProps(const PwVolumeProps& volumeProps) {
return; return;
} }
this->volumeStep = volumeProps.volumeStep;
// It is important that the lengths of channels and volumes stay in sync whenever you read them. // It is important that the lengths of channels and volumes stay in sync whenever you read them.
auto channelsChanged = false; auto channelsChanged = false;
auto volumesChanged = false; auto volumesChanged = false;
@ -435,11 +437,12 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
<< "via device"; << "via device";
this->waitingVolumes = realVolumes; this->waitingVolumes = realVolumes;
} else { } else {
if (this->volumeStep != -1) {
auto significantChange = this->mServerVolumes.isEmpty(); auto significantChange = this->mServerVolumes.isEmpty();
for (auto i = 0; i < this->mServerVolumes.length(); i++) { for (auto i = 0; i < this->mServerVolumes.length(); i++) {
auto serverVolume = this->mServerVolumes.value(i); auto serverVolume = this->mServerVolumes.value(i);
auto targetVolume = realVolumes.value(i); auto targetVolume = realVolumes.value(i);
if (targetVolume == 0 || abs(targetVolume - serverVolume) >= 0.0001) { if (targetVolume == 0 || abs(targetVolume - serverVolume) >= this->volumeStep) {
significantChange = true; significantChange = true;
break; break;
} }
@ -457,9 +460,12 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
} else { } else {
// Insignificant changes won't cause an info event on the device, leaving qs hung in the // Insignificant changes won't cause an info event on the device, leaving qs hung in the
// "waiting for acknowledgement" state forever. // "waiting for acknowledgement" state forever.
qCInfo(logNode) << "Ignoring volume change for" << this->node << "to" << realVolumes qCInfo(logNode).nospace()
<< "from" << this->mServerVolumes << "Ignoring volume change for " << this->node << " to " << realVolumes << " from "
<< "as it is a device node and the change is too small."; << this->mServerVolumes
<< " as it is a device node and the change is too small (min step: "
<< this->volumeStep << ").";
}
} }
} }
} else { } else {
@ -519,6 +525,7 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes); const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap); const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap);
const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute); const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute);
const auto* volumeStepProp = spa_pod_find_prop(param, nullptr, SPA_PROP_volumeStep);
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value); const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value);
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value); const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value);
@ -537,6 +544,12 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
spa_pod_get_bool(&muteProp->value, &props.mute); spa_pod_get_bool(&muteProp->value, &props.mute);
if (volumeStepProp) {
spa_pod_get_float(&volumeStepProp->value, &props.volumeStep);
} else {
props.volumeStep = -1;
}
return props; return props;
} }

View file

@ -158,6 +158,7 @@ struct PwVolumeProps {
QVector<PwAudioChannel::Enum> channels; QVector<PwAudioChannel::Enum> channels;
QVector<float> volumes; QVector<float> volumes;
bool mute = false; bool mute = false;
float volumeStep = -1;
static PwVolumeProps parseSpaPod(const spa_pod* param); static PwVolumeProps parseSpaPod(const spa_pod* param);
}; };
@ -214,6 +215,7 @@ private:
QVector<float> mServerVolumes; QVector<float> mServerVolumes;
QVector<float> mDeviceVolumes; QVector<float> mDeviceVolumes;
QVector<float> waitingVolumes; QVector<float> waitingVolumes;
float volumeStep = -1;
PwNode* node; PwNode* node;
}; };