Files
vumeter-go/pw_audio.h
2026-04-02 21:47:00 +02:00

190 lines
4.6 KiB
C

#ifndef PW_AUDIO_H
#define PW_AUDIO_H
#include <math.h>
#include <pipewire/pipewire.h>
#include <pthread.h>
#include <spa/param/audio/format-utils.h>
// Shared audio state
typedef struct {
float left;
float right;
float peak_left;
float peak_right;
int terminate;
pthread_mutex_t lock;
struct pw_main_loop *loop;
struct pw_stream *stream;
} pw_audio_t;
static pw_audio_t g_audio;
static float amplitude_to_db(float amplitude) {
if (amplitude <= 0.00001f)
return -60.0f;
float db = 20.0f * log10f(amplitude);
if (db < -60.0f)
db = -60.0f;
if (db > 0.0f)
db = 0.0f;
return db;
}
// Normalize dB (-60..0) to 0..100
static float db_to_level(float db) {
if (db <= -60.0f)
return 0.0f;
return ((db + 60.0f) / 60.0f) * 100.0f;
}
static void on_process(void *userdata) {
pw_audio_t *data = userdata;
struct pw_buffer *b;
struct spa_buffer *buf;
float *samples;
uint32_t n_channels, n_samples;
if (data->terminate) {
pw_main_loop_quit(data->loop);
return;
}
if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL)
return;
buf = b->buffer;
if ((samples = buf->datas[0].data) == NULL) {
pw_stream_queue_buffer(data->stream, b);
return;
}
n_channels = 2; // stereo
n_samples = buf->datas[0].chunk->size / sizeof(float);
// Calculate RMS (Root Mean Square) instead of just peak values
// This gives a more accurate analog needle movement and prevents immediate
// pegging.
float sum_l = 0.0f, sum_r = 0.0f;
for (uint32_t n = 0; n + 1 < n_samples; n += n_channels) {
float l = samples[n];
float r = samples[n + 1];
sum_l += l * l;
sum_r += r * r;
}
float rms_l = sqrtf(sum_l / (n_samples / n_channels));
float rms_r = sqrtf(sum_r / (n_samples / n_channels));
float db_l = amplitude_to_db(rms_l);
float db_r = amplitude_to_db(rms_r);
float level_l = db_to_level(db_l);
float level_r = db_to_level(db_r);
pthread_mutex_lock(&data->lock);
data->left = level_l;
data->right = level_r;
// Peak hold
if (level_l > data->peak_left)
data->peak_left = level_l;
if (level_r > data->peak_right)
data->peak_right = level_r;
pthread_mutex_unlock(&data->lock);
pw_stream_queue_buffer(data->stream, b);
}
static void on_stream_param_changed(void *_data, uint32_t id,
const struct spa_pod *param) {
pw_audio_t *data = _data;
struct spa_audio_info format;
if (param == NULL || id != SPA_PARAM_Format)
return;
if (spa_format_parse(param, &format.media_type, &format.media_subtype) < 0)
return;
if (format.media_type != SPA_MEDIA_TYPE_audio ||
format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
return;
}
static const struct pw_stream_events stream_events = {
PW_VERSION_STREAM_EVENTS,
.param_changed = on_stream_param_changed,
.process = on_process,
};
static void *pw_audio_thread(void *arg) {
pw_audio_t *data = &g_audio;
const struct spa_pod *params[1];
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
pw_init(0, NULL);
data->loop = pw_main_loop_new(NULL);
if (data->loop == NULL) {
data->terminate = 1;
return NULL;
}
struct pw_properties *props = pw_properties_new(
PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_CONFIG_NAME, "client-rt.conf",
PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", NULL);
pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true");
data->stream =
pw_stream_new_simple(pw_main_loop_get_loop(data->loop), "vumeter-capture",
props, &stream_events, data);
params[0] = spa_format_audio_raw_build(
&b, SPA_PARAM_EnumFormat,
&SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_F32));
pw_stream_connect(data->stream, PW_DIRECTION_INPUT, PW_ID_ANY,
PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS,
params, 1);
pw_main_loop_run(data->loop);
pw_stream_destroy(data->stream);
pw_main_loop_destroy(data->loop);
pw_deinit();
return NULL;
}
static int pw_audio_start() {
g_audio.left = 0;
g_audio.right = 0;
g_audio.peak_left = 0;
g_audio.peak_right = 0;
g_audio.terminate = 0;
pthread_mutex_init(&g_audio.lock, NULL);
pthread_t thread;
return pthread_create(&thread, NULL, pw_audio_thread, NULL);
}
static void pw_audio_get_levels(float *left, float *right) {
pthread_mutex_lock(&g_audio.lock);
*left = g_audio.left;
*right = g_audio.right;
pthread_mutex_unlock(&g_audio.lock);
}
static void pw_audio_stop() {
g_audio.terminate = 1;
if (g_audio.loop) {
pw_main_loop_quit(g_audio.loop);
}
}
#endif