Make plugin pipe audio from the outside
This commit is contained in:
parent
898bc2705f
commit
4126db3e0e
4 changed files with 246 additions and 42 deletions
|
@ -1,5 +1,6 @@
|
|||
.PHONY: build
|
||||
CXXFLAGS=-O3 -march=native $(CXXFLAGS)
|
||||
|
||||
.PHONY: build
|
||||
build: main.out
|
||||
main.out: main.o
|
||||
$(CXX) $(LDFLAGS) $^ -o $@
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
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 -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 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
|
||||
|
|
|
@ -1,47 +1,80 @@
|
|||
#include <random>
|
||||
#include <cstdio>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include "pipewire/pipewire.h"
|
||||
#include "spa/param/audio/format-utils.h"
|
||||
#include "spa/param/latency-utils.h"
|
||||
|
||||
constexpr int samplerate = 48000;
|
||||
|
||||
struct local_data {
|
||||
pw_main_loop* loop;
|
||||
pw_filter* filter;
|
||||
|
||||
void* iport;
|
||||
void* oport;
|
||||
void* mic_port; // Input data from mic
|
||||
void* listen_port; // Input music port
|
||||
void* hp_port; // Output data to headphone
|
||||
|
||||
std::random_device gen;
|
||||
std::normal_distribution<float> nd{0, 0.3};
|
||||
// Hopefully we never have >100ms roundtrip delay
|
||||
float record_buffer[samplerate * 10];
|
||||
float mic_buffer[samplerate * 10];
|
||||
|
||||
uint32_t buffer_index = 0;
|
||||
float record_buffer[48000 * 10][2]; // Forçage (to R.), retour (mic L)
|
||||
int32_t head_index = 0; // The head is where the NEXT sample is going to be saved
|
||||
|
||||
/*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) {
|
||||
float* in; float* out;
|
||||
uint32_t n_samples = position->clock.duration;
|
||||
int32_t n_samples = (int32_t)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);
|
||||
float* mic_in = (float*)pw_filter_get_dsp_buffer(ld->mic_port, 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;}
|
||||
|
||||
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++;
|
||||
}
|
||||
if(mic_in == NULL || listen_in == NULL || hp_out == NULL) {return;}
|
||||
|
||||
*out++ = val;
|
||||
// Simply echo the outgoing data
|
||||
int copy_frames = std::min(samplerate*10 - ld->head_index, n_samples);
|
||||
|
||||
ld->buffer_index+=1;
|
||||
}
|
||||
std::memcpy(hp_out, listen_in, n_samples * sizeof(float));
|
||||
|
||||
if(ld->buffer_index >= 48000 * 10) {
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -51,25 +84,24 @@ const struct pw_filter_events filter_events {
|
|||
.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;
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -91,17 +123,17 @@ int main(int argc, char** argv) {
|
|||
&filter_events,
|
||||
&local);
|
||||
|
||||
local.iport = pw_filter_add_port(local.filter,
|
||||
local.mic_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, "input",
|
||||
PW_KEY_PORT_NAME, "micinput",
|
||||
NULL),
|
||||
NULL, 0);
|
||||
|
||||
local.oport = pw_filter_add_port(local.filter,
|
||||
local.hp_port = pw_filter_add_port(local.filter,
|
||||
PW_DIRECTION_OUTPUT,
|
||||
PW_FILTER_PORT_FLAG_MAP_BUFFERS,
|
||||
sizeof(void*),
|
||||
|
@ -111,6 +143,16 @@ int main(int argc, char** argv) {
|
|||
NULL),
|
||||
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];
|
||||
uint8_t buffer[1024];
|
||||
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_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("Call count : %u\n", local.call_count);
|
||||
|
||||
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;
|
||||
for(int delta = 0; delta < 48000/5; ++delta) {
|
||||
float correct_crosscor = avg_correlation(&local.record_buffer[48000], &local.mic_buffer[48000], delta, 48000*9);
|
||||
// Normalizing -> if h(n) = del_0(n) then correct_crosscor = del_0.
|
||||
std::printf("%e,", correct_crosscor);
|
||||
}
|
||||
std::printf("]\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
158
pw_plugin/td_xcorr.cc
Normal file
158
pw_plugin/td_xcorr.cc
Normal 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;
|
||||
}
|
Loading…
Add table
Reference in a new issue