diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 2d0032a..74fd7a3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -35,4 +35,4 @@ moka = {version = "0.12", features = ["future"] } arc-swap = "1.7" crossbeam-channel = "0.5" kanal = "0.1" -rubato = "0.16.2" +rubato = "0.16" \ No newline at end of file diff --git a/src-tauri/src/core/capture.rs b/src-tauri/src/core/capture.rs index 5e9dfe4..c2a0fdc 100644 --- a/src-tauri/src/core/capture.rs +++ b/src-tauri/src/core/capture.rs @@ -4,7 +4,7 @@ use std::thread; use std::thread::JoinHandle; use cpal::{default_host, BufferSize, Device, SampleRate, Stream, StreamConfig, SupportedStreamConfig}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; -use rubato::{FftFixedInOut, Resampler}; +// ✅ Supprimé rubato complètement ! use crate::core::opus::AudioOpus; use crate::domain::event::{Event, EventBus}; use crate::utils::ringbuf::RingBuffer; @@ -18,7 +18,7 @@ pub struct AudioCapture { event_bus: EventBus, microphone: Microphone, running: Arc, - ring_buffer: RingBuffer, + ring_buffer: RingBuffer, steam: Option, worker: Option>, } @@ -26,9 +26,7 @@ pub struct AudioCapture { impl Microphone { pub fn new(device: Device) -> Self { println!("Initializing microphone with device: {}", device.name().unwrap_or_else(|_| "Unknown".to_string())); - Self { - device - } + Self { device } } pub fn default() -> Self { @@ -54,17 +52,16 @@ impl Microphone { StreamConfig { channels, - sample_rate: SampleRate(48_000), + sample_rate: SampleRate(48_000), // ✅ Force 48kHz buffer_size: BufferSize::Default, } } pub fn build_stream(&self, callback: F) -> Stream where - F: FnMut(&[f32], &cpal::InputCallbackInfo) + Send + 'static, + F: FnMut(&[i16], &cpal::InputCallbackInfo) + Send + 'static, { let config = self.get_stream_config(); - self.device.build_input_stream( &config, callback, @@ -73,7 +70,6 @@ impl Microphone { ).unwrap() } - // helpers pub fn get_supported_channels(&self) -> Vec { self.device.supported_input_configs() .map(|configs| configs.map(|c| c.channels()).collect()) @@ -108,9 +104,7 @@ impl AudioCapture { let writer = self.ring_buffer.writer(); let stream_running = self.running.clone(); let stream = self.microphone.build_stream(move |data, _| { - if !stream_running.load(Ordering::Relaxed){ - return; - } + if !stream_running.load(Ordering::Relaxed) { return; } writer.push_slice_overwrite(data); }); stream.play().unwrap(); @@ -126,74 +120,57 @@ impl AudioCapture { pub async fn stop(&mut self) { println!("Stopping audio capture"); self.running.store(false, Ordering::Relaxed); - println!("Releasing audio stream"); self.steam = None; self.ring_buffer.force_wake_up(); - // code possiblement bloquant, wrap vers un thread tokio bloquant if let Some(worker) = self.worker.take() { println!("Waiting for audio processing worker to finish"); tokio::task::spawn_blocking(move || { worker.join().unwrap(); }).await.unwrap(); } - println!("Clearing ring buffer"); self.ring_buffer.clear(); println!("Audio capture stopped"); } - fn run_processing_worker(&mut self){ + fn run_processing_worker(&mut self) { println!("Configuring audio processing worker"); let worker_running = self.running.clone(); let event_bus = self.event_bus.clone(); - let input_config = self.microphone.get_input_config(); - println!("Audio input config: sample rate: {}, channels: {}", - input_config.sample_rate().0, input_config.channels()); + let stream_config = self.microphone.get_stream_config(); - // 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 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(); - // Démarrage du worker println!("Spawning audio processing thread"); - let stream_config = self.microphone.get_stream_config(); // ✅ Clone la config self.worker = Some(thread::spawn(move || { println!("Audio processing thread started"); - let source_rate = stream_config.sample_rate.0 as usize; // ✅ Utilise stream_config - let frame_size = source_rate * 10 / 1000; // 10ms en échantillons - let mut raw_buffer = vec![0.0f32; frame_size]; + let frame_size = 48_000 * 10 / 1000; // ✅ 10ms = 480 samples @ 48kHz + let mut raw_buffer = vec![0i16; frame_size]; while worker_running.load(Ordering::Relaxed) { - let read_count = reader.pop_slice_blocking(&mut raw_buffer); - if !worker_running.load(Ordering::Relaxed){ + let _read_count = reader.pop_slice_blocking(&mut raw_buffer); + if !worker_running.load(Ordering::Relaxed) { println!("Audio processing thread stopping"); break; } - // Resampling : 441→480, 48000→480, 96000→480, etc. + // ✅ Processing ultra-simple let processed_audio = Self::process_audio_frame( - &stream_config, // ✅ Utilise stream_config - resampler.as_mut(), + &stream_config, &raw_buffer ); - // processed_audio est TOUJOURS 480 f32 (mono/48kHz) // Events event_bus.emit_sync(Event::AudioIn(processed_audio.clone())); - match encoder.encode(&processed_audio){ + match encoder.encode(&processed_audio) { Ok(encoded_data) => { event_bus.emit_sync(Event::AudioEncoded(encoded_data)) } @@ -205,49 +182,22 @@ impl AudioCapture { })); } - /// Standardise un buffer en mono float32 échantillonné à 48kHz. - fn process_audio_frame( - config: &StreamConfig, - resampler: Option<&mut FftFixedInOut>, - samples: &[f32], - ) -> Vec { - // 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 - } + // ✅ Super simple : juste conversion mono + fn process_audio_frame(config: &StreamConfig, samples: &[i16]) -> Vec { + Self::sample_to_mono(config.channels as usize, samples) } - fn sample_to_mono(input_channels: usize, samples: &[f32]) -> Vec { + fn sample_to_mono(input_channels: usize, samples: &[i16]) -> Vec { if input_channels == 1 { samples.to_vec() - }else{ + } else { samples .chunks_exact(input_channels) - .map(|frame| frame.iter().copied().sum::() / 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() } } - - // ✅ Méthode corrigée avec le bon nom - fn create_resampler(source_rate: usize) -> FftFixedInOut { - println!("Creating resampler: {}Hz -> 48kHz", source_rate); - FftFixedInOut::::new( - source_rate, - 48_000, - 512, // chunk size - 1, // mono après conversion - ).unwrap() - } } \ No newline at end of file diff --git a/src-tauri/src/core/opus.rs b/src-tauri/src/core/opus.rs index 0f53339..6452a9c 100644 --- a/src-tauri/src/core/opus.rs +++ b/src-tauri/src/core/opus.rs @@ -65,8 +65,7 @@ impl AudioOpusEncoder { Ok(Self{audio_opus, encoder}) } - // old version i16 en input, on garde au cas ou ... - pub fn encode_i16(&mut self, frames: &[i16]) -> Result, String> { + pub fn encode(&mut self, frames: &[i16]) -> Result, String> { 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()) .map_err(|e| format!("Erreur encodage: {:?}", e))?; @@ -74,21 +73,6 @@ impl AudioOpusEncoder { Ok(output) } - pub fn encode(&mut self, frames: &[f32]) -> Result, String> { - // Conversion f32 -> i16 seulement ici - let frames_i16: Vec = 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) fn encode_reuse(&mut self, frames: &[i16], output: &mut Vec) -> Result { output.clear(); @@ -117,24 +101,10 @@ impl AudioOpusDecoder { Ok(Self{audio_opus, decoder}) } - pub fn decode_i16(&mut self, frames: &[u8]) -> Result, String> { + pub fn decode(&mut self, frames: &[u8]) -> Result, String> { 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))?; output.truncate(len); Ok(output) } - - pub fn decode(&mut self, frames: &[u8]) -> Result, 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 = output_i16[..len].iter() - .map(|&sample| sample as f32 / i16::MAX as f32) - .collect(); - - Ok(output_f32) - } - } \ No newline at end of file diff --git a/src-tauri/src/core/playback.rs b/src-tauri/src/core/playback.rs index 0d58db9..8d39a48 100644 --- a/src-tauri/src/core/playback.rs +++ b/src-tauri/src/core/playback.rs @@ -40,14 +40,95 @@ impl Speaker { } pub fn get_stream_config(&self) -> StreamConfig { - let config = self.get_output_config(); - let mut stream_config: StreamConfig = config.into(); - stream_config.channels = 2; - stream_config.sample_rate = SampleRate(44100); - // stream_config.buffer_size = BufferSize::Fixed(960); - stream_config + // Lister toutes les configurations supportées + self.print_supported_configs(); + + StreamConfig { + channels: 2, + 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(&self, callback: F) -> Stream where F: FnMut(&mut [i16], &cpal::OutputCallbackInfo) + Send + 'static, @@ -104,7 +185,6 @@ impl AudioPlayback { if !stream_running.load(Ordering::Relaxed){ return; } - println!("Audio playback stream tick"); let audio_mixer = mixer.read(data.len()); data.copy_from_slice(&audio_mixer); diff --git a/src-tauri/src/domain/event.rs b/src-tauri/src/domain/event.rs index 712561d..d5ba80b 100644 --- a/src-tauri/src/domain/event.rs +++ b/src-tauri/src/domain/event.rs @@ -1,26 +1,23 @@ use bytes::Bytes; -// use tokio::sync::mpsc; use crate::network::protocol::{MessageClient, MessageServer}; pub enum Event { AppStarted, AppStopped, - AudioIn(Vec), + AudioIn(Vec), AudioEncoded(Vec), PlaybackTick(usize), - // PlaybackRequest(Bytes), NetConnected, NetDisconnected, NetIn(MessageServer), NetOut(MessageClient), - UiStarted, UiStopped, - + TaskTick } @@ -34,16 +31,16 @@ impl EventBus { let (sender, receiver) = kanal::bounded_async::(4096); (Self { sender }, receiver) } - + pub async fn emit(&self, event: Event) { // s'utilise de cette façon : bus.emit(Event::AudioIn {Vec[0,1,2,3]}.await; let _ = self.sender.send(event).await; } - + pub fn emit_sync(&self, event: Event) { let _ = self.sender.try_send(event); } - + pub fn clone_sender(&self) -> kanal::AsyncSender { self.sender.clone() } diff --git a/src-tauri/src/utils/audio_utils.rs b/src-tauri/src/utils/audio_utils.rs new file mode 100644 index 0000000..f37b78b --- /dev/null +++ b/src-tauri/src/utils/audio_utils.rs @@ -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>, + // Buffer de conversion réutilisable (évite allocations) + conversion_buffers: Arc>, +} + +struct ResamplerState { + resampler: Option>, + current_from_rate: u32, + current_to_rate: u32, + current_channels: usize, +} + +struct ConversionBuffers { + f32_buffer: Vec, + planar_input: Vec>, + output_i16: Vec, +} + +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 { + // ✅ 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, Box> { + 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::::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, + input: &[i16], + channels: usize, + buffers: &mut ConversionBuffers, + ) -> Result, Box> { + 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, Box> { + let resampler = AudioResampler::new(); + Ok(resampler.resample(input, from_rate, to_rate, channels)) + } +} \ No newline at end of file diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index 9523a66..b589a36 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -1,3 +1,4 @@ pub mod ringbuf; pub mod real_time_event; -pub mod shared_store; \ No newline at end of file +pub mod shared_store; +pub mod audio_utils; \ No newline at end of file