summaryrefslogtreecommitdiff
path: root/plugin/src/Caelestia/audiocollector.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugin/src/Caelestia/audiocollector.cpp')
-rw-r--r--plugin/src/Caelestia/audiocollector.cpp275
1 files changed, 0 insertions, 275 deletions
diff --git a/plugin/src/Caelestia/audiocollector.cpp b/plugin/src/Caelestia/audiocollector.cpp
deleted file mode 100644
index 9dc3871..0000000
--- a/plugin/src/Caelestia/audiocollector.cpp
+++ /dev/null
@@ -1,275 +0,0 @@
-#include "audiocollector.hpp"
-
-#include "service.hpp"
-#include <algorithm>
-#include <pipewire/pipewire.h>
-#include <qdebug.h>
-#include <qmutex.h>
-#include <spa/param/audio/format-utils.h>
-#include <spa/param/latency-utils.h>
-#include <stop_token>
-#include <vector>
-
-namespace caelestia {
-
-PipeWireWorker::PipeWireWorker(std::stop_token token, AudioCollector* collector)
- : m_loop(nullptr)
- , m_stream(nullptr)
- , m_timer(nullptr)
- , m_idle(true)
- , m_token(token)
- , m_collector(collector) {
- pw_init(nullptr, nullptr);
-
- m_loop = pw_main_loop_new(nullptr);
- if (!m_loop) {
- qWarning() << "PipeWireWorker::init: failed to create PipeWire main loop";
- pw_deinit();
- return;
- }
-
- timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
- m_timer = pw_loop_add_timer(pw_main_loop_get_loop(m_loop), handleTimeout, this);
- pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
-
- auto props = pw_properties_new(
- PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", nullptr);
- pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
- pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", nextPowerOf2(512 * collector->sampleRate() / 48000),
- collector->sampleRate());
- pw_properties_set(props, PW_KEY_NODE_PASSIVE, "true");
- pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
- pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "false");
- pw_properties_set(props, "channelmix.upmix", "true");
-
- std::vector<uint8_t> buffer(collector->chunkSize());
- spa_pod_builder b;
- spa_pod_builder_init(&b, buffer.data(), static_cast<quint32>(buffer.size()));
-
- spa_audio_info_raw info{};
- info.format = SPA_AUDIO_FORMAT_S16;
- info.rate = collector->sampleRate();
- info.channels = 1;
-
- const spa_pod* params[1];
- params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
-
- pw_stream_events events{};
- events.state_changed = [](void* data, pw_stream_state, pw_stream_state state, const char*) {
- auto* self = static_cast<PipeWireWorker*>(data);
- self->streamStateChanged(state);
- };
- events.process = [](void* data) {
- auto* self = static_cast<PipeWireWorker*>(data);
- self->processStream();
- };
-
- m_stream = pw_stream_new_simple(pw_main_loop_get_loop(m_loop), "caelestia-shell", props, &events, this);
-
- const int success = pw_stream_connect(m_stream, PW_DIRECTION_INPUT, collector->nodeId(),
- static_cast<pw_stream_flags>(
- PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS),
- params, 1);
- if (success < 0) {
- qWarning() << "PipeWireWorker::init: failed to connect stream";
- pw_stream_destroy(m_stream);
- pw_main_loop_destroy(m_loop);
- pw_deinit();
- return;
- }
-
- pw_main_loop_run(m_loop);
-
- pw_stream_destroy(m_stream);
- pw_main_loop_destroy(m_loop);
- pw_deinit();
-}
-
-void PipeWireWorker::handleTimeout(void* data, uint64_t expirations) {
- auto* self = static_cast<PipeWireWorker*>(data);
-
- if (self->m_token.stop_requested()) {
- pw_main_loop_quit(self->m_loop);
- return;
- }
-
- if (!self->m_idle) {
- if (expirations < 10) {
- self->m_collector->clearBuffer();
- } else {
- self->m_idle = true;
- timespec timeout = { 0, 500 * SPA_NSEC_PER_MSEC };
- pw_loop_update_timer(pw_main_loop_get_loop(self->m_loop), self->m_timer, &timeout, &timeout, false);
- }
- }
-}
-
-void PipeWireWorker::streamStateChanged(pw_stream_state state) {
- m_idle = false;
- switch (state) {
- case PW_STREAM_STATE_PAUSED: {
- timespec timeout = { 0, 10 * SPA_NSEC_PER_MSEC };
- pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, &timeout, &timeout, false);
- break;
- }
- case PW_STREAM_STATE_STREAMING:
- pw_loop_update_timer(pw_main_loop_get_loop(m_loop), m_timer, nullptr, nullptr, false);
- break;
- case PW_STREAM_STATE_ERROR:
- pw_main_loop_quit(m_loop);
- break;
- default:
- break;
- }
-}
-
-void PipeWireWorker::processStream() {
- if (m_token.stop_requested()) {
- pw_main_loop_quit(m_loop);
- return;
- }
-
- pw_buffer* buffer = pw_stream_dequeue_buffer(m_stream);
- if (buffer == nullptr) {
- return;
- }
-
- const spa_buffer* buf = buffer->buffer;
- const qint16* samples = reinterpret_cast<const qint16*>(buf->datas[0].data);
- if (samples == nullptr) {
- return;
- }
-
- const quint32 count = buf->datas[0].chunk->size / 2;
- m_collector->loadChunk(samples, count);
-
- pw_stream_queue_buffer(m_stream, buffer);
-}
-
-unsigned int PipeWireWorker::nextPowerOf2(unsigned int n) {
- if (n == 0) {
- return 1;
- }
-
- n--;
- n |= n >> 1;
- n |= n >> 2;
- n |= n >> 4;
- n |= n >> 8;
- n |= n >> 16;
- n++;
-
- return n;
-}
-
-AudioCollector::AudioCollector(QObject* parent)
- : Service(parent)
- , m_sampleRate(44100)
- , m_chunkSize(512)
- , m_nodeId(PW_ID_ANY)
- , m_buffer1(m_chunkSize)
- , m_buffer2(m_chunkSize)
- , m_readBuffer(&m_buffer1)
- , m_writeBuffer(&m_buffer2) {}
-
-AudioCollector::~AudioCollector() {
- stop();
-}
-
-quint32 AudioCollector::sampleRate() const {
- return m_sampleRate;
-}
-
-quint32 AudioCollector::chunkSize() const {
- return m_chunkSize;
-}
-
-quint32 AudioCollector::nodeId() {
- QMutexLocker locker(&m_nodeIdMutex);
- return m_nodeId;
-}
-
-void AudioCollector::setNodeId(quint32 nodeId) {
- {
- QMutexLocker locker(&m_nodeIdMutex);
-
- if (nodeId == m_nodeId) {
- return;
- }
-
- m_nodeId = nodeId;
- }
- emit nodeIdChanged();
-
- if (m_thread.joinable()) {
- stop();
- start();
- }
-}
-
-void AudioCollector::clearBuffer() {
- auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
- std::fill(writeBuffer->begin(), writeBuffer->end(), 0.0f);
-
- auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
- m_writeBuffer.store(oldRead, std::memory_order_release);
-}
-
-void AudioCollector::loadChunk(const qint16* samples, quint32 count) {
- if (count > m_chunkSize) {
- count = m_chunkSize;
- }
-
- auto* writeBuffer = m_writeBuffer.load(std::memory_order_relaxed);
- std::transform(samples, samples + count, writeBuffer->begin(), [](qint16 sample) {
- return sample / 32768.0f;
- });
-
- auto* oldRead = m_readBuffer.exchange(writeBuffer, std::memory_order_acq_rel);
- m_writeBuffer.store(oldRead, std::memory_order_release);
-}
-
-quint32 AudioCollector::readChunk(float* out, quint32 count) {
- if (count == 0 || count > m_chunkSize) {
- count = m_chunkSize;
- }
-
- auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
- std::memcpy(out, readBuffer->data(), count * sizeof(float));
-
- return count;
-}
-
-quint32 AudioCollector::readChunk(double* out, quint32 count) {
- if (count == 0 || count > m_chunkSize) {
- count = m_chunkSize;
- }
-
- auto* readBuffer = m_readBuffer.load(std::memory_order_acquire);
- std::transform(readBuffer->begin(), readBuffer->begin() + count, out, [](float sample) {
- return static_cast<double>(sample);
- });
-
- return count;
-}
-
-void AudioCollector::start() {
- if (m_thread.joinable()) {
- return;
- }
-
- clearBuffer();
-
- m_thread = std::jthread([this](std::stop_token token) {
- PipeWireWorker worker(token, this);
- });
-}
-
-void AudioCollector::stop() {
- if (m_thread.joinable()) {
- m_thread.request_stop();
- m_thread.join();
- }
-}
-
-} // namespace caelestia