Make plugin pipe audio from the outside

This commit is contained in:
Sélène Corbineau 2025-01-18 18:17:07 +01:00
parent 898bc2705f
commit 4126db3e0e
4 changed files with 246 additions and 42 deletions

View file

@ -1,5 +1,6 @@
.PHONY: build CXXFLAGS=-O3 -march=native $(CXXFLAGS)
.PHONY: build
build: main.out build: main.out
main.out: main.o main.out: main.o
$(CXX) $(LDFLAGS) $^ -o $@ $(CXX) $(LDFLAGS) $^ -o $@

View file

@ -1,4 +1,12 @@
#!/usr/bin/env sh #!/usr/bin/env sh
pw-link -d mpv:output_FL alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo:playback_FL
pw-link -d mpv:output_FR alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo:playback_FR
# Setup the 3 ports
pw-link mpv:output_FR audio-filter:musinput
pw-link audio-filter:output alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo:playback_FR pw-link audio-filter:output alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo:playback_FR
pw-link alsa_input.pci-0000_00_1b.0.analog-stereo:capture_FL audio-filter:input pw-link alsa_input.pci-0000_00_1b.0.analog-stereo:capture_FL audio-filter:micinput
#pw-link alsa_input.usb-Logitech_Logitech_USB_Headset-00.mono-fallback:capture_MONO audio-filter:input

View file

@ -1,47 +1,80 @@
#include <random> #include <random>
#include <cstdio> #include <cstdio>
#include <cmath> #include <cmath>
#include <cstring>
#include <iostream> #include <iostream>
#include "pipewire/pipewire.h" #include "pipewire/pipewire.h"
#include "spa/param/audio/format-utils.h" #include "spa/param/audio/format-utils.h"
#include "spa/param/latency-utils.h" #include "spa/param/latency-utils.h"
constexpr int samplerate = 48000;
struct local_data { struct local_data {
pw_main_loop* loop; pw_main_loop* loop;
pw_filter* filter; pw_filter* filter;
void* iport; void* mic_port; // Input data from mic
void* oport; void* listen_port; // Input music port
void* hp_port; // Output data to headphone
std::random_device gen; // Hopefully we never have >100ms roundtrip delay
std::normal_distribution<float> nd{0, 0.3}; float record_buffer[samplerate * 10];
float mic_buffer[samplerate * 10];
uint32_t buffer_index = 0; int32_t head_index = 0; // The head is where the NEXT sample is going to be saved
float record_buffer[48000 * 10][2]; // Forçage (to R.), retour (mic L)
/*double xcor_buffer[samplerate / 10] = {0};
uint32_t sample_count = 0;*/
uint32_t call_count = 0;
}; };
void on_process(local_data* ld, spa_io_position* position) { void on_process(local_data* ld, spa_io_position* position) {
float* in; float* out; int32_t n_samples = (int32_t)position->clock.duration;
uint32_t n_samples = position->clock.duration;
in = (float*)pw_filter_get_dsp_buffer(ld->iport, n_samples); float* mic_in = (float*)pw_filter_get_dsp_buffer(ld->mic_port, n_samples);
out = (float*)pw_filter_get_dsp_buffer(ld->oport, n_samples); float* listen_in = (float*)pw_filter_get_dsp_buffer(ld->listen_port, n_samples);
float* hp_out = (float*)pw_filter_get_dsp_buffer(ld->hp_port, n_samples);
if(in == NULL || out == NULL) {return;} if(mic_in == NULL || listen_in == NULL || hp_out == NULL) {return;}
for(uint32_t i = 0; i < n_samples; ++i) { // Simply echo the outgoing data
auto val = ld->nd(ld->gen); int copy_frames = std::min(samplerate*10 - ld->head_index, n_samples);
if(ld->buffer_index < 48000 * 10) {
ld->record_buffer[ld->buffer_index][0] = val;
ld->record_buffer[ld->buffer_index][1] = *in++;
}
*out++ = val; std::memcpy(hp_out, listen_in, n_samples * sizeof(float));
ld->buffer_index+=1; std::memcpy(&ld->record_buffer[ld->head_index], listen_in, copy_frames*sizeof(float));
} std::memcpy(&ld->mic_buffer[ld->head_index], mic_in, copy_frames*sizeof(float));
if(ld->buffer_index >= 48000 * 10) { ld->head_index += copy_frames;
/*
// Now, copy data to the circular buffer
int32_t fst_cpy = std::min(n_samples, (int)(samplerate/10) - ld->head_index);
std::memcpy(&ld->record_buffer[ld->head_index], listen_in, fst_cpy * sizeof(float));
ld->head_index += fst_cpy;
int32_t snd_cpy = std::max(0, n_samples - fst_cpy);
std::memcpy(&ld->record_buffer[0], &listen_in[fst_cpy], snd_cpy * sizeof(float));
if(snd_cpy != 0) { ld->head_index = snd_cpy; }
*/
// To save on computation time, we randomly sample only one of the mic_in
// inputs.
/*
float input = mic_in[n_samples - 1];
int32_t head_pos = ld->head_index - 1;
if(head_pos == -1) {head_pos = samplerate/10 - 1;}
// Update the xcorr
for(int i = 0; i < samplerate / 10; ++i) {
ld->xcor_buffer[i] = (1 - 1.0/512)*ld->xcor_buffer[i]
+ 1.0/512 * input * ld->record_buffer[head_pos];
head_pos--;
if(head_pos == -1) {head_pos = samplerate/10 - 1;}
}*/
ld->call_count++;
if(ld->head_index == samplerate*10) {
pw_main_loop_quit(ld->loop); pw_main_loop_quit(ld->loop);
} }
} }
@ -51,25 +84,24 @@ const struct pw_filter_events filter_events {
.process = (void(*)(void*, spa_io_position*))on_process, .process = (void(*)(void*, spa_io_position*))on_process,
}; };
float compute_RMS_power(float* buffer, int stride, int n_samps) { float compute_RMS_power(float* buffer, int n_samps) {
double acc = 0; double acc = 0;
for(int i = 0; i < n_samps; ++i) { for(int i = 0; i < n_samps; ++i) {
acc += buffer[stride*i] * buffer[stride*i]; acc += buffer[i] * buffer[i];
} }
return (float)std::sqrt(acc/n_samps); return (float)std::sqrt(acc/n_samps);
} }
// Delta positif -> retard du micro par rapport au HP -> physique. // Delta positif -> retard du micro par rapport au HP -> physique.
float avg_correlation(float buffer[][2], int delta, int n_samps) { float avg_correlation(float bufferHP[], float bufferMIC[], int delta, int n_samps) {
double acc = 0; double acc = 0;
for(int i = delta; i < n_samps; ++i) { for(int i = delta; i < n_samps; ++i) {
acc += buffer[i - delta][0] * buffer[i][1]; acc += bufferHP[i - delta] * bufferMIC[i];
} }
return acc / (n_samps - delta); return acc / (n_samps - delta);
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
// We do NOT seed the MT engine -> the time series will be predictable!
local_data local{nullptr,nullptr,}; local_data local{nullptr,nullptr,};
pw_init(&argc, &argv); pw_init(&argc, &argv);
@ -91,17 +123,17 @@ int main(int argc, char** argv) {
&filter_events, &filter_events,
&local); &local);
local.iport = pw_filter_add_port(local.filter, local.mic_port = pw_filter_add_port(local.filter,
PW_DIRECTION_INPUT, PW_DIRECTION_INPUT,
PW_FILTER_PORT_FLAG_MAP_BUFFERS, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(void*), sizeof(void*),
pw_properties_new( pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_FORMAT_DSP, "32 bit float mono audio",
PW_KEY_PORT_NAME, "input", PW_KEY_PORT_NAME, "micinput",
NULL), NULL),
NULL, 0); NULL, 0);
local.oport = pw_filter_add_port(local.filter, local.hp_port = pw_filter_add_port(local.filter,
PW_DIRECTION_OUTPUT, PW_DIRECTION_OUTPUT,
PW_FILTER_PORT_FLAG_MAP_BUFFERS, PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(void*), sizeof(void*),
@ -111,6 +143,16 @@ int main(int argc, char** argv) {
NULL), NULL),
NULL, 0); NULL, 0);
local.listen_port = pw_filter_add_port(local.filter,
PW_DIRECTION_INPUT,
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(void*),
pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit float mono audio",
PW_KEY_PORT_NAME, "musinput",
NULL),
NULL, 0);
const spa_pod* params[2]; const spa_pod* params[2];
uint8_t buffer[1024]; uint8_t buffer[1024];
spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
@ -138,21 +180,16 @@ int main(int argc, char** argv) {
pw_main_loop_destroy(local.loop); pw_main_loop_destroy(local.loop);
pw_deinit(); pw_deinit();
float power_input = compute_RMS_power(&local.record_buffer[0][0], 2, 48000*10); std::printf("Call count : %u\n", local.call_count);
float power_mic = compute_RMS_power(&local.record_buffer[0][1], 2, 48000*10);
std::printf("RMS Powers : Input %f, Mic. %f\n",
power_input,
power_mic);
std::printf("XCor = [\n"); std::printf("XCor = [\n");
// Look at the first 200ms... // Look at the first 200ms...
for(int delta = 0; delta < 48000 / 5; ++delta) { for(int delta = 0; delta < 48000/5; ++delta) {
float correct_crosscor = avg_correlation(&local.record_buffer[48000], delta, 48000*9); float correct_crosscor = avg_correlation(&local.record_buffer[48000], &local.mic_buffer[48000], delta, 48000*9);
correct_crosscor /= power_input * power_input;
// Normalizing -> if h(n) = del_0(n) then correct_crosscor = del_0. // Normalizing -> if h(n) = del_0(n) then correct_crosscor = del_0.
std::printf("%e,", correct_crosscor); std::printf("%e,", correct_crosscor);
} }
std::printf("]\n"); std::printf("]\n");
return 0; return 0;
} }

158
pw_plugin/td_xcorr.cc Normal file
View file

@ -0,0 +1,158 @@
#include <random>
#include <cstdio>
#include <cmath>
#include <iostream>
#include "pipewire/pipewire.h"
#include "spa/param/audio/format-utils.h"
#include "spa/param/latency-utils.h"
struct local_data {
pw_main_loop* loop;
pw_filter* filter;
void* iport;
void* oport;
std::random_device gen;
std::normal_distribution<float> nd{0, 0.3};
uint32_t buffer_index = 0;
float record_buffer[48000 * 10][2]; // Forçage (to R.), retour (mic L)
};
void on_process(local_data* ld, spa_io_position* position) {
float* in; float* out;
uint32_t n_samples = position->clock.duration;
in = (float*)pw_filter_get_dsp_buffer(ld->iport, n_samples);
out = (float*)pw_filter_get_dsp_buffer(ld->oport, n_samples);
if(in == NULL || out == NULL) {return;}
for(uint32_t i = 0; i < n_samples; ++i) {
auto val = ld->nd(ld->gen);
if(ld->buffer_index < 48000 * 10) {
ld->record_buffer[ld->buffer_index][0] = val;
ld->record_buffer[ld->buffer_index][1] = *in++;
}
*out++ = val;
ld->buffer_index+=1;
}
if(ld->buffer_index >= 48000 * 10) {
pw_main_loop_quit(ld->loop);
}
}
const struct pw_filter_events filter_events {
PW_VERSION_FILTER_EVENTS,
.process = (void(*)(void*, spa_io_position*))on_process,
};
float compute_RMS_power(float* buffer, int stride, int n_samps) {
double acc = 0;
for(int i = 0; i < n_samps; ++i) {
acc += buffer[stride*i] * buffer[stride*i];
}
return (float)std::sqrt(acc/n_samps);
}
// Delta positif -> retard du micro par rapport au HP -> physique.
float avg_correlation(float buffer[][2], int delta, int n_samps) {
double acc = 0;
for(int i = delta; i < n_samps; ++i) {
acc += buffer[i - delta][0] * buffer[i][1];
}
return acc / (n_samps - delta);
}
int main(int argc, char** argv) {
// We do NOT seed the MT engine -> the time series will be predictable!
local_data local{nullptr,nullptr,};
pw_init(&argc, &argv);
local.loop = pw_main_loop_new(NULL);
if(local.loop == NULL) {
std::cerr << "Could not create loop!\n";
return 1;
}
local.filter = pw_filter_new_simple(
pw_main_loop_get_loop(local.loop),
"audio-filter",
pw_properties_new(
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Filter",
PW_KEY_MEDIA_ROLE, "DSP",
NULL),
&filter_events,
&local);
local.iport = pw_filter_add_port(local.filter,
PW_DIRECTION_INPUT,
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(void*),
pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit float mono audio",
PW_KEY_PORT_NAME, "input",
NULL),
NULL, 0);
local.oport = pw_filter_add_port(local.filter,
PW_DIRECTION_OUTPUT,
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
sizeof(void*),
pw_properties_new(
PW_KEY_FORMAT_DSP, "32 bit float mono audio",
PW_KEY_PORT_NAME, "output",
NULL),
NULL, 0);
const spa_pod* params[2];
uint8_t buffer[1024];
spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
auto latinfo = SPA_PROCESS_LATENCY_INFO_INIT(.ns = 10 * SPA_NSEC_PER_MSEC);
auto formatinfo = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_DSP_F32,
.rate = 48000,
.channels = 1);
params[0] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &latinfo);
params[1] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &formatinfo);
if(pw_filter_connect(local.filter,
PW_FILTER_FLAG_RT_PROCESS,
params, 1) < 0) {
std::fprintf(stderr, "Cannot connect\n");
return -1;
}
std::printf("Waiting for connection\n");
pw_main_loop_run(local.loop);
std::printf("Successfully recorded 10s\n");
pw_filter_destroy(local.filter);
pw_main_loop_destroy(local.loop);
pw_deinit();
float power_input = compute_RMS_power(&local.record_buffer[0][0], 2, 48000*10);
float power_mic = compute_RMS_power(&local.record_buffer[0][1], 2, 48000*10);
std::printf("RMS Powers : Input %f, Mic. %f\n",
power_input,
power_mic);
std::printf("XCor = [\n");
// Look at the first 200ms...
for(int delta = 0; delta < 48000 / 5; ++delta) {
float correct_crosscor = avg_correlation(&local.record_buffer[48000], delta, 48000*9);
correct_crosscor /= power_input * power_input;
// Normalizing -> if h(n) = del_0(n) then correct_crosscor = del_0.
std::printf("%e,", correct_crosscor);
}
std::printf("]\n");
return 0;
}