forked from quickshell/quickshell
service/pipewire: avoid overloading devices with volume changes
Wait until in-flight changes have been responded to before sending more.
This commit is contained in:
parent
c60871a7fb
commit
79b22af093
4 changed files with 140 additions and 71 deletions
|
@ -8,6 +8,7 @@
|
|||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <spa/param/param.h>
|
||||
#include <spa/param/props.h>
|
||||
|
@ -37,6 +38,7 @@ void PwDevice::unbindHooks() {
|
|||
this->listener.remove();
|
||||
this->stagingIndexes.clear();
|
||||
this->routeDeviceIndexes.clear();
|
||||
this->mWaitingForDevice = false;
|
||||
}
|
||||
|
||||
const pw_device_events PwDevice::EVENTS = {
|
||||
|
@ -56,6 +58,7 @@ void PwDevice::onInfo(void* data, const pw_device_info* info) {
|
|||
if ((param.flags & SPA_PARAM_INFO_READWRITE) == SPA_PARAM_INFO_READWRITE) {
|
||||
qCDebug(logDevice) << "Enumerating routes param for" << self;
|
||||
self->stagingIndexes.clear();
|
||||
self->deviceResponded = false;
|
||||
pw_device_enum_params(self->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
|
||||
} else {
|
||||
qCWarning(logDevice) << "Unable to enumerate route param for" << self
|
||||
|
@ -73,12 +76,21 @@ void PwDevice::onParam(
|
|||
qint32 /*seq*/,
|
||||
quint32 id,
|
||||
quint32 /*index*/,
|
||||
quint32 next,
|
||||
quint32 /*next*/,
|
||||
const spa_pod* param
|
||||
) {
|
||||
auto* self = static_cast<PwDevice*>(data);
|
||||
|
||||
if (id == SPA_PARAM_Route) {
|
||||
if (!self->deviceResponded) {
|
||||
self->deviceResponded = true;
|
||||
|
||||
if (self->mWaitingForDevice) {
|
||||
self->mWaitingForDevice = false;
|
||||
emit self->deviceReady();
|
||||
}
|
||||
}
|
||||
|
||||
self->addDeviceIndexPairs(param);
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +143,7 @@ bool PwDevice::setVolumes(qint32 routeDevice, const QVector<float>& volumes) {
|
|||
);
|
||||
// clang-format on
|
||||
|
||||
qCInfo(logDevice) << "Changed volumes of" << this << "on route device" << routeDevice << "to"
|
||||
qCInfo(logDevice) << "Changing volumes of" << this << "on route device" << routeDevice << "to"
|
||||
<< volumes;
|
||||
return props;
|
||||
});
|
||||
|
@ -146,12 +158,15 @@ bool PwDevice::setMuted(qint32 routeDevice, bool muted) {
|
|||
);
|
||||
// clang-format on
|
||||
|
||||
qCInfo(logDevice) << "Changed muted state of" << this << "on route device" << routeDevice
|
||||
qCInfo(logDevice) << "Changing muted state of" << this << "on route device" << routeDevice
|
||||
<< "to" << muted;
|
||||
return props;
|
||||
});
|
||||
}
|
||||
|
||||
void PwDevice::waitForDevice() { this->mWaitingForDevice = true; }
|
||||
bool PwDevice::waitingForDevice() const { return this->mWaitingForDevice; }
|
||||
|
||||
bool PwDevice::setRouteProps(
|
||||
qint32 routeDevice,
|
||||
const std::function<void*(spa_pod_builder*)>& propsCallback
|
||||
|
|
|
@ -28,6 +28,12 @@ public:
|
|||
bool setVolumes(qint32 routeDevice, const QVector<float>& volumes);
|
||||
bool setMuted(qint32 routeDevice, bool muted);
|
||||
|
||||
void waitForDevice();
|
||||
[[nodiscard]] bool waitingForDevice() const;
|
||||
|
||||
signals:
|
||||
void deviceReady();
|
||||
|
||||
private slots:
|
||||
void polled();
|
||||
|
||||
|
@ -44,6 +50,8 @@ private:
|
|||
bool
|
||||
setRouteProps(qint32 routeDevice, const std::function<void*(spa_pod_builder*)>& propsCallback);
|
||||
|
||||
bool mWaitingForDevice = false;
|
||||
bool deviceResponded = false;
|
||||
SpaHook listener;
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include <spa/param/props.h>
|
||||
#include <spa/pod/builder.h>
|
||||
#include <spa/pod/iter.h>
|
||||
#include <spa/pod/parser.h>
|
||||
#include <spa/pod/pod.h>
|
||||
#include <spa/pod/vararg.h>
|
||||
#include <spa/utils/dict.h>
|
||||
|
@ -216,13 +215,25 @@ void PwNode::onParam(
|
|||
}
|
||||
}
|
||||
|
||||
PwNodeBoundAudio::PwNodeBoundAudio(PwNode* node): node(node) {
|
||||
if (node->device) {
|
||||
QObject::connect(node->device, &PwDevice::deviceReady, this, &PwNodeBoundAudio::onDeviceReady);
|
||||
}
|
||||
}
|
||||
|
||||
void PwNodeBoundAudio::onInfo(const pw_node_info* info) {
|
||||
if ((info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) != 0) {
|
||||
for (quint32 i = 0; i < info->n_params; i++) {
|
||||
auto& param = info->params[i]; // NOLINT
|
||||
|
||||
if (param.id == SPA_PARAM_Props && (param.flags & SPA_PARAM_INFO_READ) != 0) {
|
||||
pw_node_enum_params(this->node->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
|
||||
if (param.id == SPA_PARAM_Props) {
|
||||
if ((param.flags & SPA_PARAM_INFO_READWRITE) == SPA_PARAM_INFO_READWRITE) {
|
||||
qCDebug(logNode) << "Enumerating props param for" << this;
|
||||
pw_node_enum_params(this->node->proxy(), 0, param.id, 0, UINT32_MAX, nullptr);
|
||||
} else {
|
||||
qCWarning(logNode) << "Unable to enumerate props param for" << this
|
||||
<< "as the param does not have read+write permissions.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,84 +241,53 @@ void PwNodeBoundAudio::onInfo(const pw_node_info* info) {
|
|||
|
||||
void PwNodeBoundAudio::onSpaParam(quint32 id, quint32 index, const spa_pod* param) {
|
||||
if (id == SPA_PARAM_Props && index == 0) {
|
||||
this->updateVolumeFromParam(param);
|
||||
this->updateMutedFromParam(param);
|
||||
this->updateVolumeProps(param);
|
||||
}
|
||||
}
|
||||
|
||||
void PwNodeBoundAudio::updateVolumeFromParam(const spa_pod* param) {
|
||||
const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes);
|
||||
const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap);
|
||||
void PwNodeBoundAudio::updateVolumeProps(const spa_pod* param) {
|
||||
auto volumeProps = PwVolumeProps::parseSpaPod(param);
|
||||
|
||||
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value); // NOLINT
|
||||
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value); // NOLINT
|
||||
|
||||
auto volumesVec = QVector<float>();
|
||||
auto channelsVec = QVector<PwAudioChannel::Enum>();
|
||||
|
||||
spa_pod* iter = nullptr;
|
||||
SPA_POD_ARRAY_FOREACH(volumes, iter) {
|
||||
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
|
||||
auto linear = *reinterpret_cast<float*>(iter); // NOLINT
|
||||
auto visual = std::cbrt(linear);
|
||||
volumesVec.push_back(visual);
|
||||
}
|
||||
|
||||
SPA_POD_ARRAY_FOREACH(channels, iter) {
|
||||
channelsVec.push_back(*reinterpret_cast<PwAudioChannel::Enum*>(iter)); // NOLINT
|
||||
}
|
||||
|
||||
if (volumesVec.size() != channelsVec.size()) {
|
||||
if (volumeProps.volumes.size() != volumeProps.channels.size()) {
|
||||
qCWarning(logNode) << "Cannot update volume props of" << this->node
|
||||
<< "- channelVolumes and channelMap are not the same size. Sizes:"
|
||||
<< volumesVec.size() << channelsVec.size();
|
||||
<< volumeProps.volumes.size() << volumeProps.channels.size();
|
||||
return;
|
||||
}
|
||||
|
||||
// It is important that the lengths of channels and volumes stay in sync whenever you read them.
|
||||
auto channelsChanged = false;
|
||||
auto volumesChanged = false;
|
||||
auto mutedChanged = false;
|
||||
|
||||
if (this->mChannels != channelsVec) {
|
||||
this->mChannels = channelsVec;
|
||||
if (this->mChannels != volumeProps.channels) {
|
||||
this->mChannels = volumeProps.channels;
|
||||
channelsChanged = true;
|
||||
qCInfo(logNode) << "Got updated channels of" << this->node << '-' << this->mChannels;
|
||||
}
|
||||
|
||||
if (this->mVolumes != volumesVec) {
|
||||
this->mVolumes = volumesVec;
|
||||
if (this->mVolumes != volumeProps.volumes) {
|
||||
this->mVolumes = volumeProps.volumes;
|
||||
volumesChanged = true;
|
||||
qCInfo(logNode) << "Got updated volumes of" << this->node << '-' << this->mVolumes;
|
||||
}
|
||||
|
||||
if (volumeProps.mute != this->mMuted) {
|
||||
this->mMuted = volumeProps.mute;
|
||||
mutedChanged = true;
|
||||
qCInfo(logNode) << "Got updated mute status of" << this->node << '-' << volumeProps.mute;
|
||||
}
|
||||
|
||||
if (channelsChanged) emit this->channelsChanged();
|
||||
if (volumesChanged) emit this->volumesChanged();
|
||||
}
|
||||
|
||||
void PwNodeBoundAudio::updateMutedFromParam(const spa_pod* param) {
|
||||
auto parser = spa_pod_parser();
|
||||
spa_pod_parser_pod(&parser, param);
|
||||
|
||||
auto muted = false;
|
||||
|
||||
// clang-format off
|
||||
quint32 id = SPA_PARAM_Props;
|
||||
spa_pod_parser_get_object(
|
||||
&parser, SPA_TYPE_OBJECT_Props, &id,
|
||||
SPA_PROP_mute, SPA_POD_Bool(&muted)
|
||||
);
|
||||
// clang-format on
|
||||
|
||||
if (muted != this->mMuted) {
|
||||
qCInfo(logNode) << "Got updated mute status of" << this->node << '-' << muted;
|
||||
this->mMuted = muted;
|
||||
emit this->mutedChanged();
|
||||
}
|
||||
if (mutedChanged) emit this->mutedChanged();
|
||||
}
|
||||
|
||||
void PwNodeBoundAudio::onUnbind() {
|
||||
this->mChannels.clear();
|
||||
this->mVolumes.clear();
|
||||
this->mDeviceVolumes.clear();
|
||||
this->waitingVolumes.clear();
|
||||
emit this->channelsChanged();
|
||||
emit this->volumesChanged();
|
||||
}
|
||||
|
@ -323,11 +303,10 @@ void PwNodeBoundAudio::setMuted(bool muted) {
|
|||
if (muted == this->mMuted) return;
|
||||
|
||||
if (this->node->device) {
|
||||
qCInfo(logNode) << "Changing muted state of" << this->node << "to" << muted << "via device";
|
||||
if (!this->node->device->setMuted(this->node->routeDevice, muted)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(logNode) << "Changed muted state of" << this->node << "to" << muted << "via device";
|
||||
} else {
|
||||
auto buffer = std::array<quint8, 1024>();
|
||||
auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
|
||||
|
@ -340,7 +319,7 @@ void PwNodeBoundAudio::setMuted(bool muted) {
|
|||
);
|
||||
// clang-format on
|
||||
|
||||
qCInfo(logNode) << "Changed muted state of" << this->node << "to" << muted;
|
||||
qCInfo(logNode) << "Changed muted state of" << this->node << "to" << muted << "via node";
|
||||
pw_node_set_param(this->node->proxy(), SPA_PARAM_Props, 0, static_cast<spa_pod*>(pod));
|
||||
}
|
||||
|
||||
|
@ -381,9 +360,14 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (volumes == this->mVolumes) return;
|
||||
auto realVolumes = QVector<float>();
|
||||
for (auto volume: volumes) {
|
||||
realVolumes.push_back(volume < 0 ? 0 : volume);
|
||||
}
|
||||
|
||||
if (volumes.length() != this->mVolumes.length()) {
|
||||
if (realVolumes == this->mVolumes) return;
|
||||
|
||||
if (realVolumes.length() != this->mVolumes.length()) {
|
||||
qCCritical(logNode) << "Tried to change node volumes for" << this->node << "from"
|
||||
<< this->mVolumes << "to" << volumes
|
||||
<< "which has a different length than the list of channels"
|
||||
|
@ -392,17 +376,25 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
|
|||
}
|
||||
|
||||
if (this->node->device) {
|
||||
if (!this->node->device->setVolumes(this->node->routeDevice, volumes)) {
|
||||
return;
|
||||
}
|
||||
if (this->node->device->waitingForDevice()) {
|
||||
qCInfo(logNode) << "Waiting to change volumes of" << this->node << "to" << realVolumes
|
||||
<< "via device";
|
||||
this->waitingVolumes = realVolumes;
|
||||
} else {
|
||||
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes << "via device";
|
||||
if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(logNode) << "Changed volumes of" << this->node << "to" << volumes << "via device";
|
||||
this->mDeviceVolumes = realVolumes;
|
||||
this->node->device->waitForDevice();
|
||||
}
|
||||
} else {
|
||||
auto buffer = std::array<quint8, 1024>();
|
||||
auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size());
|
||||
|
||||
auto cubedVolumes = QVector<float>();
|
||||
for (auto volume: volumes) {
|
||||
for (auto volume: realVolumes) {
|
||||
cubedVolumes.push_back(volume * volume * volume);
|
||||
}
|
||||
|
||||
|
@ -413,12 +405,54 @@ void PwNodeBoundAudio::setVolumes(const QVector<float>& volumes) {
|
|||
);
|
||||
// clang-format on
|
||||
|
||||
qCInfo(logNode) << "Changed volumes of" << this->node << "to" << volumes;
|
||||
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << volumes << "via node";
|
||||
pw_node_set_param(this->node->proxy(), SPA_PARAM_Props, 0, static_cast<spa_pod*>(pod));
|
||||
}
|
||||
|
||||
this->mVolumes = volumes;
|
||||
this->mVolumes = realVolumes;
|
||||
emit this->volumesChanged();
|
||||
}
|
||||
|
||||
void PwNodeBoundAudio::onDeviceReady() {
|
||||
if (!this->waitingVolumes.isEmpty()) {
|
||||
if (this->waitingVolumes != this->mDeviceVolumes) {
|
||||
qCInfo(logNode) << "Changing volumes of" << this->node << "to" << this->waitingVolumes
|
||||
<< "via device (delayed)";
|
||||
|
||||
this->node->device->setVolumes(this->node->routeDevice, this->waitingVolumes);
|
||||
this->mDeviceVolumes = this->waitingVolumes;
|
||||
this->mVolumes = this->waitingVolumes;
|
||||
}
|
||||
|
||||
this->waitingVolumes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) {
|
||||
auto props = PwVolumeProps();
|
||||
|
||||
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* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute);
|
||||
|
||||
const auto* volumes = reinterpret_cast<const spa_pod_array*>(&volumesProp->value); // NOLINT
|
||||
const auto* channels = reinterpret_cast<const spa_pod_array*>(&channelsProp->value); // NOLINT
|
||||
|
||||
spa_pod* iter = nullptr;
|
||||
SPA_POD_ARRAY_FOREACH(volumes, iter) {
|
||||
// Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly.
|
||||
auto linear = *reinterpret_cast<float*>(iter); // NOLINT
|
||||
auto visual = std::cbrt(linear);
|
||||
props.volumes.push_back(visual);
|
||||
}
|
||||
|
||||
SPA_POD_ARRAY_FOREACH(channels, iter) {
|
||||
props.channels.push_back(*reinterpret_cast<PwAudioChannel::Enum*>(iter)); // NOLINT
|
||||
}
|
||||
|
||||
spa_pod_get_bool(&muteProp->value, &props.mute);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
} // namespace qs::service::pipewire
|
||||
|
|
|
@ -94,6 +94,14 @@ enum class PwNodeType {
|
|||
|
||||
class PwNode;
|
||||
|
||||
struct PwVolumeProps {
|
||||
QVector<PwAudioChannel::Enum> channels;
|
||||
QVector<float> volumes;
|
||||
bool mute = false;
|
||||
|
||||
static PwVolumeProps parseSpaPod(const spa_pod* param);
|
||||
};
|
||||
|
||||
class PwNodeBoundData {
|
||||
public:
|
||||
PwNodeBoundData() = default;
|
||||
|
@ -111,7 +119,7 @@ class PwNodeBoundAudio
|
|||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit PwNodeBoundAudio(PwNode* node): node(node) {}
|
||||
explicit PwNodeBoundAudio(PwNode* node);
|
||||
|
||||
void onInfo(const pw_node_info* info) override;
|
||||
void onSpaParam(quint32 id, quint32 index, const spa_pod* param) override;
|
||||
|
@ -133,13 +141,17 @@ signals:
|
|||
void channelsChanged();
|
||||
void mutedChanged();
|
||||
|
||||
private slots:
|
||||
void onDeviceReady();
|
||||
|
||||
private:
|
||||
void updateVolumeFromParam(const spa_pod* param);
|
||||
void updateMutedFromParam(const spa_pod* param);
|
||||
void updateVolumeProps(const spa_pod* param);
|
||||
|
||||
bool mMuted = false;
|
||||
QVector<PwAudioChannel::Enum> mChannels;
|
||||
QVector<float> mVolumes;
|
||||
QVector<float> mDeviceVolumes;
|
||||
QVector<float> waitingVolumes;
|
||||
PwNode* node;
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue