18
18
#include < spa/param/props.h>
19
19
#include < spa/pod/builder.h>
20
20
#include < spa/pod/iter.h>
21
- #include < spa/pod/parser.h>
22
21
#include < spa/pod/pod.h>
23
22
#include < spa/pod/vararg.h>
24
23
#include < spa/utils/dict.h>
@@ -216,98 +215,79 @@ void PwNode::onParam(
216
215
}
217
216
}
218
217
218
+ PwNodeBoundAudio::PwNodeBoundAudio (PwNode* node): node(node) {
219
+ if (node->device ) {
220
+ QObject::connect (node->device , &PwDevice::deviceReady, this , &PwNodeBoundAudio::onDeviceReady);
221
+ }
222
+ }
223
+
219
224
void PwNodeBoundAudio::onInfo (const pw_node_info* info) {
220
225
if ((info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) != 0 ) {
221
226
for (quint32 i = 0 ; i < info->n_params ; i++) {
222
227
auto & param = info->params [i]; // NOLINT
223
228
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
+ }
226
237
}
227
238
}
228
239
}
229
240
}
230
241
231
242
void PwNodeBoundAudio::onSpaParam (quint32 id, quint32 index, const spa_pod* param) {
232
243
if (id == SPA_PARAM_Props && index == 0 ) {
233
- this ->updateVolumeFromParam (param);
234
- this ->updateMutedFromParam (param);
244
+ this ->updateVolumeProps (param);
235
245
}
236
246
}
237
247
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);
247
250
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 ()) {
261
252
qCWarning (logNode) << " Cannot update volume props of" << this ->node
262
253
<< " - channelVolumes and channelMap are not the same size. Sizes:"
263
- << volumesVec. size () << channelsVec .size ();
254
+ << volumeProps. volumes . size () << volumeProps. channels .size ();
264
255
return ;
265
256
}
266
257
267
258
// It is important that the lengths of channels and volumes stay in sync whenever you read them.
268
259
auto channelsChanged = false ;
269
260
auto volumesChanged = false ;
261
+ auto mutedChanged = false ;
270
262
271
- if (this ->mChannels != channelsVec ) {
272
- this ->mChannels = channelsVec ;
263
+ if (this ->mChannels != volumeProps. channels ) {
264
+ this ->mChannels = volumeProps. channels ;
273
265
channelsChanged = true ;
274
266
qCInfo (logNode) << " Got updated channels of" << this ->node << ' -' << this ->mChannels ;
275
267
}
276
268
277
- if (this ->mVolumes != volumesVec ) {
278
- this ->mVolumes = volumesVec ;
269
+ if (this ->mVolumes != volumeProps. volumes ) {
270
+ this ->mVolumes = volumeProps. volumes ;
279
271
volumesChanged = true ;
280
272
qCInfo (logNode) << " Got updated volumes of" << this ->node << ' -' << this ->mVolumes ;
281
273
}
282
274
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
+
283
281
if (channelsChanged) emit this ->channelsChanged ();
284
282
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 ();
306
284
}
307
285
308
286
void PwNodeBoundAudio::onUnbind () {
309
287
this ->mChannels .clear ();
310
288
this ->mVolumes .clear ();
289
+ this ->mDeviceVolumes .clear ();
290
+ this ->waitingVolumes .clear ();
311
291
emit this ->channelsChanged ();
312
292
emit this ->volumesChanged ();
313
293
}
@@ -323,11 +303,10 @@ void PwNodeBoundAudio::setMuted(bool muted) {
323
303
if (muted == this ->mMuted ) return ;
324
304
325
305
if (this ->node ->device ) {
306
+ qCInfo (logNode) << " Changing muted state of" << this ->node << " to" << muted << " via device" ;
326
307
if (!this ->node ->device ->setMuted (this ->node ->routeDevice , muted)) {
327
308
return ;
328
309
}
329
-
330
- qCInfo (logNode) << " Changed muted state of" << this ->node << " to" << muted << " via device" ;
331
310
} else {
332
311
auto buffer = std::array<quint8, 1024 >();
333
312
auto builder = SPA_POD_BUILDER_INIT (buffer.data (), buffer.size ());
@@ -340,7 +319,7 @@ void PwNodeBoundAudio::setMuted(bool muted) {
340
319
);
341
320
// clang-format on
342
321
343
- qCInfo (logNode) << " Changed muted state of" << this ->node << " to" << muted;
322
+ qCInfo (logNode) << " Changed muted state of" << this ->node << " to" << muted << " via node " ;
344
323
pw_node_set_param (this ->node ->proxy (), SPA_PARAM_Props, 0 , static_cast <spa_pod*>(pod));
345
324
}
346
325
@@ -381,9 +360,14 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
381
360
return ;
382
361
}
383
362
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 ;
385
369
386
- if (volumes .length () != this ->mVolumes .length ()) {
370
+ if (realVolumes .length () != this ->mVolumes .length ()) {
387
371
qCCritical (logNode) << " Tried to change node volumes for" << this ->node << " from"
388
372
<< this ->mVolumes << " to" << volumes
389
373
<< " which has a different length than the list of channels"
@@ -392,17 +376,25 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
392
376
}
393
377
394
378
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
+ }
398
388
399
- qCInfo (logNode) << " Changed volumes of" << this ->node << " to" << volumes << " via device" ;
389
+ this ->mDeviceVolumes = realVolumes;
390
+ this ->node ->device ->waitForDevice ();
391
+ }
400
392
} else {
401
393
auto buffer = std::array<quint8, 1024 >();
402
394
auto builder = SPA_POD_BUILDER_INIT (buffer.data (), buffer.size ());
403
395
404
396
auto cubedVolumes = QVector<float >();
405
- for (auto volume: volumes ) {
397
+ for (auto volume: realVolumes ) {
406
398
cubedVolumes.push_back (volume * volume * volume);
407
399
}
408
400
@@ -413,12 +405,54 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
413
405
);
414
406
// clang-format on
415
407
416
- qCInfo (logNode) << " Changed volumes of" << this ->node << " to" << volumes;
408
+ qCInfo (logNode) << " Changing volumes of" << this ->node << " to" << volumes << " via node " ;
417
409
pw_node_set_param (this ->node ->proxy (), SPA_PARAM_Props, 0 , static_cast <spa_pod*>(pod));
418
410
}
419
411
420
- this ->mVolumes = volumes ;
412
+ this ->mVolumes = realVolumes ;
421
413
emit this ->volumesChanged ();
422
414
}
423
415
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
+
424
458
} // namespace qs::service::pipewire
0 commit comments