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+
219224void 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
231242void 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
308286void 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