mirror of https://github.com/cutefishos/settings
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
96 lines
3.7 KiB
C++
96 lines
3.7 KiB
C++
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <pipewire/pipewire.h>
|
|
#include <spa/param/audio/format-utils.h>
|
|
|
|
class TestStream
|
|
{
|
|
public:
|
|
// A playback stream plays a constant level, which a volume monitor reads back. Each
|
|
// stream is its own application, so WirePlumber restores no other stream's volume onto it.
|
|
TestStream(pw_core *core, const std::string &name, const std::string &target, bool capture = false,
|
|
float level = 0.0f)
|
|
: m_capture(capture)
|
|
, m_level(level)
|
|
{
|
|
// Controls must use a different client connection from the exported stream.
|
|
m_core = pw_context_connect(pw_core_get_context(core), nullptr, 0);
|
|
if (!m_core)
|
|
return;
|
|
auto *properties = pw_properties_new(PW_KEY_NODE_NAME, name.c_str(),
|
|
PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, capture ? "Capture" : "Playback",
|
|
PW_KEY_APP_NAME, name.c_str(), PW_KEY_TARGET_OBJECT, target.c_str(), nullptr);
|
|
m_stream = pw_stream_new(m_core, name.c_str(), properties);
|
|
if (!m_stream)
|
|
return;
|
|
pw_stream_add_listener(m_stream, &m_listener, &events, this);
|
|
spa_audio_info_raw format {};
|
|
format.format = SPA_AUDIO_FORMAT_F32;
|
|
format.rate = 48000;
|
|
format.channels = 2;
|
|
format.position[0] = SPA_AUDIO_CHANNEL_FL;
|
|
format.position[1] = SPA_AUDIO_CHANNEL_FR;
|
|
uint8_t buffer[512];
|
|
spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
|
const spa_pod *params[] = {spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &format)};
|
|
connected = pw_stream_connect(m_stream, capture ? PW_DIRECTION_INPUT : PW_DIRECTION_OUTPUT,
|
|
PW_ID_ANY, static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params, 1) >= 0;
|
|
}
|
|
|
|
~TestStream()
|
|
{
|
|
if (m_stream) {
|
|
spa_hook_remove(&m_listener);
|
|
pw_stream_destroy(m_stream);
|
|
}
|
|
if (m_core)
|
|
pw_core_disconnect(m_core);
|
|
}
|
|
|
|
bool connected = false;
|
|
bool streaming = false;
|
|
|
|
private:
|
|
pw_core *m_core = nullptr;
|
|
pw_stream *m_stream = nullptr;
|
|
spa_hook m_listener {};
|
|
bool m_capture;
|
|
float m_level;
|
|
|
|
inline static const pw_stream_events events = [] {
|
|
pw_stream_events events {};
|
|
events.version = PW_VERSION_STREAM_EVENTS;
|
|
events.state_changed = [](void *data, pw_stream_state, pw_stream_state state, const char *) {
|
|
static_cast<TestStream *>(data)->streaming = state == PW_STREAM_STATE_STREAMING;
|
|
};
|
|
events.process = [](void *data) {
|
|
auto *self = static_cast<TestStream *>(data);
|
|
pw_buffer *buffer = pw_stream_dequeue_buffer(self->m_stream);
|
|
if (!buffer)
|
|
return;
|
|
if (!self->m_capture && buffer->buffer->n_datas > 0) {
|
|
spa_data &data = buffer->buffer->datas[0];
|
|
if (data.data && data.chunk) {
|
|
constexpr uint32_t stride = 2 * sizeof(float);
|
|
const uint32_t frames = buffer->requested
|
|
? std::min<uint64_t>(buffer->requested, data.maxsize / stride) : data.maxsize / stride;
|
|
std::fill_n(static_cast<float *>(data.data), frames * 2, self->m_level);
|
|
data.chunk->offset = 0;
|
|
data.chunk->size = frames * stride;
|
|
data.chunk->stride = stride;
|
|
buffer->size = frames;
|
|
}
|
|
}
|
|
pw_stream_queue_buffer(self->m_stream, buffer);
|
|
};
|
|
return events;
|
|
}();
|
|
};
|