This commit is contained in:
Joey Yakimowich-Payne 2026-01-27 17:44:09 -07:00
commit 4bf830763f
4 changed files with 285 additions and 3 deletions

View file

@ -2,6 +2,7 @@
#include <QDebug>
#include <QMutexLocker>
#include <QByteArray>
#include <QString>
#include <QElapsedTimer>
#include <QThread>
#include <cstring>
@ -159,6 +160,58 @@ static const struct pw_stream_events meter_events = []() {
return events;
}();
struct NodeMeter {
uint32_t nodeId;
QString targetName;
pw_stream *stream = nullptr;
std::atomic<float> peak{0.0f};
};
static void nodeMeterProcess(void *data)
{
auto *meter = static_cast<NodeMeter*>(data);
if (!meter || !meter->stream) {
return;
}
struct pw_buffer *buf = pw_stream_dequeue_buffer(meter->stream);
if (!buf || !buf->buffer || buf->buffer->n_datas == 0) {
if (buf) {
pw_stream_queue_buffer(meter->stream, buf);
}
return;
}
struct spa_buffer *spaBuf = buf->buffer;
struct spa_data *data0 = &spaBuf->datas[0];
if (!data0->data || !data0->chunk) {
pw_stream_queue_buffer(meter->stream, buf);
return;
}
const uint32_t size = data0->chunk->size;
const float *samples = static_cast<const float*>(data0->data);
const uint32_t count = size / sizeof(float);
float peak = 0.0f;
for (uint32_t i = 0; i < count; ++i) {
const float value = std::fabs(samples[i]);
if (value > peak) {
peak = value;
}
}
meter->peak.store(peak, std::memory_order_relaxed);
pw_stream_queue_buffer(meter->stream, buf);
}
static const struct pw_stream_events node_meter_events = []() {
struct pw_stream_events events{};
events.version = PW_VERSION_STREAM_EVENTS;
events.process = nodeMeterProcess;
return events;
}();
PipeWireController::PipeWireController(QObject *parent)
: QObject(parent)
{
@ -256,6 +309,18 @@ void PipeWireController::shutdown()
}
teardownMeterStream();
{
QMutexLocker lock(&m_meterMutex);
for (auto it = m_nodeMeters.begin(); it != m_nodeMeters.end(); ++it) {
NodeMeter *meter = it.value();
if (meter && meter->stream) {
pw_stream_destroy(meter->stream);
}
delete meter;
}
m_nodeMeters.clear();
}
if (m_core) {
pw_core_disconnect(m_core);
@ -310,6 +375,115 @@ float PipeWireController::meterPeak() const
return m_meterPeak.load(std::memory_order_relaxed);
}
float PipeWireController::nodeMeterPeak(uint32_t nodeId) const
{
QMutexLocker lock(&m_meterMutex);
if (!m_nodeMeters.contains(nodeId)) {
return 0.0f;
}
NodeMeter *meter = m_nodeMeters.value(nodeId);
if (!meter) {
return 0.0f;
}
return meter->peak.load(std::memory_order_relaxed);
}
void PipeWireController::ensureNodeMeter(uint32_t nodeId, const QString &targetName, bool captureSink)
{
if (!m_threadLoop || !m_core) {
return;
}
{
QMutexLocker lock(&m_meterMutex);
if (m_nodeMeters.contains(nodeId)) {
return;
}
}
auto *meter = new NodeMeter;
meter->nodeId = nodeId;
meter->targetName = targetName;
const QByteArray targetNameBytes = meter->targetName.toUtf8();
struct pw_properties *props = pw_properties_new(
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_CLASS, "Stream/Input/Audio",
PW_KEY_TARGET_OBJECT, targetNameBytes.constData(),
PW_KEY_STREAM_MONITOR, "true",
nullptr);
if (captureSink) {
pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
}
lock();
meter->stream = pw_stream_new_simple(
pw_thread_loop_get_loop(m_threadLoop),
"Potato-Node-Meter",
props,
&node_meter_events,
meter);
if (!meter->stream) {
pw_properties_free(props);
unlock();
delete meter;
return;
}
uint8_t buffer[512];
spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
spa_audio_info_raw info{};
info.format = SPA_AUDIO_FORMAT_F32;
info.rate = 48000;
info.channels = 2;
info.position[0] = SPA_AUDIO_CHANNEL_FL;
info.position[1] = SPA_AUDIO_CHANNEL_FR;
const struct spa_pod *params[1];
params[0] = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &info);
const int res = pw_stream_connect(
meter->stream,
PW_DIRECTION_INPUT,
PW_ID_ANY,
static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
params,
1);
if (res != 0) {
pw_stream_destroy(meter->stream);
unlock();
delete meter;
return;
}
unlock();
QMutexLocker lock(&m_meterMutex);
m_nodeMeters.insert(nodeId, meter);
}
void PipeWireController::removeNodeMeter(uint32_t nodeId)
{
QMutexLocker lock(&m_meterMutex);
if (!m_nodeMeters.contains(nodeId)) {
return;
}
NodeMeter *meter = m_nodeMeters.take(nodeId);
if (meter && meter->stream) {
pw_stream_destroy(meter->stream);
}
delete meter;
}
uint32_t PipeWireController::createLink(uint32_t outputNodeId, uint32_t outputPortId,
uint32_t inputNodeId, uint32_t inputPortId)
{
@ -611,7 +785,9 @@ bool PipeWireController::setupMeterStream()
struct pw_properties *props = pw_properties_new(
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Capture",
PW_KEY_MEDIA_CLASS, "Audio/Source",
PW_KEY_MEDIA_CLASS, "Stream/Input/Audio",
PW_KEY_STREAM_CAPTURE_SINK, "true",
PW_KEY_STREAM_MONITOR, "true",
nullptr);
m_meterStream = pw_stream_new_simple(