pre-passage f32
This commit is contained in:
@@ -35,4 +35,4 @@ moka = {version = "0.12", features = ["future"] }
|
|||||||
arc-swap = "1.7"
|
arc-swap = "1.7"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
kanal = "0.1"
|
kanal = "0.1"
|
||||||
rubato = "0.16.2"
|
rubato = "0.16"
|
||||||
@@ -4,7 +4,7 @@ use std::thread;
|
|||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use cpal::{default_host, BufferSize, Device, SampleRate, Stream, StreamConfig, SupportedStreamConfig};
|
use cpal::{default_host, BufferSize, Device, SampleRate, Stream, StreamConfig, SupportedStreamConfig};
|
||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use rubato::{FftFixedInOut, Resampler};
|
// ✅ Supprimé rubato complètement !
|
||||||
use crate::core::opus::AudioOpus;
|
use crate::core::opus::AudioOpus;
|
||||||
use crate::domain::event::{Event, EventBus};
|
use crate::domain::event::{Event, EventBus};
|
||||||
use crate::utils::ringbuf::RingBuffer;
|
use crate::utils::ringbuf::RingBuffer;
|
||||||
@@ -18,7 +18,7 @@ pub struct AudioCapture {
|
|||||||
event_bus: EventBus,
|
event_bus: EventBus,
|
||||||
microphone: Microphone,
|
microphone: Microphone,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
ring_buffer: RingBuffer<f32>,
|
ring_buffer: RingBuffer<i16>,
|
||||||
steam: Option<Stream>,
|
steam: Option<Stream>,
|
||||||
worker: Option<JoinHandle<()>>,
|
worker: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,7 @@ pub struct AudioCapture {
|
|||||||
impl Microphone {
|
impl Microphone {
|
||||||
pub fn new(device: Device) -> Self {
|
pub fn new(device: Device) -> Self {
|
||||||
println!("Initializing microphone with device: {}", device.name().unwrap_or_else(|_| "Unknown".to_string()));
|
println!("Initializing microphone with device: {}", device.name().unwrap_or_else(|_| "Unknown".to_string()));
|
||||||
Self {
|
Self { device }
|
||||||
device
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default() -> Self {
|
pub fn default() -> Self {
|
||||||
@@ -54,17 +52,16 @@ impl Microphone {
|
|||||||
|
|
||||||
StreamConfig {
|
StreamConfig {
|
||||||
channels,
|
channels,
|
||||||
sample_rate: SampleRate(48_000),
|
sample_rate: SampleRate(48_000), // ✅ Force 48kHz
|
||||||
buffer_size: BufferSize::Default,
|
buffer_size: BufferSize::Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_stream<F>(&self, callback: F) -> Stream
|
pub fn build_stream<F>(&self, callback: F) -> Stream
|
||||||
where
|
where
|
||||||
F: FnMut(&[f32], &cpal::InputCallbackInfo) + Send + 'static,
|
F: FnMut(&[i16], &cpal::InputCallbackInfo) + Send + 'static,
|
||||||
{
|
{
|
||||||
let config = self.get_stream_config();
|
let config = self.get_stream_config();
|
||||||
|
|
||||||
self.device.build_input_stream(
|
self.device.build_input_stream(
|
||||||
&config,
|
&config,
|
||||||
callback,
|
callback,
|
||||||
@@ -73,7 +70,6 @@ impl Microphone {
|
|||||||
).unwrap()
|
).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// helpers
|
|
||||||
pub fn get_supported_channels(&self) -> Vec<u16> {
|
pub fn get_supported_channels(&self) -> Vec<u16> {
|
||||||
self.device.supported_input_configs()
|
self.device.supported_input_configs()
|
||||||
.map(|configs| configs.map(|c| c.channels()).collect())
|
.map(|configs| configs.map(|c| c.channels()).collect())
|
||||||
@@ -108,9 +104,7 @@ impl AudioCapture {
|
|||||||
let writer = self.ring_buffer.writer();
|
let writer = self.ring_buffer.writer();
|
||||||
let stream_running = self.running.clone();
|
let stream_running = self.running.clone();
|
||||||
let stream = self.microphone.build_stream(move |data, _| {
|
let stream = self.microphone.build_stream(move |data, _| {
|
||||||
if !stream_running.load(Ordering::Relaxed){
|
if !stream_running.load(Ordering::Relaxed) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
writer.push_slice_overwrite(data);
|
writer.push_slice_overwrite(data);
|
||||||
});
|
});
|
||||||
stream.play().unwrap();
|
stream.play().unwrap();
|
||||||
@@ -126,74 +120,57 @@ impl AudioCapture {
|
|||||||
pub async fn stop(&mut self) {
|
pub async fn stop(&mut self) {
|
||||||
println!("Stopping audio capture");
|
println!("Stopping audio capture");
|
||||||
self.running.store(false, Ordering::Relaxed);
|
self.running.store(false, Ordering::Relaxed);
|
||||||
println!("Releasing audio stream");
|
|
||||||
self.steam = None;
|
self.steam = None;
|
||||||
self.ring_buffer.force_wake_up();
|
self.ring_buffer.force_wake_up();
|
||||||
|
|
||||||
// code possiblement bloquant, wrap vers un thread tokio bloquant
|
|
||||||
if let Some(worker) = self.worker.take() {
|
if let Some(worker) = self.worker.take() {
|
||||||
println!("Waiting for audio processing worker to finish");
|
println!("Waiting for audio processing worker to finish");
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
worker.join().unwrap();
|
worker.join().unwrap();
|
||||||
}).await.unwrap();
|
}).await.unwrap();
|
||||||
}
|
}
|
||||||
println!("Clearing ring buffer");
|
|
||||||
self.ring_buffer.clear();
|
self.ring_buffer.clear();
|
||||||
println!("Audio capture stopped");
|
println!("Audio capture stopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_processing_worker(&mut self){
|
fn run_processing_worker(&mut self) {
|
||||||
println!("Configuring audio processing worker");
|
println!("Configuring audio processing worker");
|
||||||
let worker_running = self.running.clone();
|
let worker_running = self.running.clone();
|
||||||
let event_bus = self.event_bus.clone();
|
let event_bus = self.event_bus.clone();
|
||||||
let input_config = self.microphone.get_input_config();
|
let stream_config = self.microphone.get_stream_config();
|
||||||
println!("Audio input config: sample rate: {}, channels: {}",
|
|
||||||
input_config.sample_rate().0, input_config.channels());
|
|
||||||
|
|
||||||
// création du worker opus
|
println!("Audio config: {} channels @ {}Hz",
|
||||||
|
stream_config.channels, stream_config.sample_rate.0);
|
||||||
|
|
||||||
|
// ✅ Simple : on assume 48kHz partout !
|
||||||
let opus = AudioOpus::new(48_000, 1, "voip");
|
let opus = AudioOpus::new(48_000, 1, "voip");
|
||||||
let mut encoder = opus.create_encoder().unwrap();
|
let mut encoder = opus.create_encoder().unwrap();
|
||||||
|
|
||||||
// création du worker resampler
|
|
||||||
let source_rate = input_config.sample_rate().0 as usize;
|
|
||||||
let mut resampler = if source_rate != 48_000 {
|
|
||||||
Some(Self::create_resampler(source_rate)) // ✅ Corrigé
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// création du ringbuffer
|
|
||||||
let reader = self.ring_buffer.reader();
|
let reader = self.ring_buffer.reader();
|
||||||
|
|
||||||
// Démarrage du worker
|
|
||||||
println!("Spawning audio processing thread");
|
println!("Spawning audio processing thread");
|
||||||
let stream_config = self.microphone.get_stream_config(); // ✅ Clone la config
|
|
||||||
self.worker = Some(thread::spawn(move || {
|
self.worker = Some(thread::spawn(move || {
|
||||||
println!("Audio processing thread started");
|
println!("Audio processing thread started");
|
||||||
|
|
||||||
let source_rate = stream_config.sample_rate.0 as usize; // ✅ Utilise stream_config
|
let frame_size = 48_000 * 10 / 1000; // ✅ 10ms = 480 samples @ 48kHz
|
||||||
let frame_size = source_rate * 10 / 1000; // 10ms en échantillons
|
let mut raw_buffer = vec![0i16; frame_size];
|
||||||
let mut raw_buffer = vec![0.0f32; frame_size];
|
|
||||||
|
|
||||||
while worker_running.load(Ordering::Relaxed) {
|
while worker_running.load(Ordering::Relaxed) {
|
||||||
let read_count = reader.pop_slice_blocking(&mut raw_buffer);
|
let _read_count = reader.pop_slice_blocking(&mut raw_buffer);
|
||||||
if !worker_running.load(Ordering::Relaxed){
|
if !worker_running.load(Ordering::Relaxed) {
|
||||||
println!("Audio processing thread stopping");
|
println!("Audio processing thread stopping");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resampling : 441→480, 48000→480, 96000→480, etc.
|
// ✅ Processing ultra-simple
|
||||||
let processed_audio = Self::process_audio_frame(
|
let processed_audio = Self::process_audio_frame(
|
||||||
&stream_config, // ✅ Utilise stream_config
|
&stream_config,
|
||||||
resampler.as_mut(),
|
|
||||||
&raw_buffer
|
&raw_buffer
|
||||||
);
|
);
|
||||||
// processed_audio est TOUJOURS 480 f32 (mono/48kHz)
|
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
event_bus.emit_sync(Event::AudioIn(processed_audio.clone()));
|
event_bus.emit_sync(Event::AudioIn(processed_audio.clone()));
|
||||||
|
|
||||||
match encoder.encode(&processed_audio){
|
match encoder.encode(&processed_audio) {
|
||||||
Ok(encoded_data) => {
|
Ok(encoded_data) => {
|
||||||
event_bus.emit_sync(Event::AudioEncoded(encoded_data))
|
event_bus.emit_sync(Event::AudioEncoded(encoded_data))
|
||||||
}
|
}
|
||||||
@@ -205,49 +182,22 @@ impl AudioCapture {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Standardise un buffer en mono float32 échantillonné à 48kHz.
|
// ✅ Super simple : juste conversion mono
|
||||||
fn process_audio_frame(
|
fn process_audio_frame(config: &StreamConfig, samples: &[i16]) -> Vec<i16> {
|
||||||
config: &StreamConfig,
|
Self::sample_to_mono(config.channels as usize, samples)
|
||||||
resampler: Option<&mut FftFixedInOut<f32>>,
|
|
||||||
samples: &[f32],
|
|
||||||
) -> Vec<f32> {
|
|
||||||
// 1. Conversion mono d'abord
|
|
||||||
let mono = Self::sample_to_mono(config.channels as usize, samples);
|
|
||||||
|
|
||||||
// 2. Resampling si nécessaire
|
|
||||||
if let Some(resampler) = resampler {
|
|
||||||
let input: Vec<&[f32]> = vec![&mono];
|
|
||||||
match resampler.process(&input, None) {
|
|
||||||
Ok(mut output) => output.remove(0),
|
|
||||||
Err(e) => {
|
|
||||||
println!("Erreur de resampling: {e}");
|
|
||||||
mono // Fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mono
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_to_mono(input_channels: usize, samples: &[f32]) -> Vec<f32> {
|
fn sample_to_mono(input_channels: usize, samples: &[i16]) -> Vec<i16> {
|
||||||
if input_channels == 1 {
|
if input_channels == 1 {
|
||||||
samples.to_vec()
|
samples.to_vec()
|
||||||
}else{
|
} else {
|
||||||
samples
|
samples
|
||||||
.chunks_exact(input_channels)
|
.chunks_exact(input_channels)
|
||||||
.map(|frame| frame.iter().copied().sum::<f32>() / input_channels as f32)
|
.map(|frame| {
|
||||||
|
let sum: i32 = frame.iter().map(|&s| s as i32).sum();
|
||||||
|
(sum / input_channels as i32) as i16
|
||||||
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Méthode corrigée avec le bon nom
|
|
||||||
fn create_resampler(source_rate: usize) -> FftFixedInOut<f32> {
|
|
||||||
println!("Creating resampler: {}Hz -> 48kHz", source_rate);
|
|
||||||
FftFixedInOut::<f32>::new(
|
|
||||||
source_rate,
|
|
||||||
48_000,
|
|
||||||
512, // chunk size
|
|
||||||
1, // mono après conversion
|
|
||||||
).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -65,8 +65,7 @@ impl AudioOpusEncoder {
|
|||||||
Ok(Self{audio_opus, encoder})
|
Ok(Self{audio_opus, encoder})
|
||||||
}
|
}
|
||||||
|
|
||||||
// old version i16 en input, on garde au cas ou ...
|
pub fn encode(&mut self, frames: &[i16]) -> Result<Vec<u8>, String> {
|
||||||
pub fn encode_i16(&mut self, frames: &[i16]) -> Result<Vec<u8>, String> {
|
|
||||||
let mut output = vec![0u8; 1276]; // 1276 octets (la vraie worst-case recommandée par Opus).
|
let mut output = vec![0u8; 1276]; // 1276 octets (la vraie worst-case recommandée par Opus).
|
||||||
let len = self.encoder.encode(frames, output.as_mut_slice())
|
let len = self.encoder.encode(frames, output.as_mut_slice())
|
||||||
.map_err(|e| format!("Erreur encodage: {:?}", e))?;
|
.map_err(|e| format!("Erreur encodage: {:?}", e))?;
|
||||||
@@ -74,21 +73,6 @@ impl AudioOpusEncoder {
|
|||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn encode(&mut self, frames: &[f32]) -> Result<Vec<u8>, String> {
|
|
||||||
// Conversion f32 -> i16 seulement ici
|
|
||||||
let frames_i16: Vec<i16> = frames.iter()
|
|
||||||
.map(|&sample| (sample * i16::MAX as f32)
|
|
||||||
.clamp(i16::MIN as f32, i16::MAX as f32) as i16)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut output = vec![0u8; 1276];
|
|
||||||
let len = self.encoder.encode(&frames_i16, output.as_mut_slice())
|
|
||||||
.map_err(|e| format!("Erreur encodage: {:?}", e))?;
|
|
||||||
output.truncate(len);
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 🔄 Approche avec buffer réutilisable (encore plus optimal)
|
// 🔄 Approche avec buffer réutilisable (encore plus optimal)
|
||||||
fn encode_reuse(&mut self, frames: &[i16], output: &mut Vec<u8>) -> Result<usize, String> {
|
fn encode_reuse(&mut self, frames: &[i16], output: &mut Vec<u8>) -> Result<usize, String> {
|
||||||
output.clear();
|
output.clear();
|
||||||
@@ -117,24 +101,10 @@ impl AudioOpusDecoder {
|
|||||||
Ok(Self{audio_opus, decoder})
|
Ok(Self{audio_opus, decoder})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_i16(&mut self, frames: &[u8]) -> Result<Vec<i16>, String> {
|
pub fn decode(&mut self, frames: &[u8]) -> Result<Vec<i16>, String> {
|
||||||
let mut output = vec![0i16; 5760];
|
let mut output = vec![0i16; 5760];
|
||||||
let len = self.decoder.decode(frames, output.as_mut_slice(), false).map_err(|e| format!("Erreur décodage: {:?}", e))?;
|
let len = self.decoder.decode(frames, output.as_mut_slice(), false).map_err(|e| format!("Erreur décodage: {:?}", e))?;
|
||||||
output.truncate(len);
|
output.truncate(len);
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode(&mut self, frames: &[u8]) -> Result<Vec<f32>, String> {
|
|
||||||
let mut output_i16 = vec![0i16; 5760];
|
|
||||||
let len = self.decoder.decode(frames, &mut output_i16, false)
|
|
||||||
.map_err(|e| format!("Erreur décodage: {:?}", e))?;
|
|
||||||
|
|
||||||
// Conversion i16 -> f32
|
|
||||||
let output_f32: Vec<f32> = output_i16[..len].iter()
|
|
||||||
.map(|&sample| sample as f32 / i16::MAX as f32)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(output_f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -40,13 +40,94 @@ impl Speaker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stream_config(&self) -> StreamConfig {
|
pub fn get_stream_config(&self) -> StreamConfig {
|
||||||
let config = self.get_output_config();
|
// Lister toutes les configurations supportées
|
||||||
let mut stream_config: StreamConfig = config.into();
|
self.print_supported_configs();
|
||||||
stream_config.channels = 2;
|
|
||||||
stream_config.sample_rate = SampleRate(44100);
|
StreamConfig {
|
||||||
// stream_config.buffer_size = BufferSize::Fixed(960);
|
channels: 2,
|
||||||
stream_config
|
sample_rate: SampleRate(44100),
|
||||||
|
buffer_size: BufferSize::Default
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_supported_configs(&self) {
|
||||||
|
println!("\n=== CONFIGURATIONS AUDIO DISPONIBLES ===");
|
||||||
|
|
||||||
|
// Configuration par défaut
|
||||||
|
match self.device.default_output_config() {
|
||||||
|
Ok(config) => {
|
||||||
|
println!("📌 Configuration par défaut:");
|
||||||
|
println!(" Canaux: {}", config.channels());
|
||||||
|
println!(" Sample Rate: {} Hz", config.sample_rate().0);
|
||||||
|
println!(" Format: {:?}", config.sample_format());
|
||||||
|
println!(" Buffer Size: {:?}", config.buffer_size());
|
||||||
|
},
|
||||||
|
Err(e) => println!("❌ Impossible d'obtenir la config par défaut: {}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toutes les configurations supportées
|
||||||
|
println!("\n📋 Toutes les configurations supportées:");
|
||||||
|
match self.device.supported_output_configs() {
|
||||||
|
Ok(configs) => {
|
||||||
|
for (i, config_range) in configs.enumerate() {
|
||||||
|
println!("\n Config #{}", i + 1);
|
||||||
|
println!(" Canaux: {}", config_range.channels());
|
||||||
|
println!(" Sample Rate: {} - {} Hz",
|
||||||
|
config_range.min_sample_rate().0,
|
||||||
|
config_range.max_sample_rate().0);
|
||||||
|
println!(" Format: {:?}", config_range.sample_format());
|
||||||
|
println!(" Buffer Size: {:?}", config_range.buffer_size());
|
||||||
|
|
||||||
|
// Suggestions de sample rates courants
|
||||||
|
let common_rates = [8000, 11025, 16000, 22050, 44100, 48000, 88200, 96000];
|
||||||
|
let mut supported_common = Vec::new();
|
||||||
|
for rate in common_rates {
|
||||||
|
if rate >= config_range.min_sample_rate().0 && rate <= config_range.max_sample_rate().0 {
|
||||||
|
supported_common.push(rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !supported_common.is_empty() {
|
||||||
|
println!(" Sample rates courants supportés: {:?}", supported_common);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => println!("❌ Impossible de lister les configs: {}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Informations sur le device
|
||||||
|
println!("\n🎧 Informations du device:");
|
||||||
|
if let Ok(name) = self.device.name() {
|
||||||
|
println!(" Nom: {}", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test de configurations spécifiques
|
||||||
|
println!("\n🧪 Test de configurations spécifiques:");
|
||||||
|
let test_configs = [
|
||||||
|
(44100, 1, "Mono 44.1kHz"),
|
||||||
|
(44100, 2, "Stéréo 44.1kHz"),
|
||||||
|
(48000, 1, "Mono 48kHz"),
|
||||||
|
(48000, 2, "Stéréo 48kHz"),
|
||||||
|
(22050, 2, "Stéréo 22.05kHz"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (sample_rate, channels, description) in test_configs {
|
||||||
|
let test_config = StreamConfig {
|
||||||
|
channels,
|
||||||
|
sample_rate: SampleRate(sample_rate),
|
||||||
|
buffer_size: BufferSize::Default
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test si cette config est supportée (tentative de création d'un stream fictif)
|
||||||
|
let dummy_callback = |_: &mut [f32], _: &cpal::OutputCallbackInfo| {};
|
||||||
|
match self.device.build_output_stream(&test_config, dummy_callback, |_| {}, None) {
|
||||||
|
Ok(_) => println!(" ✅ {} - SUPPORTÉ", description),
|
||||||
|
Err(_) => println!(" ❌ {} - NON SUPPORTÉ", description),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n===========================================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn build_stream<F>(&self, callback: F) -> Stream
|
pub fn build_stream<F>(&self, callback: F) -> Stream
|
||||||
where
|
where
|
||||||
@@ -104,7 +185,6 @@ impl AudioPlayback {
|
|||||||
if !stream_running.load(Ordering::Relaxed){
|
if !stream_running.load(Ordering::Relaxed){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
println!("Audio playback stream tick");
|
|
||||||
|
|
||||||
let audio_mixer = mixer.read(data.len());
|
let audio_mixer = mixer.read(data.len());
|
||||||
data.copy_from_slice(&audio_mixer);
|
data.copy_from_slice(&audio_mixer);
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
// use tokio::sync::mpsc;
|
|
||||||
use crate::network::protocol::{MessageClient, MessageServer};
|
use crate::network::protocol::{MessageClient, MessageServer};
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
AppStarted,
|
AppStarted,
|
||||||
AppStopped,
|
AppStopped,
|
||||||
|
|
||||||
AudioIn(Vec<f32>),
|
AudioIn(Vec<i16>),
|
||||||
AudioEncoded(Vec<u8>),
|
AudioEncoded(Vec<u8>),
|
||||||
|
|
||||||
PlaybackTick(usize),
|
PlaybackTick(usize),
|
||||||
// PlaybackRequest(Bytes),
|
|
||||||
|
|
||||||
NetConnected,
|
NetConnected,
|
||||||
NetDisconnected,
|
NetDisconnected,
|
||||||
NetIn(MessageServer),
|
NetIn(MessageServer),
|
||||||
NetOut(MessageClient),
|
NetOut(MessageClient),
|
||||||
|
|
||||||
|
|
||||||
UiStarted,
|
UiStarted,
|
||||||
UiStopped,
|
UiStopped,
|
||||||
|
|
||||||
|
|||||||
195
src-tauri/src/utils/audio_utils.rs
Normal file
195
src-tauri/src/utils/audio_utils.rs
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
use rubato::{Resampler, SincFixedIn, SincInterpolationType, SincInterpolationParameters, WindowFunction};
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Resampler audio optimisé avec rubato pour temps réel
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AudioResampler {
|
||||||
|
// État du resampler (recréé quand les paramètres changent)
|
||||||
|
state: Arc<Mutex<ResamplerState>>,
|
||||||
|
// Buffer de conversion réutilisable (évite allocations)
|
||||||
|
conversion_buffers: Arc<Mutex<ConversionBuffers>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ResamplerState {
|
||||||
|
resampler: Option<SincFixedIn<f32>>,
|
||||||
|
current_from_rate: u32,
|
||||||
|
current_to_rate: u32,
|
||||||
|
current_channels: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConversionBuffers {
|
||||||
|
f32_buffer: Vec<f32>,
|
||||||
|
planar_input: Vec<Vec<f32>>,
|
||||||
|
output_i16: Vec<i16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioResampler {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(ResamplerState {
|
||||||
|
resampler: None,
|
||||||
|
current_from_rate: 0,
|
||||||
|
current_to_rate: 0,
|
||||||
|
current_channels: 0,
|
||||||
|
})),
|
||||||
|
conversion_buffers: Arc::new(Mutex::new(ConversionBuffers {
|
||||||
|
f32_buffer: Vec::with_capacity(8192),
|
||||||
|
planar_input: Vec::new(),
|
||||||
|
output_i16: Vec::with_capacity(8192),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resample audio en gardant la continuité entre les chunks
|
||||||
|
pub fn resample(
|
||||||
|
&self,
|
||||||
|
input: &[i16],
|
||||||
|
from_sample_rate: u32,
|
||||||
|
to_sample_rate: u32,
|
||||||
|
channels: usize,
|
||||||
|
) -> Vec<i16> {
|
||||||
|
// ✅ Pas de conversion si même sample rate
|
||||||
|
if from_sample_rate == to_sample_rate || input.is_empty() {
|
||||||
|
return input.to_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
let mut buffers = self.conversion_buffers.lock();
|
||||||
|
|
||||||
|
// 🔄 Recrée le resampler si configuration changée
|
||||||
|
let need_new_resampler = state.resampler.is_none()
|
||||||
|
|| state.current_from_rate != from_sample_rate
|
||||||
|
|| state.current_to_rate != to_sample_rate
|
||||||
|
|| state.current_channels != channels;
|
||||||
|
|
||||||
|
if state.resampler.is_none()
|
||||||
|
|| state.current_from_rate != from_sample_rate
|
||||||
|
|| state.current_to_rate != to_sample_rate
|
||||||
|
|| state.current_channels != channels {
|
||||||
|
match Self::create_resampler(from_sample_rate, to_sample_rate, channels) {
|
||||||
|
Ok(new_resampler) => {
|
||||||
|
state.resampler = Some(new_resampler);
|
||||||
|
state.current_from_rate = from_sample_rate;
|
||||||
|
state.current_to_rate = to_sample_rate;
|
||||||
|
state.current_channels = channels;
|
||||||
|
println!("🔧 Resampler reconfiguré: {}Hz → {}Hz, {} canaux",
|
||||||
|
from_sample_rate, to_sample_rate, channels);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Erreur création resampler: {}", e);
|
||||||
|
return input.to_vec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🚀 Processing avec le resampler
|
||||||
|
if let Some(ref mut resampler) = state.resampler {
|
||||||
|
match Self::process_with_resampler(resampler, input, channels, &mut buffers) {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("❌ Erreur resampling: {}", e);
|
||||||
|
input.to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crée un resampler optimisé pour votre cas d'usage
|
||||||
|
fn create_resampler(
|
||||||
|
from_rate: u32,
|
||||||
|
to_rate: u32,
|
||||||
|
channels: usize,
|
||||||
|
) -> Result<SincFixedIn<f32>, Box<dyn std::error::Error>> {
|
||||||
|
let ratio = to_rate as f64 / from_rate as f64;
|
||||||
|
|
||||||
|
// 🎯 Paramètres optimisés pour audio temps réel de qualité
|
||||||
|
let params = SincInterpolationParameters {
|
||||||
|
sinc_len: 256, // Bon compromis qualité/performance
|
||||||
|
f_cutoff: 0.95, // Anti-aliasing fort
|
||||||
|
interpolation: SincInterpolationType::Linear, // Plus rapide que Cubic
|
||||||
|
oversampling_factor: 256,
|
||||||
|
window: WindowFunction::BlackmanHarris2,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chunk size optimisé pour vos frames audio
|
||||||
|
let chunk_size = 1024; // Compatible avec vos frames de 960-1024 samples
|
||||||
|
|
||||||
|
Ok(SincFixedIn::<f32>::new(
|
||||||
|
ratio,
|
||||||
|
2.0, // Max ratio change pour stabilité
|
||||||
|
params,
|
||||||
|
chunk_size,
|
||||||
|
channels,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process audio avec buffers réutilisables
|
||||||
|
fn process_with_resampler(
|
||||||
|
resampler: &mut SincFixedIn<f32>,
|
||||||
|
input: &[i16],
|
||||||
|
channels: usize,
|
||||||
|
buffers: &mut ConversionBuffers,
|
||||||
|
) -> Result<Vec<i16>, Box<dyn std::error::Error>> {
|
||||||
|
let frames = input.len() / channels;
|
||||||
|
|
||||||
|
// 🔄 1. Conversion i16 → f32 (réutilise buffer)
|
||||||
|
buffers.f32_buffer.clear();
|
||||||
|
buffers.f32_buffer.extend(input.iter().map(|&s| s as f32 / 32768.0));
|
||||||
|
|
||||||
|
// 🔄 2. Conversion interleaved → planar (réutilise buffers)
|
||||||
|
buffers.planar_input.clear();
|
||||||
|
buffers.planar_input.resize(channels, Vec::with_capacity(frames));
|
||||||
|
|
||||||
|
for ch in 0..channels {
|
||||||
|
buffers.planar_input[ch].clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (frame_idx, frame) in buffers.f32_buffer.chunks_exact(channels).enumerate() {
|
||||||
|
for (ch, &sample) in frame.iter().enumerate() {
|
||||||
|
buffers.planar_input[ch].push(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🎯 3. Resampling magique !
|
||||||
|
let output_planar = resampler.process(&buffers.planar_input, None)?;
|
||||||
|
|
||||||
|
// 🔄 4. Conversion planar → interleaved i16 (réutilise buffer)
|
||||||
|
let output_frames = output_planar[0].len();
|
||||||
|
buffers.output_i16.clear();
|
||||||
|
buffers.output_i16.reserve(output_frames * channels);
|
||||||
|
|
||||||
|
for frame_idx in 0..output_frames {
|
||||||
|
for ch in 0..channels {
|
||||||
|
let sample = (output_planar[ch][frame_idx] * 32767.0)
|
||||||
|
.round()
|
||||||
|
.clamp(-32768.0, 32767.0) as i16;
|
||||||
|
buffers.output_i16.push(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buffers.output_i16.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Réinitialise l'état interne (pour éviter glitches lors de changements)
|
||||||
|
pub fn reset(&self) {
|
||||||
|
let mut state = self.state.lock();
|
||||||
|
if let Some(ref mut resampler) = state.resampler {
|
||||||
|
let _ = resampler.reset();
|
||||||
|
}
|
||||||
|
println!("🔄 Resampler reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Version oneshot sans état (pour tests)
|
||||||
|
pub fn resample_oneshot(
|
||||||
|
input: &[i16],
|
||||||
|
from_rate: u32,
|
||||||
|
to_rate: u32,
|
||||||
|
channels: usize,
|
||||||
|
) -> Result<Vec<i16>, Box<dyn std::error::Error>> {
|
||||||
|
let resampler = AudioResampler::new();
|
||||||
|
Ok(resampler.resample(input, from_rate, to_rate, channels))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod ringbuf;
|
pub mod ringbuf;
|
||||||
pub mod real_time_event;
|
pub mod real_time_event;
|
||||||
pub mod shared_store;
|
pub mod shared_store;
|
||||||
|
pub mod audio_utils;
|
||||||
Reference in New Issue
Block a user