Skip to content

Commit 79b22af

Browse files
committed
service/pipewire: avoid overloading devices with volume changes
Wait until in-flight changes have been responded to before sending more.
1 parent c60871a commit 79b22af

File tree

4 files changed

+140
-71
lines changed

4 files changed

+140
-71
lines changed

src/services/pipewire/device.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <qlogging.h>
99
#include <qloggingcategory.h>
1010
#include <qobject.h>
11+
#include <qtmetamacros.h>
1112
#include <qtypes.h>
1213
#include <spa/param/param.h>
1314
#include <spa/param/props.h>
@@ -37,6 +38,7 @@ void PwDevice::unbindHooks() {
3738
this->listener.remove();
3839
this->stagingIndexes.clear();
3940
this->routeDeviceIndexes.clear();
41+
this->mWaitingForDevice = false;
4042
}
4143

4244
const pw_device_events PwDevice::EVENTS = {
@@ -56,6 +58,7 @@ void PwDevice::onInfo(void* data, const pw_device_info* info) {
5658
if ((param.flags & SPA_PARAM_INFO_READWRITE) == SPA_PARAM_INFO_READWRITE) {
5759
qCDebug(logDevice) << "Enumerating routes param for" << self;
5860
self->stagingIndexes.clear();
61+
self->deviceResponded = false;
5962
pw_device_enum_params(self->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
6063
} else {
6164
qCWarning(logDevice) << "Unable to enumerate route param for" << self
@@ -73,12 +76,21 @@ void PwDevice::onParam(
7376
qint32 /*seq*/,
7477
quint32 id,
7578
quint32 /*index*/,
76-
quint32 next,
79+
quint32 /*next*/,
7780
const spa_pod* param
7881
) {
7982
auto* self = static_cast<PwDevice*>(data);
8083

8184
if (id == SPA_PARAM_Route) {
85+
if (!self->deviceResponded) {
86+
self->deviceResponded = true;
87+
88+
if (self->mWaitingForDevice) {
89+
self->mWaitingForDevice = false;
90+
emit self->deviceReady();
91+
}
92+
}
93+
8294
self->addDeviceIndexPairs(param);
8395
}
8496
}
@@ -131,7 +143,7 @@ bool PwDevice::setVolumes(qint32 routeDevice, const QVector<float>& volumes) {
131143
);
132144
// clang-format on
133145

134-
qCInfo(logDevice) << "Changed volumes of" << this << "on route device" << routeDevice << "to"
146+
qCInfo(logDevice) << "Changing volumes of" << this << "on route device" << routeDevice << "to"
135147
<< volumes;
136148
return props;
137149
});
@@ -146,12 +158,15 @@ bool PwDevice::setMuted(qint32 routeDevice, bool muted) {
146158
);
147159
// clang-format on
148160

149-
qCInfo(logDevice) << "Changed muted state of" << this << "on route device" << routeDevice
161+
qCInfo(logDevice) << "Changing muted state of" << this << "on route device" << routeDevice
150162
<< "to" << muted;
151163
return props;
152164
});
153165
}
154166

167+
void PwDevice::waitForDevice() { this->mWaitingForDevice = true; }
168+
bool PwDevice::waitingForDevice() const { return this->mWaitingForDevice; }
169+
155170
bool PwDevice::setRouteProps(
156171
qint32 routeDevice,
157172
const std::function<void*(spa_pod_builder*)>& propsCallback

src/services/pipewire/device.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ class PwDevice: public PwBindable<pw_device, TYPE_INTERFACE_Device, PW_VERSION_D
2828
bool setVolumes(qint32 routeDevice, const QVector<float>& volumes);
2929
bool setMuted(qint32 routeDevice, bool muted);
3030

31+
void waitForDevice();
32+
[[nodiscard]] bool waitingForDevice() const;
33+
34+
signals:
35+
void deviceReady();
36+
3137
private slots:
3238
void polled();
3339

@@ -44,6 +50,8 @@ private slots:
4450
bool
4551
setRouteProps(qint32 routeDevice, const std::function<void*(spa_pod_builder*)>& propsCallback);
4652

53+
bool mWaitingForDevice = false;
54+
bool deviceResponded = false;
4755
SpaHook listener;
4856
};
4957

src/services/pipewire/node.cpp

Lines changed: 99 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include <spa/param/props.h>
1919
#include <spa/pod/builder.h>
2020
#include <spa/pod/iter.h>
21-
#include <spa/pod/parser.h>
2221
#include <spa/pod/pod.h>
2322
#include <spa/pod/vararg.h>
2423
#include <spa/utils/dict.h>
@@ -216,98 +215,79 @@ void PwNode::onParam(
216215
}
217216
}
218217

218+
PwNodeBoundAudio::PwNodeBoundAudio(PwNode* node): node(node) {
219+
if (node->device) {
220+
QObject::connect(node->device, &PwDevice::deviceReady, this, &PwNodeBoundAudio::onDeviceReady);
221+
}
222+
}
223+
219224
void PwNodeBoundAudio::onInfo(const pw_node_info* info) {
220225
if ((info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) != 0) {
221226
for (quint32 i = 0; i < info->n_params; i++) {
222227
auto& param = info->params[i]; // NOLINT
223228

224-
if (param.id == SPA_PARAM_Props && (param.flags & SPA_PARAM_INFO_READ) != 0) {
225-
pw_node_enum_params(this->node->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
229+
if (param.id == SPA_PARAM_Props) {
230+
if ((param.flags & SPA_PARAM_INFO_READWRITE) == SPA_PARAM_INFO_READWRITE) {
231+
qCDebug(logNode) << "Enumerating props param for" << this;
232+
pw_node_enum_params(this->node->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
233+
} else {
234+
qCWarning(logNode) << "Unable to enumerate props param for" << this
235+
<< "as the param does not have read+write permissions.";
236+
}
226237
}
227238
}
228239
}
229240
}
230241

231242
void PwNodeBoundAudio::onSpaParam(quint32 id, quint32 index, const spa_pod* param) {
232243
if (id == SPA_PARAM_Props && index == 0) {
233-
this->updateVolumeFromParam(param);
234-
this->updateMutedFromParam(param);
244+
this->updateVolumeProps(param);
235245
}
236246
}
237247

238-
void PwNodeBoundAudio::updateVolumeFromParam(const spa_pod* param) {
239-
const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
240-
const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap);
241-
242-
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value); // NOLINT
243-
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value); // NOLINT
244-
245-
auto volumesVec = QVector<float>();
246-
auto channelsVec = QVector<PwAudioChannel::Enum>();
248+
void PwNodeBoundAudio::updateVolumeProps(const spa_pod* param) {
249+
auto volumeProps = PwVolumeProps::parseSpaPod(param);
247250

248-
spa_pod* iter = nullptr;
249-
SPA_POD_ARRAY_FOREACH(volumes, iter) {
250-
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
251-
auto linear = *reinterpret_cast<float*>(iter); // NOLINT
252-
auto visual = std::cbrt(linear);
253-
volumesVec.push_back(visual);
254-
}
255-
256-
SPA_POD_ARRAY_FOREACH(channels, iter) {
257-
channelsVec.push_back(*reinterpret_cast<PwAudioChannel::Enum*>(iter)); // NOLINT
258-
}
259-
260-
if (volumesVec.size() != channelsVec.size()) {
251+
if (volumeProps.volumes.size() != volumeProps.channels.size()) {
261252
qCWarning(logNode) << "Cannot update volume props of" << this->node
262253
<< "- channelVolumes and channelMap are not the same size. Sizes:"
263-
<< volumesVec.size() << channelsVec.size();
254+
<< volumeProps.volumes.size() << volumeProps.channels.size();
264255
return;
265256
}
266257

267258
// It is important that the lengths of channels and volumes stay in sync whenever you read them.
268259
auto channelsChanged = false;
269260
auto volumesChanged = false;
261+
auto mutedChanged = false;
270262

271-
if (this->mChannels != channelsVec) {
272-
this->mChannels = channelsVec;
263+
if (this->mChannels != volumeProps.channels) {
264+
this->mChannels = volumeProps.channels;
273265
channelsChanged = true;
274266
qCInfo(logNode) << "Got updated channels of" << this->node << '-' << this->mChannels;
275267
}
276268

277-
if (this->mVolumes != volumesVec) {
278-
this->mVolumes = volumesVec;
269+
if (this->mVolumes != volumeProps.volumes) {
270+
this->mVolumes = volumeProps.volumes;
279271
volumesChanged = true;
280272
qCInfo(logNode) << "Got updated volumes of" << this->node << '-' << this->mVolumes;
281273
}
282274

275+
if (volumeProps.mute != this->mMuted) {
276+
this->mMuted = volumeProps.mute;
277+
mutedChanged = true;
278+
qCInfo(logNode) << "Got updated mute status of" << this->node << '-' << volumeProps.mute;
279+
}
280+
283281
if (channelsChanged) emit this->channelsChanged();
284282
if (volumesChanged) emit this->volumesChanged();
285-
}
286-
287-
void PwNodeBoundAudio::updateMutedFromParam(const spa_pod* param) {
288-
auto parser = spa_pod_parser();
289-
spa_pod_parser_pod(&parser, param);
290-
291-
auto muted = false;
292-
293-
// clang-format off
294-
quint32 id = SPA_PARAM_Props;
295-
spa_pod_parser_get_object(
296-
&parser, SPA_TYPE_OBJECT_Props, &id,
297-
SPA_PROP_mute, SPA_POD_Bool(&muted)
298-
);
299-
// clang-format on
300-
301-
if (muted != this->mMuted) {
302-
qCInfo(logNode) << "Got updated mute status of" << this->node << '-' << muted;
303-
this->mMuted = muted;
304-
emit this->mutedChanged();
305-
}
283+
if (mutedChanged) emit this->mutedChanged();
306284
}
307285

308286
void PwNodeBoundAudio::onUnbind() {
309287
this->mChannels.clear();
310288
this->mVolumes.clear();
289+
this->mDeviceVolumes.clear();
290+
this->waitingVolumes.clear();
311291
emit this->channelsChanged();
312292
emit this->volumesChanged();
313293
}
@@ -323,11 +303,10 @@ void PwNodeBoundAudio::setMuted(bool muted) {
323303
if (muted == this->mMuted) return;
324304

325305
if (this->node->device) {
306+
qCInfo(logNode) << "Changing muted state of" << this->node << "to" << muted << "via device";
326307
if (!this->node->device->setMuted(this->node->routeDevice, muted)) {
327308
return;
328309
}
329-
330-
qCInfo(logNode) << "Changed muted state of" << this->node << "to" << muted << "via device";
331310
} else {
332311
auto buffer = std::array<quint8, 1024>();
333312
auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
@@ -340,7 +319,7 @@ void PwNodeBoundAudio::setMuted(bool muted) {
340319
);
341320
// clang-format on
342321

343-
qCInfo(logNode) << "Changed muted state of" << this->node << "to" << muted;
322+
qCInfo(logNode) << "Changed muted state of" << this->node << "to" << muted << "via node";
344323
pw_node_set_param(this->node->proxy(), SPA_PARAM_Props, 0, static_cast<spa_pod*>(pod));
345324
}
346325

@@ -381,9 +360,14 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
381360
return;
382361
}
383362

384-
if (volumes == this->mVolumes) return;
363+
auto realVolumes = QVector<float>();
364+
for (auto volume: volumes) {
365+
realVolumes.push_back(volume < 0 ? 0 : volume);
366+
}
367+
368+
if (realVolumes == this->mVolumes) return;
385369

386-
if (volumes.length() != this->mVolumes.length()) {
370+
if (realVolumes.length() != this->mVolumes.length()) {
387371
qCCritical(logNode) << "Tried to change node volumes for" << this->node << "from"
388372
<< this->mVolumes << "to" << volumes
389373
<< "which has a different length than the list of channels"
@@ -392,17 +376,25 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
392376
}
393377

394378
if (this->node->device) {
395-
if (!this->node->device->setVolumes(this->node->routeDevice, volumes)) {
396-
return;
397-
}
379+
if (this->node->device->waitingForDevice()) {
380+
qCInfo(logNode) << "Waiting to change volumes of" << this->node << "to" << realVolumes
381+
<< "via device";
382+
this->waitingVolumes = realVolumes;
383+
} else {
384+
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes << "via device";
385+
if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) {
386+
return;
387+
}
398388

399-
qCInfo(logNode) << "Changed volumes of" << this->node << "to" << volumes << "via device";
389+
this->mDeviceVolumes = realVolumes;
390+
this->node->device->waitForDevice();
391+
}
400392
} else {
401393
auto buffer = std::array<quint8, 1024>();
402394
auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
403395

404396
auto cubedVolumes = QVector<float>();
405-
for (auto volume: volumes) {
397+
for (auto volume: realVolumes) {
406398
cubedVolumes.push_back(volume * volume * volume);
407399
}
408400

@@ -413,12 +405,54 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
413405
);
414406
// clang-format on
415407

416-
qCInfo(logNode) << "Changed volumes of" << this->node << "to" << volumes;
408+
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << volumes << "via node";
417409
pw_node_set_param(this->node->proxy(), SPA_PARAM_Props, 0, static_cast<spa_pod*>(pod));
418410
}
419411

420-
this->mVolumes = volumes;
412+
this->mVolumes = realVolumes;
421413
emit this->volumesChanged();
422414
}
423415

416+
void PwNodeBoundAudio::onDeviceReady() {
417+
if (!this->waitingVolumes.isEmpty()) {
418+
if (this->waitingVolumes != this->mDeviceVolumes) {
419+
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << this->waitingVolumes
420+
<< "via device (delayed)";
421+
422+
this->node->device->setVolumes(this->node->routeDevice, this->waitingVolumes);
423+
this->mDeviceVolumes = this->waitingVolumes;
424+
this->mVolumes = this->waitingVolumes;
425+
}
426+
427+
this->waitingVolumes.clear();
428+
}
429+
}
430+
431+
PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
432+
auto props = PwVolumeProps();
433+
434+
const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
435+
const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap);
436+
const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute);
437+
438+
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value); // NOLINT
439+
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value); // NOLINT
440+
441+
spa_pod* iter = nullptr;
442+
SPA_POD_ARRAY_FOREACH(volumes, iter) {
443+
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
444+
auto linear = *reinterpret_cast<float*>(iter); // NOLINT
445+
auto visual = std::cbrt(linear);
446+
props.volumes.push_back(visual);
447+
}
448+
449+
SPA_POD_ARRAY_FOREACH(channels, iter) {
450+
props.channels.push_back(*reinterpret_cast<PwAudioChannel::Enum*>(iter)); // NOLINT
451+
}
452+
453+
spa_pod_get_bool(&muteProp->value, &props.mute);
454+
455+
return props;
456+
}
457+
424458
} // namespace qs::service::pipewire

0 commit comments

Comments
 (0)