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)) } }