Volume
This commit is contained in:
parent
94ce8617d6
commit
4bf830763f
4 changed files with 285 additions and 3 deletions
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue