Compare commits
4 Commits
21164df8cd
...
044f9781de
| Author | SHA1 | Date | |
|---|---|---|---|
| 044f9781de | |||
| db8e876c1c | |||
| d7a7af9eb4 | |||
| a1f829e2e6 |
@@ -5,3 +5,10 @@ This template should help get you started developing with Tauri + Vue 3 in Vite.
|
|||||||
## Recommended IDE Setup
|
## Recommended IDE Setup
|
||||||
|
|
||||||
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
- [VS Code](https://code.visualstudio.com/) + [Vue - Official](https://marketplace.visualstudio.com/items?itemName=Vue.volar) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
|
||||||
|
|
||||||
|
|
||||||
|
## todo :
|
||||||
|
|
||||||
|
- rendre les échantillonage d'entré et de sorti audio
|
||||||
|
- conversion une fois le paquet reçu en entré en mono/48000hz
|
||||||
|
- à la réception, adapter à l'échantillonnage de la sortie audio.
|
||||||
90
src-tauri/Cargo.lock
generated
90
src-tauri/Cargo.lock
generated
@@ -1945,6 +1945,16 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kanal"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e3953adf0cd667798b396c2fa13552d6d9b3269d7dd1154c4c416442d1ff574"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"lock_api",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyboard-types"
|
name = "keyboard-types"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@@ -2270,6 +2280,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2287,6 +2306,15 @@ dependencies = [
|
|||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2637,9 +2665,11 @@ dependencies = [
|
|||||||
"cpal",
|
"cpal",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"event-listener",
|
"event-listener",
|
||||||
|
"kanal",
|
||||||
"moka",
|
"moka",
|
||||||
"opus",
|
"opus",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"rubato",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
@@ -2956,6 +2986,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "primal-check"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc0d895b311e3af9902528fbb8f928688abbd95872819320517cc24ca6b2bd08"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@@ -3134,6 +3173,15 @@ version = "0.6.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "realfft"
|
||||||
|
version = "3.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677"
|
||||||
|
dependencies = [
|
||||||
|
"rustfft",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
@@ -3253,6 +3301,18 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rubato"
|
||||||
|
version = "0.16.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5258099699851cfd0082aeb645feb9c084d9a5e1f1b8d5372086b989fc5e56a1"
|
||||||
|
dependencies = [
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"realfft",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.25"
|
version = "0.1.25"
|
||||||
@@ -3268,6 +3328,20 @@ dependencies = [
|
|||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustfft"
|
||||||
|
version = "6.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6f140db74548f7c9d7cce60912c9ac414e74df5e718dc947d514b051b42f3f4"
|
||||||
|
dependencies = [
|
||||||
|
"num-complex",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
"primal-check",
|
||||||
|
"strength_reduce",
|
||||||
|
"transpose",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -3677,6 +3751,12 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strength_reduce"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.8.9"
|
version = "0.8.9"
|
||||||
@@ -4435,6 +4515,16 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "transpose"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"strength_reduce",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tray-icon"
|
name = "tray-icon"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
|||||||
@@ -34,3 +34,5 @@ bytes = "1.10"
|
|||||||
moka = {version = "0.12", features = ["future"] }
|
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"
|
||||||
|
rubato = "0.16"
|
||||||
@@ -4,6 +4,9 @@ use tauri::{AppHandle, Emitter, Listener};
|
|||||||
use tokio;
|
use tokio;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use crate::core::capture::AudioCapture;
|
use crate::core::capture::AudioCapture;
|
||||||
|
use crate::core::mixer::AudioMixer;
|
||||||
|
use crate::core::playback::AudioPlayback;
|
||||||
|
use crate::domain::audio_client::AudioClientManager;
|
||||||
use crate::domain::event::{Event, EventBus};
|
use crate::domain::event::{Event, EventBus};
|
||||||
use crate::network::udp::UdpSession;
|
use crate::network::udp::UdpSession;
|
||||||
use crate::runtime::dispatcher::Dispatcher;
|
use crate::runtime::dispatcher::Dispatcher;
|
||||||
@@ -12,13 +15,18 @@ pub struct OxSpeakApp {
|
|||||||
// Communication inter-thread
|
// Communication inter-thread
|
||||||
event_bus: EventBus,
|
event_bus: EventBus,
|
||||||
dispatcher: Dispatcher,
|
dispatcher: Dispatcher,
|
||||||
event_rx: Option<mpsc::Receiver<Event>>,
|
event_rx: kanal::AsyncReceiver<Event>,
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
udp_session: UdpSession,
|
udp_session: UdpSession,
|
||||||
|
|
||||||
// audio
|
// audio
|
||||||
audio_capture: AudioCapture,
|
audio_capture: AudioCapture,
|
||||||
|
audio_playback: AudioPlayback,
|
||||||
|
audio_mixer: AudioMixer,
|
||||||
|
|
||||||
|
// audio_client
|
||||||
|
audio_client_manager: AudioClientManager,
|
||||||
|
|
||||||
// Tauri handle
|
// Tauri handle
|
||||||
tauri_handle: AppHandle
|
tauri_handle: AppHandle
|
||||||
@@ -36,48 +44,58 @@ impl OxSpeakApp {
|
|||||||
// todo : pour le moment, paramètre par défaut, on verra plus tard pour dynamiser ça
|
// todo : pour le moment, paramètre par défaut, on verra plus tard pour dynamiser ça
|
||||||
println!("Initializing audio capture");
|
println!("Initializing audio capture");
|
||||||
let audio_capture = AudioCapture::default(event_bus.clone());
|
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());
|
||||||
|
let audio_playback = AudioPlayback::default(event_bus.clone(), audio_mixer.clone());
|
||||||
|
|
||||||
// UdpSession
|
// UdpSession
|
||||||
println!("Initializing UDP session");
|
println!("Initializing UDP session");
|
||||||
let udp_session = UdpSession::new(event_bus.clone());
|
let udp_session = UdpSession::new(event_bus.clone());
|
||||||
|
|
||||||
|
// UdpClient
|
||||||
|
|
||||||
|
|
||||||
// Dispatcher - Communication inter-components
|
// Dispatcher - Communication inter-components
|
||||||
println!("Initializing event dispatcher");
|
println!("Initializing event dispatcher");
|
||||||
let mut dispatcher = Dispatcher::new(event_bus.clone(), udp_session.clone(), tauri_handle.clone());
|
let mut dispatcher = Dispatcher::new(event_bus.clone(), udp_session.clone(), audio_client_manager.clone(), audio_mixer.clone(),tauri_handle.clone());
|
||||||
|
|
||||||
println!("OxSpeakApp initialization complete");
|
println!("OxSpeakApp initialization complete");
|
||||||
Self {
|
Self {
|
||||||
event_bus,
|
event_bus,
|
||||||
dispatcher,
|
dispatcher,
|
||||||
event_rx: Some(event_rx),
|
event_rx,
|
||||||
udp_session,
|
udp_session,
|
||||||
audio_capture,
|
audio_capture,
|
||||||
|
audio_client_manager,
|
||||||
|
audio_playback,
|
||||||
|
audio_mixer,
|
||||||
tauri_handle,
|
tauri_handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub async fn start(&mut self) {
|
pub async fn start(&mut self) {
|
||||||
println!("Starting OxSpeakApp");
|
println!("Starting OxSpeakApp");
|
||||||
|
|
||||||
// dispatcher - lancement du process pour la communication inter-process
|
// dispatcher - lancement du process pour la communication inter-process
|
||||||
println!("Starting event dispatcher");
|
println!("Starting 4 instances of event dispatcher");
|
||||||
let mut dispatcher = self.dispatcher.clone();
|
for _ in 0..4 {
|
||||||
// Prendre l'ownership du receiver (event_rx)
|
let dispatcher = self.dispatcher.clone();
|
||||||
if let Some(event_rx) = self.event_rx.take() {
|
let event_rx = self.event_rx.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
dispatcher.start(event_rx).await
|
dispatcher.start(event_rx).await
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Démarrer la connexion réseau
|
// Démarrer la connexion réseau
|
||||||
println!("Connecting to UDP server at 127.0.0.1:5000");
|
println!("Connecting to UDP server at 82.64.205.121:5000");
|
||||||
self.udp_session.connect("127.0.0.1:5000").await;
|
self.udp_session.connect("82.64.205.121:5000").await;
|
||||||
|
|
||||||
// Démarrer l'audio-capture
|
// Démarrer l'audio-capture
|
||||||
println!("Starting audio capture");
|
println!("Starting audio capture");
|
||||||
self.audio_capture.start().await;
|
self.audio_capture.start().await;
|
||||||
|
self.audio_playback.start().await;
|
||||||
|
|
||||||
println!("OxSpeakApp started successfully");
|
println!("OxSpeakApp started successfully");
|
||||||
|
|
||||||
let _ = self.tick_tasks().await;
|
let _ = self.tick_tasks().await;
|
||||||
|
|||||||
@@ -4,6 +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};
|
||||||
|
// ✅ 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;
|
||||||
@@ -25,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 {
|
||||||
@@ -42,12 +41,20 @@ impl Microphone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stream_config(&self) -> StreamConfig {
|
pub fn get_stream_config(&self) -> StreamConfig {
|
||||||
let config = self.get_input_config();
|
let supported_channels = self.get_supported_channels();
|
||||||
let mut stream_config: StreamConfig = config.into();
|
|
||||||
stream_config.channels = 1;
|
// Priorité : mono si supporté, sinon prend le premier disponible
|
||||||
stream_config.sample_rate = SampleRate(48000);
|
let channels = if supported_channels.contains(&1) {
|
||||||
stream_config.buffer_size = BufferSize::Fixed(960);
|
1 // ✅ Mono préféré
|
||||||
stream_config
|
} else {
|
||||||
|
supported_channels.first().copied().unwrap_or(2) // Fallback
|
||||||
|
};
|
||||||
|
|
||||||
|
StreamConfig {
|
||||||
|
channels,
|
||||||
|
sample_rate: SampleRate(48_000), // ✅ Force 48kHz
|
||||||
|
buffer_size: BufferSize::Default,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_stream<F>(&self, callback: F) -> Stream
|
pub fn build_stream<F>(&self, callback: F) -> Stream
|
||||||
@@ -55,7 +62,6 @@ impl Microphone {
|
|||||||
F: FnMut(&[i16], &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,
|
||||||
@@ -63,6 +69,12 @@ impl Microphone {
|
|||||||
None
|
None
|
||||||
).unwrap()
|
).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_supported_channels(&self) -> Vec<u16> {
|
||||||
|
self.device.supported_input_configs()
|
||||||
|
.map(|configs| configs.map(|c| c.channels()).collect())
|
||||||
|
.unwrap_or(vec![1])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioCapture {
|
impl AudioCapture {
|
||||||
@@ -92,14 +104,12 @@ 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();
|
||||||
self.steam = Some(stream);
|
self.steam = Some(stream);
|
||||||
println!("Audio stream started");
|
println!("Audio capture stream started");
|
||||||
|
|
||||||
// Audio processing worker
|
// Audio processing worker
|
||||||
println!("Starting audio processing worker");
|
println!("Starting audio processing worker");
|
||||||
@@ -110,54 +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());
|
|
||||||
let opus = AudioOpus::new(input_config.sample_rate().0, input_config.channels(), "voip");
|
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 mut encoder = opus.create_encoder().unwrap();
|
||||||
let reader = self.ring_buffer.reader();
|
let reader = self.ring_buffer.reader();
|
||||||
|
|
||||||
println!("Spawning audio processing thread");
|
println!("Spawning audio processing thread");
|
||||||
self.worker = Some(thread::spawn(move || {
|
self.worker = Some(thread::spawn(move || {
|
||||||
println!("Audio processing thread started");
|
println!("Audio processing thread started");
|
||||||
let mut frame = [0i16; 960];
|
|
||||||
let mut frame_count = 0;
|
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) {
|
while worker_running.load(Ordering::Relaxed) {
|
||||||
let _ = reader.pop_slice_blocking(&mut frame);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame_count += 1;
|
// ✅ Processing ultra-simple
|
||||||
if frame_count % 100 == 0 {
|
let processed_audio = Self::process_audio_frame(
|
||||||
println!("Processed {} audio frames", frame_count);
|
&stream_config,
|
||||||
}
|
&raw_buffer
|
||||||
|
);
|
||||||
|
|
||||||
let raw_data = frame.to_vec();
|
// Events
|
||||||
event_bus.emit_sync(Event::AudioIn(raw_data));
|
event_bus.emit_sync(Event::AudioIn(processed_audio.clone()));
|
||||||
|
|
||||||
match encoder.encode(&frame){
|
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))
|
||||||
}
|
}
|
||||||
@@ -166,13 +179,25 @@ impl AudioCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("Audio processing thread finished after processing {} frames", frame_count);
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl AudioCapture {
|
// ✅ Super simple : juste conversion mono
|
||||||
fn audio_processing(){
|
fn process_audio_frame(config: &StreamConfig, samples: &[i16]) -> Vec<i16> {
|
||||||
|
Self::sample_to_mono(config.channels as usize, samples)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_to_mono(input_channels: usize, samples: &[i16]) -> Vec<i16> {
|
||||||
|
if input_channels == 1 {
|
||||||
|
samples.to_vec()
|
||||||
|
} else {
|
||||||
|
samples
|
||||||
|
.chunks_exact(input_channels)
|
||||||
|
.map(|frame| {
|
||||||
|
let sum: i32 = frame.iter().map(|&s| s as i32).sum();
|
||||||
|
(sum / input_channels as i32) as i16
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1 +1,86 @@
|
|||||||
// aller pick l'audio des clients
|
// version pull et ringbuf based - buffer de 2048 ou 4096 (donc 1/2 ou 3/4 trames de retard)
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use crate::domain::audio_client::AudioClientManager;
|
||||||
|
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>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioMixer {
|
||||||
|
pub fn new(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Récupérer tous les sons des notifications (pas encore dev)
|
||||||
|
|
||||||
|
let mixed_frame = if frames.is_empty() {
|
||||||
|
vec![0i16; size]
|
||||||
|
}else{
|
||||||
|
Self::mix_frames(&frames, size)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.buffer_writer.push_slice_overwrite(&mixed_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, size: usize) -> Vec<i16> {
|
||||||
|
let mut data = vec![0i16; 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
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src-tauri/src/core/mixer_arcswap.rs
Normal file
78
src-tauri/src/core/mixer_arcswap.rs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// version pull et arcswap based - buffer vraiment faible, 1 trame de retard seulement.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use arc_swap::ArcSwap;
|
||||||
|
use crate::domain::audio_client::AudioClientManager;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AudioMixer {
|
||||||
|
audio_client_manager: AudioClientManager,
|
||||||
|
buffer: Arc<ArcSwap<Vec<i16>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioMixer {
|
||||||
|
pub fn new(audio_client_manager: AudioClientManager) -> Self {
|
||||||
|
Self {
|
||||||
|
audio_client_manager,
|
||||||
|
buffer: Arc::new(ArcSwap::from_pointee(Vec::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);
|
||||||
|
|
||||||
|
// Récupérer tous les sons des notifications (pas encore dev)
|
||||||
|
|
||||||
|
let mixed_frame = if frames.is_empty() {
|
||||||
|
vec![0i16; size]
|
||||||
|
}else{
|
||||||
|
Self::mix_frames(&frames, size)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.buffer.store(Arc::new(mixed_frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&self, size: usize) -> Vec<i16> {
|
||||||
|
let mut data = (**self.buffer.load()).clone();
|
||||||
|
data.resize(size, 0);
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,9 +1,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
|
use std::time::Instant;
|
||||||
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};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use crate::domain::event::EventBus;
|
use crate::core::mixer::AudioMixer;
|
||||||
|
use crate::domain::event::{Event, EventBus};
|
||||||
use crate::utils::real_time_event::RealTimeEvent;
|
use crate::utils::real_time_event::RealTimeEvent;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -17,7 +19,7 @@ pub struct AudioPlayback {
|
|||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
stream: Option<Stream>,
|
stream: Option<Stream>,
|
||||||
worker: Option<JoinHandle<()>>,
|
worker: Option<JoinHandle<()>>,
|
||||||
next_tick: RealTimeEvent
|
mixer: AudioMixer
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Speaker {
|
impl Speaker {
|
||||||
@@ -33,18 +35,99 @@ impl Speaker {
|
|||||||
Speaker::new(device)
|
Speaker::new(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_input_config(&self) -> SupportedStreamConfig {
|
pub fn get_output_config(&self) -> SupportedStreamConfig {
|
||||||
self.device.default_output_config().unwrap()
|
self.device.default_output_config().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_stream_config(&self) -> StreamConfig {
|
pub fn get_stream_config(&self) -> StreamConfig {
|
||||||
let config = self.get_input_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(48000);
|
StreamConfig {
|
||||||
stream_config.buffer_size = BufferSize::Fixed(1920);
|
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
|
||||||
@@ -58,43 +141,64 @@ impl Speaker {
|
|||||||
|err| println!("Error output stream: {err}"),
|
|err| println!("Error output stream: {err}"),
|
||||||
None
|
None
|
||||||
).unwrap()
|
).unwrap()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioPlayback {
|
impl AudioPlayback {
|
||||||
pub fn new(event_bus: EventBus, speaker: Speaker) -> Self {
|
pub fn new(event_bus: EventBus, speaker: Speaker, mixer: AudioMixer) -> Self {
|
||||||
Self {
|
Self {
|
||||||
event_bus,
|
event_bus,
|
||||||
speaker,
|
speaker,
|
||||||
running: Arc::new(AtomicBool::new(false)),
|
running: Arc::new(AtomicBool::new(false)),
|
||||||
stream: None,
|
stream: None,
|
||||||
worker: None,
|
worker: None,
|
||||||
next_tick: RealTimeEvent::new(),
|
mixer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn default(event_bus: EventBus) -> Self {
|
pub fn default(event_bus: EventBus, mixer: AudioMixer) -> Self {
|
||||||
let speaker = Speaker::default();
|
let speaker = Speaker::default();
|
||||||
AudioPlayback::new(event_bus, speaker)
|
AudioPlayback::new(event_bus, speaker, mixer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(&mut self) {
|
pub async fn start(&mut self) {
|
||||||
|
self.running.store(true, Ordering::SeqCst);
|
||||||
}
|
let stream_running = self.running.clone();
|
||||||
|
let event_bus = self.event_bus.clone();
|
||||||
pub async fn stop(&mut self) {
|
let mixer = self.mixer.clone();
|
||||||
self.running.store(false, std::sync::atomic::Ordering::SeqCst);
|
|
||||||
|
|
||||||
// stream cpal
|
// stream cpal
|
||||||
println!("Setting up audio playback stream...");
|
println!("Setting up audio playback stream...");
|
||||||
let stream_running = self.running.clone();
|
let last_time = Mutex::new(Instant::now());
|
||||||
let stream = self.speaker.build_stream(move |data, _| {
|
let stream = self.speaker.build_stream(move |data, info| {
|
||||||
|
println!(
|
||||||
|
"CALLBACK : reçu {} samples, type info = {:?}",
|
||||||
|
data.len(),
|
||||||
|
info
|
||||||
|
);
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut last = last_time.lock().unwrap();
|
||||||
|
let dt = now.duration_since(*last);
|
||||||
|
println!("Callback audio appelée chaque {:?} ms (≈ {:.1} Hz)", dt.as_millis(), 1000.0 / dt.as_millis().max(1) as f32);
|
||||||
|
*last = now;
|
||||||
|
|
||||||
if !stream_running.load(Ordering::Relaxed){
|
if !stream_running.load(Ordering::Relaxed){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// aller récupérer 1920 sur un buffer
|
|
||||||
// écrire le contenu dans data
|
let audio_mixer = mixer.read(data.len());
|
||||||
|
data.copy_from_slice(&audio_mixer);
|
||||||
|
// println!("data content : {:?}", data);
|
||||||
|
|
||||||
|
let _ = event_bus.emit_sync(Event::PlaybackTick(data.len()));
|
||||||
});
|
});
|
||||||
|
stream.play().unwrap();
|
||||||
|
self.stream = Some(stream);
|
||||||
|
println!("Audio playback stream started");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop(&mut self) {
|
||||||
|
self.running.store(false, Ordering::SeqCst);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,11 +8,11 @@ use crate::core::opus::{AudioOpus, AudioOpusDecoder};
|
|||||||
use crate::utils::ringbuf::{RingBufReader, RingBufWriter, RingBuffer};
|
use crate::utils::ringbuf::{RingBufReader, RingBufWriter, RingBuffer};
|
||||||
use crate::utils::shared_store::SharedArcMap;
|
use crate::utils::shared_store::SharedArcMap;
|
||||||
|
|
||||||
struct AudioClient {
|
pub struct AudioClient {
|
||||||
uuid: uuid::Uuid,
|
uuid: uuid::Uuid,
|
||||||
decode_sender: mpsc::Sender<DecodeRequest>,
|
decode_sender: mpsc::Sender<DecodeRequest>,
|
||||||
buffer_reader: RingBufReader<Vec<i16>>,
|
buffer_reader: RingBufReader<i16>,
|
||||||
buffer_writer: RingBufWriter<Vec<i16>>
|
buffer_writer: RingBufWriter<i16>
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DecodeRequest {
|
struct DecodeRequest {
|
||||||
@@ -21,21 +21,23 @@ struct DecodeRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AudioClientManager {
|
pub struct AudioClientManager {
|
||||||
audio_clients: SharedArcMap<uuid::Uuid, AudioClient>,
|
audio_clients: SharedArcMap<uuid::Uuid, AudioClient>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioClient {
|
impl AudioClient {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let (writer, reader) = RingBuffer::<Vec<i16>>::new(1024).split();
|
let (writer, reader) = RingBuffer::<i16>::new(4096).split();
|
||||||
|
|
||||||
let (decode_sender, mut decode_reader) = mpsc::channel::<DecodeRequest>(100);
|
let (decode_sender, mut decode_reader) = mpsc::channel::<DecodeRequest>(100);
|
||||||
|
let writer_clone = writer.clone();
|
||||||
let decode_handle = tokio::spawn(async move {
|
let decode_handle = tokio::spawn(async move {
|
||||||
let mut decoder = AudioOpus::new(44800, 1, "voip")
|
let mut decoder = AudioOpus::new(48000, 1, "voip")
|
||||||
.create_decoder().unwrap();
|
.create_decoder().unwrap();
|
||||||
|
|
||||||
let mut last_sequence: u16 = 0;
|
let mut last_sequence: u16 = 0;
|
||||||
while let Some(request) = decode_reader.recv().await {
|
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 ratrapage si c'est possible)
|
// 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 {
|
if last_sequence < request.sequence {
|
||||||
// todo : si le décodage est trop long, voir pour le mettre dans un thread
|
// todo : si le décodage est trop long, voir pour le mettre dans un thread
|
||||||
// avec let result = tokio::task::spawn_blocking({
|
// avec let result = tokio::task::spawn_blocking({
|
||||||
@@ -50,7 +52,7 @@ impl AudioClient {
|
|||||||
match result {
|
match result {
|
||||||
Ok(audio_frame) => {
|
Ok(audio_frame) => {
|
||||||
// Pousser la frame complète dans le buffer
|
// Pousser la frame complète dans le buffer
|
||||||
writer.push(audio_frame);
|
writer_clone.push_slice_overwrite(&audio_frame);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Erreur de décodage audio : {}", e);
|
eprintln!("Erreur de décodage audio : {}", e);
|
||||||
@@ -69,19 +71,66 @@ impl AudioClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write_audio(&self, sequence: u16, data: Bytes) {
|
pub fn write_audio(&self, sequence: u16, data: Bytes) {
|
||||||
let _ = self.decode_sender.send(DecodeRequest {
|
let _ = self.decode_sender.try_send(DecodeRequest {
|
||||||
data,
|
data,
|
||||||
sequence
|
sequence
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_audio(&self, size: usize) -> Option<Vec<i16>> {
|
||||||
|
if self.buffer_reader.len() < size {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer = vec![0i16; size];
|
||||||
|
let read_count = self.buffer_reader.pop_slice(&mut buffer);
|
||||||
|
|
||||||
|
if read_count == size {
|
||||||
|
Some(buffer)
|
||||||
|
}else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl AudioClientManager {
|
impl AudioClientManager {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
audio_clients: SharedArcMap::new()
|
audio_clients: SharedArcMap::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn audio_client_exists(&self, uuid: uuid::Uuid) -> bool {
|
||||||
|
self.audio_clients.contains_key(&uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_audio_client(&self, uuid: uuid::Uuid) -> Option<Arc<AudioClient>> {
|
||||||
|
self.audio_clients.get(&uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_audio_client(&self, uuid: uuid::Uuid, audio_client: AudioClient) {
|
||||||
|
self.audio_clients.insert(uuid, audio_client);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_audio_client(&self, uuid: uuid::Uuid) {
|
||||||
|
self.audio_clients.remove(&uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_audio_to_client(&self, uuid: uuid::Uuid, sequence: u16, data: Bytes) {
|
||||||
|
let _ = self.audio_clients.get(&uuid).unwrap().write_audio(sequence, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn take_audio_collection(&self, size: usize) -> Vec<Vec<i16>> {
|
||||||
|
let mut buffers = Vec::new();
|
||||||
|
|
||||||
|
for client in self.audio_clients.values() {
|
||||||
|
if let Some(buffer) = client.read_audio(size) {
|
||||||
|
buffers.push(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffers
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use tokio::sync::mpsc;
|
use bytes::Bytes;
|
||||||
use crate::network::protocol::{MessageClient, MessageServer};
|
use crate::network::protocol::{MessageClient, MessageServer};
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
@@ -8,12 +8,13 @@ pub enum Event {
|
|||||||
AudioIn(Vec<i16>),
|
AudioIn(Vec<i16>),
|
||||||
AudioEncoded(Vec<u8>),
|
AudioEncoded(Vec<u8>),
|
||||||
|
|
||||||
|
PlaybackTick(usize),
|
||||||
|
|
||||||
NetConnected,
|
NetConnected,
|
||||||
NetDisconnected,
|
NetDisconnected,
|
||||||
NetIn(MessageServer),
|
NetIn(MessageServer),
|
||||||
NetOut(MessageClient),
|
NetOut(MessageClient),
|
||||||
|
|
||||||
|
|
||||||
UiStarted,
|
UiStarted,
|
||||||
UiStopped,
|
UiStopped,
|
||||||
|
|
||||||
@@ -22,12 +23,12 @@ pub enum Event {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct EventBus {
|
pub struct EventBus {
|
||||||
pub sender: mpsc::Sender<Event>
|
pub sender: kanal::AsyncSender<Event>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventBus {
|
impl EventBus {
|
||||||
pub fn new() -> (Self, mpsc::Receiver<Event>) {
|
pub fn new() -> (Self, kanal::AsyncReceiver<Event>) {
|
||||||
let (sender, receiver) = mpsc::channel(4096);
|
let (sender, receiver) = kanal::bounded_async::<Event>(4096);
|
||||||
(Self { sender }, receiver)
|
(Self { sender }, receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ impl EventBus {
|
|||||||
let _ = self.sender.try_send(event);
|
let _ = self.sender.try_send(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clone_sender(&self) -> mpsc::Sender<Event> {
|
pub fn clone_sender(&self) -> kanal::AsyncSender<Event> {
|
||||||
self.sender.clone()
|
self.sender.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,8 @@ use bytes::Bytes;
|
|||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use tokio::task::AbortHandle;
|
use tokio::task::AbortHandle;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use crate::core::mixer::AudioMixer;
|
||||||
|
use crate::domain::audio_client::{AudioClient, AudioClientManager};
|
||||||
use crate::domain::event::{Event, EventBus};
|
use crate::domain::event::{Event, EventBus};
|
||||||
use crate::network::protocol::{MessageClient, MessageServer};
|
use crate::network::protocol::{MessageClient, MessageServer};
|
||||||
use crate::network::udp::UdpSession;
|
use crate::network::udp::UdpSession;
|
||||||
@@ -20,11 +22,14 @@ struct PingInfo {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
event_bus: EventBus,
|
event_bus: Arc<EventBus>,
|
||||||
|
|
||||||
udp_session: UdpSession,
|
udp_session: Arc<UdpSession>,
|
||||||
|
|
||||||
tauri_handle: AppHandle,
|
tauri_handle: Arc<AppHandle>,
|
||||||
|
|
||||||
|
audio_client_manager: Arc<AudioClientManager>,
|
||||||
|
audio_mixer: Arc<AudioMixer>,
|
||||||
|
|
||||||
|
|
||||||
// todo : temporaire, le temps d'avoir un handler
|
// todo : temporaire, le temps d'avoir un handler
|
||||||
@@ -51,23 +56,31 @@ impl PingInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Dispatcher {
|
impl Dispatcher {
|
||||||
pub fn new(event_bus: EventBus, udp_session: UdpSession, tauri_handle: AppHandle) -> Self {
|
pub fn new(event_bus: EventBus,
|
||||||
|
udp_session: UdpSession,
|
||||||
|
audio_client_manager: AudioClientManager,
|
||||||
|
audio_mixer: AudioMixer,
|
||||||
|
tauri_handle: AppHandle
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
event_bus,
|
event_bus: Arc::new(event_bus),
|
||||||
udp_session,
|
udp_session: Arc::new(udp_session),
|
||||||
sequence_counter: Arc::new(AtomicU16::new(0)),
|
sequence_counter: Arc::new(AtomicU16::new(0)),
|
||||||
tauri_handle,
|
tauri_handle: Arc::new(tauri_handle),
|
||||||
can_send_audio: Arc::new(AtomicBool::new(false)),
|
can_send_audio: Arc::new(AtomicBool::new(false)),
|
||||||
ping_tracker: Arc::new(RwLock::new(HashMap::new())),
|
ping_tracker: Arc::new(RwLock::new(HashMap::new())),
|
||||||
|
audio_client_manager: Arc::new(audio_client_manager),
|
||||||
|
audio_mixer: Arc::new(audio_mixer),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(&mut self, mut receiver: mpsc::Receiver<Event>) {
|
pub async fn start(&self, receiver: kanal::AsyncReceiver<Event>) {
|
||||||
let (_udp_in_abort_handle, udp_in_sender) = self.udp_in_handler().await;
|
let (_udp_in_abort_handle, udp_in_sender) = self.udp_in_handler().await;
|
||||||
let udp_session = self.udp_session.clone();
|
let udp_session = self.udp_session.clone();
|
||||||
let sequence_counter = self.sequence_counter.clone();
|
let sequence_counter = self.sequence_counter.clone();
|
||||||
|
let audio_mixer = self.audio_mixer.clone();
|
||||||
|
|
||||||
while let Some(event) = receiver.recv().await {
|
while let Ok(event) = receiver.recv().await {
|
||||||
match event {
|
match event {
|
||||||
Event::AudioIn(sample) => {
|
Event::AudioIn(sample) => {
|
||||||
|
|
||||||
@@ -84,8 +97,11 @@ impl Dispatcher {
|
|||||||
sequence_counter.fetch_add(1, Ordering::Relaxed);
|
sequence_counter.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Event::PlaybackTick(paquet_size) => {
|
||||||
|
audio_mixer.mix_next_frame(paquet_size);
|
||||||
|
}
|
||||||
Event::NetIn(message_event) => {
|
Event::NetIn(message_event) => {
|
||||||
println!("NetIn: {:?}", message_event);
|
// println!("NetIn: {:?}", message_event);
|
||||||
let _ = udp_in_sender.send(message_event).await;
|
let _ = udp_in_sender.send(message_event).await;
|
||||||
}
|
}
|
||||||
Event::NetOut(message_call) => {
|
Event::NetOut(message_call) => {
|
||||||
@@ -117,6 +133,7 @@ impl Dispatcher {
|
|||||||
pub async fn udp_in_handler(&self) -> (AbortHandle, mpsc::Sender<MessageServer>) {
|
pub async fn udp_in_handler(&self) -> (AbortHandle, mpsc::Sender<MessageServer>) {
|
||||||
let (sender, mut consumer) = mpsc::channel::<MessageServer>(1024);
|
let (sender, mut consumer) = mpsc::channel::<MessageServer>(1024);
|
||||||
let ping_tracker = Arc::clone(&self.ping_tracker);
|
let ping_tracker = Arc::clone(&self.ping_tracker);
|
||||||
|
let audio_client_manager = self.audio_client_manager.clone();
|
||||||
|
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
while let Some(message) = consumer.recv().await {
|
while let Some(message) = consumer.recv().await {
|
||||||
@@ -135,7 +152,13 @@ impl Dispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
MessageServer::Audio {user, sequence, data} => {
|
MessageServer::Audio {user, sequence, data} => {
|
||||||
// Audio reçu
|
// println!("Audio received from {}: {} -> {:?}", user, sequence, data);
|
||||||
|
if audio_client_manager.audio_client_exists(user) {
|
||||||
|
audio_client_manager.write_audio_to_client(user, sequence, data);
|
||||||
|
}else{
|
||||||
|
let client = AudioClient::new();
|
||||||
|
audio_client_manager.add_audio_client(user, client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use tauri::Manager;
|
use tauri::{Manager, WindowEvent};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use crate::app::ox_speak_app::OxSpeakApp;
|
use crate::app::ox_speak_app::OxSpeakApp;
|
||||||
@@ -45,6 +45,14 @@ pub async fn run() {
|
|||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
// .invoke_handler(tauri::generate_handler![greet])
|
// .invoke_handler(tauri::generate_handler![greet])
|
||||||
|
.on_window_event(|window, event| match event {
|
||||||
|
WindowEvent::CloseRequested {api, ..} => {
|
||||||
|
println!("Closing window");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
.run(context)
|
.run(context)
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|||||||
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