Compare commits
2 Commits
044f9781de
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f2b7e6e689 | |||
| 43f8d38cb2 |
@@ -5,7 +5,7 @@ use tokio;
|
||||
use tokio::sync::mpsc;
|
||||
use crate::core::capture::AudioCapture;
|
||||
use crate::core::mixer::AudioMixer;
|
||||
use crate::core::playback::AudioPlayback;
|
||||
use crate::core::playback::{AudioPlayback, Speaker};
|
||||
use crate::domain::audio_client::AudioClientManager;
|
||||
use crate::domain::event::{Event, EventBus};
|
||||
use crate::network::udp::UdpSession;
|
||||
@@ -46,7 +46,10 @@ impl OxSpeakApp {
|
||||
let audio_capture = AudioCapture::default(event_bus.clone());
|
||||
println!("Initializing audio client");
|
||||
let audio_client_manager = AudioClientManager::new();
|
||||
let audio_mixer = AudioMixer::new(audio_client_manager.clone());
|
||||
|
||||
// todo : pas idéal (la récup du sample_rate), car le mieux serais de récupérer ça dynamiquement. Peut être charger le mixer depuis audio_playback ?
|
||||
let stream_config = Speaker::default().get_stream_config();
|
||||
let audio_mixer = AudioMixer::new(stream_config.sample_rate.0 as usize, stream_config.channels as usize, audio_client_manager.clone());
|
||||
let audio_playback = AudioPlayback::default(event_bus.clone(), audio_mixer.clone());
|
||||
|
||||
// UdpSession
|
||||
|
||||
@@ -7,6 +7,7 @@ use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
// ✅ Supprimé rubato complètement !
|
||||
use crate::core::opus::AudioOpus;
|
||||
use crate::domain::event::{Event, EventBus};
|
||||
use crate::utils::audio_utils::AudioTools;
|
||||
use crate::utils::ringbuf::RingBuffer;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -18,7 +19,7 @@ pub struct AudioCapture {
|
||||
event_bus: EventBus,
|
||||
microphone: Microphone,
|
||||
running: Arc<AtomicBool>,
|
||||
ring_buffer: RingBuffer<i16>,
|
||||
ring_buffer: RingBuffer<f32>,
|
||||
steam: Option<Stream>,
|
||||
worker: Option<JoinHandle<()>>,
|
||||
}
|
||||
@@ -41,25 +42,12 @@ impl Microphone {
|
||||
}
|
||||
|
||||
pub fn get_stream_config(&self) -> StreamConfig {
|
||||
let supported_channels = self.get_supported_channels();
|
||||
|
||||
// Priorité : mono si supporté, sinon prend le premier disponible
|
||||
let channels = if supported_channels.contains(&1) {
|
||||
1 // ✅ Mono préféré
|
||||
} else {
|
||||
supported_channels.first().copied().unwrap_or(2) // Fallback
|
||||
};
|
||||
|
||||
StreamConfig {
|
||||
channels,
|
||||
sample_rate: SampleRate(48_000), // ✅ Force 48kHz
|
||||
buffer_size: BufferSize::Default,
|
||||
}
|
||||
self.get_input_config().into()
|
||||
}
|
||||
|
||||
pub fn build_stream<F>(&self, callback: F) -> Stream
|
||||
where
|
||||
F: FnMut(&[i16], &cpal::InputCallbackInfo) + Send + 'static,
|
||||
F: FnMut(&[f32], &cpal::InputCallbackInfo) + Send + 'static,
|
||||
{
|
||||
let config = self.get_stream_config();
|
||||
self.device.build_input_stream(
|
||||
@@ -138,11 +126,12 @@ impl AudioCapture {
|
||||
let worker_running = self.running.clone();
|
||||
let event_bus = self.event_bus.clone();
|
||||
let stream_config = self.microphone.get_stream_config();
|
||||
let sample_rate: usize = stream_config.sample_rate.0 as usize;
|
||||
let channels: usize = stream_config.channels as usize;
|
||||
|
||||
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();
|
||||
let reader = self.ring_buffer.reader();
|
||||
@@ -151,8 +140,8 @@ impl AudioCapture {
|
||||
self.worker = Some(thread::spawn(move || {
|
||||
println!("Audio processing thread started");
|
||||
|
||||
let frame_size = 48_000 * 10 / 1000; // ✅ 10ms = 480 samples @ 48kHz
|
||||
let mut raw_buffer = vec![0i16; frame_size];
|
||||
let frame_size = sample_rate * 10 / 1000; // ✅ 10ms = 480 samples @ 48kHz / 441 @ 44.1khz
|
||||
let mut raw_buffer = vec![0f32; frame_size];
|
||||
|
||||
while worker_running.load(Ordering::Relaxed) {
|
||||
let _read_count = reader.pop_slice_blocking(&mut raw_buffer);
|
||||
@@ -161,16 +150,10 @@ impl AudioCapture {
|
||||
break;
|
||||
}
|
||||
|
||||
// ✅ Processing ultra-simple
|
||||
let processed_audio = Self::process_audio_frame(
|
||||
&stream_config,
|
||||
&raw_buffer
|
||||
);
|
||||
let sample = AudioTools::sample_to_mono(&raw_buffer, channels);
|
||||
// todo : voir si il est nécessaire d'intégrer un resampling avec AudioResampler
|
||||
|
||||
// Events
|
||||
event_bus.emit_sync(Event::AudioIn(processed_audio.clone()));
|
||||
|
||||
match encoder.encode(&processed_audio) {
|
||||
match encoder.encode(&sample) {
|
||||
Ok(encoded_data) => {
|
||||
event_bus.emit_sync(Event::AudioEncoded(encoded_data))
|
||||
}
|
||||
@@ -178,6 +161,8 @@ impl AudioCapture {
|
||||
println!("Error encoding: {e}");
|
||||
}
|
||||
}
|
||||
// Events
|
||||
event_bus.emit_sync(Event::AudioIn(sample));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2,47 +2,84 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
use arc_swap::ArcSwap;
|
||||
use cpal::SampleRate;
|
||||
use crate::domain::audio_client::AudioClientManager;
|
||||
use crate::utils::audio_utils::{AudioResampler, AudioTools};
|
||||
use crate::utils::ringbuf::{RingBufReader, RingBufWriter, RingBuffer};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AudioMixer {
|
||||
audio_client_manager: AudioClientManager,
|
||||
buffer_writer: Arc<RingBufWriter<i16>>,
|
||||
buffer_reader: Arc<RingBufReader<i16>>
|
||||
buffer_writer: Arc<RingBufWriter<f32>>,
|
||||
buffer_reader: Arc<RingBufReader<f32>>,
|
||||
|
||||
internal_sample_rate: usize,
|
||||
output_channels: usize,
|
||||
output_sample_rate: usize,
|
||||
resampler: AudioResampler,
|
||||
}
|
||||
|
||||
impl AudioMixer {
|
||||
pub fn new(audio_client_manager: AudioClientManager) -> Self {
|
||||
pub fn new(output_sample_rate: usize, output_channels: usize, audio_client_manager: AudioClientManager) -> Self {
|
||||
let (buffer_writer, buffer_reader) = RingBuffer::new(2048).split();
|
||||
Self {
|
||||
audio_client_manager,
|
||||
buffer_writer: Arc::new(buffer_writer),
|
||||
buffer_reader: Arc::new(buffer_reader)
|
||||
buffer_reader: Arc::new(buffer_reader),
|
||||
internal_sample_rate: 48000,
|
||||
output_sample_rate,
|
||||
output_channels,
|
||||
resampler: AudioResampler::new(),
|
||||
}
|
||||
}
|
||||
pub fn mix_next_frame(&self, size: usize) {
|
||||
let mut frames = Vec::<Vec<i16>>::new();
|
||||
// Récupérer les buffers audio des utilisateurs, par défaut, ils sont en mono, donc size / 2
|
||||
// convertir en stéréo, donc size * 2 frames
|
||||
let users_audio = self.audio_client_manager.take_audio_collection(size/2).into_iter()
|
||||
.map(|audio| AudioMixer::mono_to_stereo(audio))
|
||||
.collect::<Vec<Vec<i16>>>();
|
||||
frames.extend_from_slice(&users_audio);
|
||||
pub fn mix_next_frame(&self, output_size: usize) {
|
||||
// 1. Calcule combien de samples 48kHz on doit récupérer
|
||||
let ratio = self.internal_sample_rate as f32 / self.output_sample_rate as f32;
|
||||
let internal_frames_needed = ((output_size / self.output_channels) as f32 * ratio).ceil() as usize;
|
||||
|
||||
// Récupérer tous les sons des notifications (pas encore dev)
|
||||
// 2. Récupère les données utilisateurs (48kHz mono)
|
||||
let users_audio = self.audio_client_manager.take_audio_collection(internal_frames_needed);
|
||||
|
||||
let mixed_frame = if frames.is_empty() {
|
||||
vec![0i16; size]
|
||||
}else{
|
||||
Self::mix_frames(&frames, size)
|
||||
// 3. Mix en 48kHz mono
|
||||
let mixed_internal = if users_audio.is_empty() {
|
||||
vec![0f32; internal_frames_needed]
|
||||
} else {
|
||||
AudioTools::mix_frames(&users_audio, internal_frames_needed)
|
||||
};
|
||||
|
||||
self.buffer_writer.push_slice_overwrite(&mixed_frame);
|
||||
// 3. Mix en 48kHz mono (résultat en f32)
|
||||
let mixed_internal = if users_audio.is_empty() {
|
||||
vec![0.0f32; internal_frames_needed]
|
||||
} else {
|
||||
AudioTools::mix_frames(&users_audio, internal_frames_needed)
|
||||
};
|
||||
|
||||
// 4. Resample 48kHz -> output_rate si nécessaire
|
||||
let resampled = if self.internal_sample_rate != self.output_sample_rate {
|
||||
self.resampler.resample(
|
||||
&mixed_internal,
|
||||
self.internal_sample_rate,
|
||||
self.output_sample_rate,
|
||||
1 // mono
|
||||
)
|
||||
} else {
|
||||
mixed_internal
|
||||
};
|
||||
|
||||
// 5. Convert mono -> output_channels
|
||||
let final_frame = AudioTools::change_channel_count(
|
||||
&resampled,
|
||||
1,
|
||||
self.output_channels
|
||||
);
|
||||
|
||||
// 6. Écrit dans le ringbuffer (pas besoin de resize exacte)
|
||||
self.buffer_writer.push_slice_overwrite(&final_frame);
|
||||
|
||||
}
|
||||
|
||||
pub fn read(&self, size: usize) -> Vec<i16> {
|
||||
let mut data = vec![0i16; size];
|
||||
pub fn read(&self, size: usize) -> Vec<f32> {
|
||||
let mut data = vec![0f32; size];
|
||||
// Essaie de pop autant d'échantillons que possible
|
||||
let read = self.buffer_reader.pop_slice(&mut data);
|
||||
// Si on n'a pas tout eu, les éléments restants sont déjà à 0
|
||||
@@ -51,36 +88,3 @@ impl AudioMixer {
|
||||
|
||||
|
||||
}
|
||||
|
||||
impl AudioMixer {
|
||||
fn mono_to_stereo(mono_samples: Vec<i16>) -> Vec<i16> {
|
||||
let mut stereo_data = Vec::with_capacity(mono_samples.len() * 2);
|
||||
|
||||
// Chaque échantillon mono devient deux échantillons stéréo identiques
|
||||
for sample in mono_samples {
|
||||
stereo_data.push(sample); // Canal gauche
|
||||
stereo_data.push(sample); // Canal droit
|
||||
}
|
||||
|
||||
stereo_data
|
||||
}
|
||||
|
||||
fn mix_frames(frames: &[Vec<i16>], size: usize) -> Vec<i16> {
|
||||
let mut mixed = vec![0i32; size];
|
||||
|
||||
for frame in frames {
|
||||
for (i, &sample) in frame.iter().enumerate() {
|
||||
if i < size {
|
||||
mixed[i] += sample as i32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let count = frames.len().max(1) as i32; // éviter la division par zéro
|
||||
mixed
|
||||
.into_iter()
|
||||
.map(|sample| (sample / count).clamp(i16::MIN as i32, i16::MAX as i32) as i16)
|
||||
.collect()
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use opus::{Application, Channels, Decoder, Encoder};
|
||||
use crate::utils::audio_utils::AudioSample;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AudioOpus{
|
||||
@@ -65,22 +66,31 @@ impl AudioOpusEncoder {
|
||||
Ok(Self{audio_opus, encoder})
|
||||
}
|
||||
|
||||
pub fn encode(&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 len = self.encoder.encode(frames, output.as_mut_slice())
|
||||
// Méthode générique qui accepte i16 ou f32
|
||||
pub fn encode<T: AudioSample>(&mut self, frames: &[T]) -> Result<Vec<u8>, String> {
|
||||
// Convertir tous les échantillons vers i16
|
||||
let i16_frames: Vec<i16> = frames.iter().map(|sample| sample.to_i16()).collect();
|
||||
|
||||
// Utiliser la logique d'encodage existante
|
||||
let mut output = vec![0u8; 1276];
|
||||
let len = self.encoder.encode(&i16_frames, 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<u8>) -> Result<usize, String> {
|
||||
// Version avec buffer réutilisable
|
||||
pub fn encode_into_slice<T: AudioSample>(&mut self, frames: &[T], output: &mut Vec<u8>) -> Result<usize, String> {
|
||||
let i16_frames: Vec<i16> = frames.iter().map(|sample| sample.to_i16()).collect();
|
||||
|
||||
output.clear();
|
||||
output.resize(1276, 0);
|
||||
let len = self.encoder.encode(frames, output.as_mut_slice()).unwrap();
|
||||
let len = self.encoder.encode(&i16_frames, output.as_mut_slice())
|
||||
.map_err(|e| format!("Erreur encodage: {:?}", e))?;
|
||||
output.truncate(len);
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub struct AudioOpusDecoder{
|
||||
@@ -101,10 +111,26 @@ impl AudioOpusDecoder {
|
||||
Ok(Self{audio_opus, decoder})
|
||||
}
|
||||
|
||||
pub fn decode(&mut self, frames: &[u8]) -> Result<Vec<i16>, 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);
|
||||
pub fn decode<T: AudioSample>(&mut self, frames: &[u8]) -> Result<Vec<T>, String> {
|
||||
let mut i16_output = vec![0i16; 5760];
|
||||
let len = self.decoder.decode(frames, i16_output.as_mut_slice(), false)
|
||||
.map_err(|e| format!("Erreur décodage: {:?}", e))?;
|
||||
i16_output.truncate(len);
|
||||
|
||||
let output: Vec<T> = i16_output.iter().map(|&sample| T::from_i16(sample)).collect();
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
// Décodage avec buffer réutilisable
|
||||
pub fn decode_into_slice<T: AudioSample>(&mut self, frames: &[u8], output: &mut Vec<T>) -> Result<usize, String> {
|
||||
let mut i16_buffer = vec![0i16; 5760];
|
||||
let len = self.decoder.decode(frames, i16_buffer.as_mut_slice(), false)
|
||||
.map_err(|e| format!("Erreur décodage: {:?}", e))?;
|
||||
i16_buffer.truncate(len);
|
||||
|
||||
output.clear();
|
||||
output.extend(i16_buffer.iter().map(|&sample| T::from_i16(sample)));
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
@@ -43,11 +43,7 @@ impl Speaker {
|
||||
// Lister toutes les configurations supportées
|
||||
self.print_supported_configs();
|
||||
|
||||
StreamConfig {
|
||||
channels: 2,
|
||||
sample_rate: SampleRate(44100),
|
||||
buffer_size: BufferSize::Default
|
||||
}
|
||||
self.get_output_config().into()
|
||||
}
|
||||
|
||||
pub fn print_supported_configs(&self) {
|
||||
@@ -131,7 +127,7 @@ impl Speaker {
|
||||
|
||||
pub fn build_stream<F>(&self, callback: F) -> Stream
|
||||
where
|
||||
F: FnMut(&mut [i16], &cpal::OutputCallbackInfo) + Send + 'static,
|
||||
F: FnMut(&mut [f32], &cpal::OutputCallbackInfo) + Send + 'static,
|
||||
{
|
||||
let config = self.get_stream_config();
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::utils::shared_store::SharedArcMap;
|
||||
pub struct AudioClient {
|
||||
uuid: uuid::Uuid,
|
||||
decode_sender: mpsc::Sender<DecodeRequest>,
|
||||
buffer_reader: RingBufReader<i16>,
|
||||
buffer_writer: RingBufWriter<i16>
|
||||
buffer_reader: RingBufReader<f32>,
|
||||
buffer_writer: RingBufWriter<f32>
|
||||
}
|
||||
|
||||
struct DecodeRequest {
|
||||
@@ -27,7 +27,7 @@ pub struct AudioClientManager {
|
||||
|
||||
impl AudioClient {
|
||||
pub fn new() -> Self {
|
||||
let (writer, reader) = RingBuffer::<i16>::new(4096).split();
|
||||
let (writer, reader) = RingBuffer::<f32>::new(4096).split();
|
||||
|
||||
let (decode_sender, mut decode_reader) = mpsc::channel::<DecodeRequest>(100);
|
||||
let writer_clone = writer.clone();
|
||||
@@ -39,13 +39,13 @@ impl AudioClient {
|
||||
while let Some(request) = decode_reader.recv().await {
|
||||
// si la séquence est "trop vieille" on la drop. (voir plus tard pour un système de rattrapage si c'est possible)
|
||||
if last_sequence < request.sequence {
|
||||
// todo : si le décodage est trop long, voir pour le mettre dans un thread
|
||||
// avec let result = tokio::task::spawn_blocking({
|
||||
// todo : si le décodage est trop long, voir pour le mettre dans un thread avec
|
||||
// let result = tokio::task::spawn_blocking({
|
||||
// let data = request.data.clone();
|
||||
// move || decoder.decode(&data)
|
||||
// }).await.unwrap();
|
||||
let start = std::time::Instant::now();
|
||||
let result = decoder.decode(&request.data);
|
||||
let result = decoder.decode::<f32>(&request.data);
|
||||
if start.elapsed() > Duration::from_millis(1) {
|
||||
println!("⚠️ Frame drop possible: {:?}", start.elapsed());
|
||||
}
|
||||
@@ -78,12 +78,12 @@ impl AudioClient {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn read_audio(&self, size: usize) -> Option<Vec<i16>> {
|
||||
pub fn read_audio(&self, size: usize) -> Option<Vec<f32>> {
|
||||
if self.buffer_reader.len() < size {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut buffer = vec![0i16; size];
|
||||
let mut buffer = vec![0f32; size];
|
||||
let read_count = self.buffer_reader.pop_slice(&mut buffer);
|
||||
|
||||
if read_count == size {
|
||||
@@ -122,7 +122,7 @@ impl AudioClientManager {
|
||||
let _ = self.audio_clients.get(&uuid).unwrap().write_audio(sequence, data);
|
||||
}
|
||||
|
||||
pub fn take_audio_collection(&self, size: usize) -> Vec<Vec<i16>> {
|
||||
pub fn take_audio_collection(&self, size: usize) -> Vec<Vec<f32>> {
|
||||
let mut buffers = Vec::new();
|
||||
|
||||
for client in self.audio_clients.values() {
|
||||
|
||||
@@ -5,7 +5,7 @@ pub enum Event {
|
||||
AppStarted,
|
||||
AppStopped,
|
||||
|
||||
AudioIn(Vec<i16>),
|
||||
AudioIn(Vec<f32>),
|
||||
AudioEncoded(Vec<u8>),
|
||||
|
||||
PlaybackTick(usize),
|
||||
|
||||
@@ -1,7 +1,71 @@
|
||||
use std::fmt::Debug;
|
||||
use rubato::{Resampler, SincFixedIn, SincInterpolationType, SincInterpolationParameters, WindowFunction};
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Trait pour les échantillons audio avec conversion automatique
|
||||
pub trait AudioSample: Copy + Clone + Debug + Send + Sync + 'static {
|
||||
fn to_i16(&self) -> i16;
|
||||
fn to_f32(&self) -> f32;
|
||||
fn from_i16(value: i16) -> Self;
|
||||
fn from_f32(value: f32) -> Self;
|
||||
fn zero() -> Self;
|
||||
fn clamp_audio(&self) -> Self;
|
||||
}
|
||||
|
||||
impl AudioSample for i16 {
|
||||
fn to_i16(&self) -> i16 {
|
||||
*self
|
||||
}
|
||||
|
||||
fn to_f32(&self) -> f32 {
|
||||
*self as f32 / i16::MAX as f32
|
||||
}
|
||||
|
||||
fn from_i16(value: i16) -> Self {
|
||||
value
|
||||
}
|
||||
|
||||
fn from_f32(value: f32) -> Self {
|
||||
(value.clamp(-1.0, 1.0) * i16::MAX as f32) as i16
|
||||
}
|
||||
|
||||
fn zero() -> Self {
|
||||
0i16
|
||||
}
|
||||
|
||||
fn clamp_audio(&self) -> Self {
|
||||
*self // i16 est déjà dans sa plage valide
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioSample for f32 {
|
||||
fn to_i16(&self) -> i16 {
|
||||
(self.clamp(-1.0, 1.0) * i16::MAX as f32) as i16
|
||||
}
|
||||
|
||||
fn to_f32(&self) -> f32 {
|
||||
*self
|
||||
}
|
||||
|
||||
fn from_i16(value: i16) -> Self {
|
||||
value as f32 / i16::MAX as f32
|
||||
}
|
||||
|
||||
fn from_f32(value: f32) -> Self {
|
||||
value
|
||||
}
|
||||
|
||||
fn zero() -> Self {
|
||||
0.0f32
|
||||
}
|
||||
|
||||
fn clamp_audio(&self) -> Self {
|
||||
self.clamp(-1.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Resampler audio optimisé avec rubato pour temps réel
|
||||
#[derive(Clone)]
|
||||
pub struct AudioResampler {
|
||||
@@ -13,15 +77,15 @@ pub struct AudioResampler {
|
||||
|
||||
struct ResamplerState {
|
||||
resampler: Option<SincFixedIn<f32>>,
|
||||
current_from_rate: u32,
|
||||
current_to_rate: u32,
|
||||
current_from_rate: usize,
|
||||
current_to_rate: usize,
|
||||
current_channels: usize,
|
||||
}
|
||||
|
||||
struct ConversionBuffers {
|
||||
f32_buffer: Vec<f32>,
|
||||
planar_input: Vec<Vec<f32>>,
|
||||
output_i16: Vec<i16>,
|
||||
planar_output_buffer: Vec<Vec<f32>>, // Ajouté pour éviter des allocs
|
||||
}
|
||||
|
||||
impl AudioResampler {
|
||||
@@ -36,19 +100,19 @@ impl AudioResampler {
|
||||
conversion_buffers: Arc::new(Mutex::new(ConversionBuffers {
|
||||
f32_buffer: Vec::with_capacity(8192),
|
||||
planar_input: Vec::new(),
|
||||
output_i16: Vec::with_capacity(8192),
|
||||
planar_output_buffer: Vec::new(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resample audio en gardant la continuité entre les chunks
|
||||
pub fn resample(
|
||||
/// Resample audio générique - fonctionne avec i16 et f32
|
||||
pub fn resample<T: AudioSample>(
|
||||
&self,
|
||||
input: &[i16],
|
||||
from_sample_rate: u32,
|
||||
to_sample_rate: u32,
|
||||
input: &[T],
|
||||
from_sample_rate: usize,
|
||||
to_sample_rate: usize,
|
||||
channels: usize,
|
||||
) -> Vec<i16> {
|
||||
) -> Vec<T> {
|
||||
// ✅ Pas de conversion si même sample rate
|
||||
if from_sample_rate == to_sample_rate || input.is_empty() {
|
||||
return input.to_vec();
|
||||
@@ -58,11 +122,6 @@ impl AudioResampler {
|
||||
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
|
||||
@@ -85,7 +144,7 @@ impl AudioResampler {
|
||||
|
||||
// 🚀 Processing avec le resampler
|
||||
if let Some(ref mut resampler) = state.resampler {
|
||||
match Self::process_with_resampler(resampler, input, channels, &mut buffers) {
|
||||
match Self::process_with_resampler_generic(resampler, input, channels, &mut buffers) {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Erreur resampling: {}", e);
|
||||
@@ -99,8 +158,8 @@ impl AudioResampler {
|
||||
|
||||
/// Crée un resampler optimisé pour votre cas d'usage
|
||||
fn create_resampler(
|
||||
from_rate: u32,
|
||||
to_rate: u32,
|
||||
from_rate: usize,
|
||||
to_rate: usize,
|
||||
channels: usize,
|
||||
) -> Result<SincFixedIn<f32>, Box<dyn std::error::Error>> {
|
||||
let ratio = to_rate as f64 / from_rate as f64;
|
||||
@@ -126,20 +185,20 @@ impl AudioResampler {
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Process audio avec buffers réutilisables
|
||||
fn process_with_resampler(
|
||||
/// Process audio générique avec buffers réutilisables
|
||||
fn process_with_resampler_generic<T: AudioSample>(
|
||||
resampler: &mut SincFixedIn<f32>,
|
||||
input: &[i16],
|
||||
input: &[T],
|
||||
channels: usize,
|
||||
buffers: &mut ConversionBuffers,
|
||||
) -> Result<Vec<i16>, Box<dyn std::error::Error>> {
|
||||
) -> Result<Vec<T>, Box<dyn std::error::Error>> {
|
||||
let frames = input.len() / channels;
|
||||
|
||||
// 🔄 1. Conversion i16 → f32 (réutilise buffer)
|
||||
// 🔄 1. Conversion vers f32 (utilise AudioSample trait)
|
||||
buffers.f32_buffer.clear();
|
||||
buffers.f32_buffer.extend(input.iter().map(|&s| s as f32 / 32768.0));
|
||||
buffers.f32_buffer.extend(input.iter().map(|sample| sample.to_f32()));
|
||||
|
||||
// 🔄 2. Conversion interleaved → planar (réutilise buffers)
|
||||
// 🔄 2. Conversion interleaved → planar
|
||||
buffers.planar_input.clear();
|
||||
buffers.planar_input.resize(channels, Vec::with_capacity(frames));
|
||||
|
||||
@@ -147,30 +206,117 @@ impl AudioResampler {
|
||||
buffers.planar_input[ch].clear();
|
||||
}
|
||||
|
||||
for (frame_idx, frame) in buffers.f32_buffer.chunks_exact(channels).enumerate() {
|
||||
for frame in buffers.f32_buffer.chunks_exact(channels) {
|
||||
for (ch, &sample) in frame.iter().enumerate() {
|
||||
buffers.planar_input[ch].push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 3. Resampling magique !
|
||||
// 🎯 3. Resampling magique ! (Rubato travaille directement en f32)
|
||||
let output_planar = resampler.process(&buffers.planar_input, None)?;
|
||||
|
||||
// 🔄 4. Conversion planar → interleaved i16 (réutilise buffer)
|
||||
// 🔄 4. Conversion planar → interleaved avec type générique
|
||||
let output_frames = output_planar[0].len();
|
||||
buffers.output_i16.clear();
|
||||
buffers.output_i16.reserve(output_frames * channels);
|
||||
let mut output = Vec::with_capacity(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);
|
||||
let f32_sample = output_planar[ch][frame_idx];
|
||||
// Utilise AudioSample pour reconvertir au format d'origine
|
||||
let converted_sample = T::from_f32(f32_sample).clamp_audio();
|
||||
output.push(converted_sample);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buffers.output_i16.clone())
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Version spécialisée pour f32 (plus efficace, évite conversions inutiles)
|
||||
pub fn resample_f32(
|
||||
&self,
|
||||
input: &[f32],
|
||||
from_sample_rate: usize,
|
||||
to_sample_rate: usize,
|
||||
channels: usize,
|
||||
) -> Vec<f32> {
|
||||
// ✅ 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
|
||||
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;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("❌ Erreur création resampler: {}", e);
|
||||
return input.to_vec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🚀 Processing optimisé pour f32 (pas de conversion)
|
||||
if let Some(ref mut resampler) = state.resampler {
|
||||
match Self::process_f32_direct(resampler, input, channels, &mut buffers) {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
eprintln!("❌ Erreur resampling f32: {}", e);
|
||||
input.to_vec()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
input.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
/// Processing optimisé pour f32 natif (pas de conversions)
|
||||
fn process_f32_direct(
|
||||
resampler: &mut SincFixedIn<f32>,
|
||||
input: &[f32],
|
||||
channels: usize,
|
||||
buffers: &mut ConversionBuffers,
|
||||
) -> Result<Vec<f32>, Box<dyn std::error::Error>> {
|
||||
let frames = input.len() / channels;
|
||||
|
||||
// 🔄 1. Conversion interleaved → planar (pas de conversion de format!)
|
||||
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 in input.chunks_exact(channels) {
|
||||
for (ch, &sample) in frame.iter().enumerate() {
|
||||
buffers.planar_input[ch].push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
// 🎯 2. Resampling direct !
|
||||
let output_planar = resampler.process(&buffers.planar_input, None)?;
|
||||
|
||||
// 🔄 3. Conversion planar → interleaved (pas de conversion de format!)
|
||||
let output_frames = output_planar[0].len();
|
||||
let mut output = Vec::with_capacity(output_frames * channels);
|
||||
|
||||
for frame_idx in 0..output_frames {
|
||||
for ch in 0..channels {
|
||||
output.push(output_planar[ch][frame_idx]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Réinitialise l'état interne (pour éviter glitches lors de changements)
|
||||
@@ -182,14 +328,219 @@ impl AudioResampler {
|
||||
println!("🔄 Resampler reset");
|
||||
}
|
||||
|
||||
/// Version oneshot sans état (pour tests)
|
||||
pub fn resample_oneshot(
|
||||
input: &[i16],
|
||||
from_rate: u32,
|
||||
to_rate: u32,
|
||||
/// Version oneshot générique sans état (pour tests)
|
||||
pub fn resample_oneshot<T: AudioSample>(
|
||||
input: &[T],
|
||||
from_rate: usize,
|
||||
to_rate: usize,
|
||||
channels: usize,
|
||||
) -> Result<Vec<i16>, Box<dyn std::error::Error>> {
|
||||
) -> Result<Vec<T>, Box<dyn std::error::Error>> {
|
||||
let resampler = AudioResampler::new();
|
||||
Ok(resampler.resample(input, from_rate, to_rate, channels))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AudioTools;
|
||||
|
||||
impl AudioTools {
|
||||
/// Convertit un échantillon audio multi-canaux en mono (générique)
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `samples` - Les échantillons audio (format interleaved)
|
||||
/// * `channels` - Le nombre de canaux d'entrée
|
||||
///
|
||||
/// # Returns
|
||||
/// Un Vec<T> contenant les échantillons mono
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// // Avec i16
|
||||
///
|
||||
/// let stereo_i16 = vec![100i16, 200i16, 150i16, 250i16];
|
||||
/// let mono_i16 = AudioTools::sample_to_mono(&stereo_i16, 2);
|
||||
///
|
||||
/// // Avec f32
|
||||
/// let stereo_f32 = vec![0.5f32, -0.3f32, 0.2f32, 0.8f32];
|
||||
/// let mono_f32 = AudioTools::sample_to_mono(&stereo_f32, 2);
|
||||
/// ```
|
||||
pub fn sample_to_mono<T: AudioSample>(samples: &[T], channels: usize) -> Vec<T> {
|
||||
// Si déjà mono, retourne une copie
|
||||
if channels <= 1 {
|
||||
return samples.to_vec();
|
||||
}
|
||||
|
||||
// Calcule le nombre de frames
|
||||
let frame_count = samples.len() / channels;
|
||||
let mut mono_samples = Vec::with_capacity(frame_count);
|
||||
|
||||
// Pour chaque frame, calcule la moyenne des canaux
|
||||
for frame_start in (0..samples.len()).step_by(channels) {
|
||||
let frame_end = (frame_start + channels).min(samples.len());
|
||||
let frame = &samples[frame_start..frame_end];
|
||||
|
||||
// Calcule la moyenne en utilisant f32 comme format intermédiaire
|
||||
let sum: f32 = frame.iter().map(|sample| sample.to_f32()).sum();
|
||||
let average = sum / channels as f32;
|
||||
|
||||
// Convertit de retour au format original et clamp
|
||||
let mono_sample = T::from_f32(average).clamp_audio();
|
||||
mono_samples.push(mono_sample);
|
||||
}
|
||||
|
||||
mono_samples
|
||||
}
|
||||
|
||||
/// Version avec stratégies de mixage (générique)
|
||||
pub fn sample_to_mono_with_strategy<T: AudioSample>(
|
||||
samples: &[T],
|
||||
channels: usize,
|
||||
strategy: MonoMixStrategy
|
||||
) -> Vec<T> {
|
||||
if channels <= 1 {
|
||||
return samples.to_vec();
|
||||
}
|
||||
|
||||
let frame_count = samples.len() / channels;
|
||||
let mut mono_samples = Vec::with_capacity(frame_count);
|
||||
|
||||
for frame_start in (0..samples.len()).step_by(channels) {
|
||||
let frame_end = (frame_start + channels).min(samples.len());
|
||||
let frame = &samples[frame_start..frame_end];
|
||||
|
||||
let mono_sample = match strategy {
|
||||
MonoMixStrategy::Average => {
|
||||
let sum: f32 = frame.iter().map(|s| s.to_f32()).sum();
|
||||
let average = sum / channels as f32;
|
||||
T::from_f32(average).clamp_audio()
|
||||
}
|
||||
MonoMixStrategy::LeftChannel => frame[0],
|
||||
MonoMixStrategy::RightChannel => {
|
||||
if channels > 1 { frame[1] } else { frame[0] }
|
||||
}
|
||||
MonoMixStrategy::Max => {
|
||||
let max_sample = frame.iter()
|
||||
.max_by(|a, b| {
|
||||
a.to_f32().abs().partial_cmp(&b.to_f32().abs())
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap_or(&frame[0]);
|
||||
*max_sample
|
||||
}
|
||||
MonoMixStrategy::Rms => {
|
||||
// Root Mean Square
|
||||
let sum_squares: f32 = frame.iter()
|
||||
.map(|s| {
|
||||
let val = s.to_f32();
|
||||
val * val
|
||||
})
|
||||
.sum();
|
||||
let rms = (sum_squares / channels as f32).sqrt();
|
||||
|
||||
// Préserve le signe général
|
||||
let sum_sign: f32 = frame.iter().map(|s| s.to_f32()).sum();
|
||||
let final_value = if sum_sign >= 0.0 { rms } else { -rms };
|
||||
|
||||
T::from_f32(final_value).clamp_audio()
|
||||
}
|
||||
};
|
||||
|
||||
mono_samples.push(mono_sample);
|
||||
}
|
||||
|
||||
mono_samples
|
||||
}
|
||||
|
||||
/// Conversion mono vers stéréo (générique)
|
||||
pub fn mono_to_stereo<T: AudioSample>(mono_samples: &[T]) -> Vec<T> {
|
||||
let mut stereo_data = Vec::with_capacity(mono_samples.len() * 2);
|
||||
|
||||
for &sample in mono_samples {
|
||||
stereo_data.push(sample); // Canal gauche
|
||||
stereo_data.push(sample); // Canal droit
|
||||
}
|
||||
|
||||
stereo_data
|
||||
}
|
||||
|
||||
/// Conversion entre formats d'échantillons
|
||||
pub fn convert_format<From: AudioSample, To: AudioSample>(samples: &[From]) -> Vec<To> {
|
||||
samples.iter()
|
||||
.map(|&sample| To::from_f32(sample.to_f32()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Mix plusieurs frames audio ensemble (générique)
|
||||
pub fn mix_frames<T: AudioSample>(frames: &[Vec<T>], target_size: usize) -> Vec<T> {
|
||||
if frames.is_empty() {
|
||||
return vec![T::zero(); target_size];
|
||||
}
|
||||
|
||||
let mut mixed = vec![0.0f32; target_size];
|
||||
|
||||
// Mix tous les frames en f32 pour éviter les débordements
|
||||
for frame in frames {
|
||||
for (i, &sample) in frame.iter().enumerate() {
|
||||
if i < target_size {
|
||||
mixed[i] += sample.to_f32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let count = frames.len().max(1) as f32;
|
||||
|
||||
// Convertit de retour au format cible avec normalisation
|
||||
mixed.into_iter()
|
||||
.map(|sample| T::from_f32(sample / count).clamp_audio())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Utilitaire pour changer le nombre de canaux
|
||||
pub fn change_channel_count<T: AudioSample>(
|
||||
samples: &[T],
|
||||
from_channels: usize,
|
||||
to_channels: usize,
|
||||
) -> Vec<T> {
|
||||
match (from_channels, to_channels) {
|
||||
(1, 2) => Self::mono_to_stereo(samples),
|
||||
(2, 1) => Self::sample_to_mono(samples, 2),
|
||||
(from, to) if from == to => samples.to_vec(),
|
||||
(from, 1) => Self::sample_to_mono(samples, from),
|
||||
(1, to) => {
|
||||
// Mono vers multi-canaux : duplique sur tous les canaux
|
||||
let mut result = Vec::with_capacity(samples.len() * to);
|
||||
for &sample in samples {
|
||||
for _ in 0..to {
|
||||
result.push(sample);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
(from, to) => {
|
||||
// Cas complexe : passe par mono puis étend
|
||||
let mono = Self::sample_to_mono(samples, from);
|
||||
Self::change_channel_count(&mono, 1, to)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stratégies de conversion multi-canaux vers mono
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MonoMixStrategy {
|
||||
/// Moyenne de tous les canaux (par défaut)
|
||||
Average,
|
||||
/// Utilise seulement le canal gauche
|
||||
LeftChannel,
|
||||
/// Utilise seulement le canal droit
|
||||
RightChannel,
|
||||
/// Prend l'échantillon avec l'amplitude maximale
|
||||
Max,
|
||||
/// Root Mean Square - plus précis énergétiquement
|
||||
Rms,
|
||||
}
|
||||
|
||||
impl Default for MonoMixStrategy {
|
||||
fn default() -> Self {
|
||||
Self::Average
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user