This commit is contained in:
2025-07-21 01:20:30 +02:00
parent d7a7af9eb4
commit db8e876c1c
11 changed files with 318 additions and 269 deletions

View File

@@ -5,3 +5,10 @@ This template should help get you started developing with Tauri + Vue 3 in Vite.
## 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)
## 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.

79
src-tauri/Cargo.lock generated
View File

@@ -2280,6 +2280,15 @@ dependencies = [
"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]]
name = "num-conv"
version = "0.1.0"
@@ -2297,6 +2306,15 @@ dependencies = [
"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]]
name = "num-traits"
version = "0.2.19"
@@ -2651,6 +2669,7 @@ dependencies = [
"moka",
"opus",
"parking_lot",
"rubato",
"serde",
"serde_json",
"strum",
@@ -2967,6 +2986,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro-crate"
version = "1.3.1"
@@ -3145,6 +3173,15 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
[[package]]
name = "realfft"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f821338fddb99d089116342c46e9f1fbf3828dba077674613e734e01d6ea8677"
dependencies = [
"rustfft",
]
[[package]]
name = "redox_syscall"
version = "0.5.13"
@@ -3264,6 +3301,18 @@ dependencies = [
"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]]
name = "rustc-demangle"
version = "0.1.25"
@@ -3279,6 +3328,20 @@ dependencies = [
"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]]
name = "rustix"
version = "1.0.7"
@@ -3688,6 +3751,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strength_reduce"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82"
[[package]]
name = "string_cache"
version = "0.8.9"
@@ -4446,6 +4515,16 @@ dependencies = [
"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]]
name = "tray-icon"
version = "0.21.0"

View File

@@ -35,3 +35,4 @@ moka = {version = "0.12", features = ["future"] }
arc-swap = "1.7"
crossbeam-channel = "0.5"
kanal = "0.1"
rubato = "0.16.2"

View File

@@ -88,8 +88,8 @@ impl OxSpeakApp {
}
// Démarrer la connexion réseau
println!("Connecting to UDP server at 127.0.0.1:5000");
self.udp_session.connect("127.0.0.1:5000").await;
println!("Connecting to UDP server at 82.64.205.121:5000");
self.udp_session.connect("82.64.205.121:5000").await;
// Démarrer l'audio-capture
println!("Starting audio capture");

View File

@@ -4,6 +4,7 @@ use std::thread;
use std::thread::JoinHandle;
use cpal::{default_host, BufferSize, Device, SampleRate, Stream, StreamConfig, SupportedStreamConfig};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use rubato::{FftFixedInOut, Resampler};
use crate::core::opus::AudioOpus;
use crate::domain::event::{Event, EventBus};
use crate::utils::ringbuf::RingBuffer;
@@ -17,7 +18,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<()>>,
}
@@ -25,8 +26,8 @@ pub struct AudioCapture {
impl Microphone {
pub fn new(device: Device) -> Self {
println!("Initializing microphone with device: {}", device.name().unwrap_or_else(|_| "Unknown".to_string()));
Self {
device
Self {
device
}
}
@@ -42,17 +43,25 @@ impl Microphone {
}
pub fn get_stream_config(&self) -> StreamConfig {
let config = self.get_input_config();
let mut stream_config: StreamConfig = config.into();
stream_config.channels = 1;
stream_config.sample_rate = SampleRate(48000);
stream_config.buffer_size = BufferSize::Fixed(960);
stream_config
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),
buffer_size: BufferSize::Default,
}
}
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();
@@ -63,6 +72,13 @@ impl Microphone {
None
).unwrap()
}
// helpers
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 {
@@ -131,33 +147,53 @@ impl AudioCapture {
let worker_running = self.running.clone();
let event_bus = self.event_bus.clone();
let input_config = self.microphone.get_input_config();
println!("Audio input config: sample rate: {}, channels: {}", input_config.sample_rate().0, input_config.channels());
let opus = AudioOpus::new(input_config.sample_rate().0, input_config.channels(), "voip");
println!("Audio input config: sample rate: {}, channels: {}",
input_config.sample_rate().0, input_config.channels());
// création du worker opus
let opus = AudioOpus::new(48_000, 1, "voip");
let mut encoder = opus.create_encoder().unwrap();
// création du worker resampler
let source_rate = input_config.sample_rate().0 as usize;
let mut resampler = if source_rate != 48_000 {
Some(Self::create_resampler(source_rate)) // ✅ Corrigé
} else {
None
};
// création du ringbuffer
let reader = self.ring_buffer.reader();
// Démarrage du worker
println!("Spawning audio processing thread");
let stream_config = self.microphone.get_stream_config(); // ✅ Clone la config
self.worker = Some(thread::spawn(move || {
println!("Audio processing thread started");
let mut frame = [0i16; 960];
let mut frame_count = 0;
let source_rate = stream_config.sample_rate.0 as usize; // ✅ Utilise stream_config
let frame_size = source_rate * 10 / 1000; // 10ms en échantillons
let mut raw_buffer = vec![0.0f32; frame_size];
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){
println!("Audio processing thread stopping");
break;
}
frame_count += 1;
if frame_count % 100 == 0 {
println!("Processed {} audio frames", frame_count);
}
// Resampling : 441→480, 48000→480, 96000→480, etc.
let processed_audio = Self::process_audio_frame(
&stream_config, // ✅ Utilise stream_config
resampler.as_mut(),
&raw_buffer
);
// processed_audio est TOUJOURS 480 f32 (mono/48kHz)
let raw_data = frame.to_vec();
event_bus.emit_sync(Event::AudioIn(raw_data));
// Events
event_bus.emit_sync(Event::AudioIn(processed_audio.clone()));
match encoder.encode(&frame){
match encoder.encode(&processed_audio){
Ok(encoded_data) => {
event_bus.emit_sync(Event::AudioEncoded(encoded_data))
}
@@ -166,13 +202,52 @@ impl AudioCapture {
}
}
}
println!("Audio processing thread finished after processing {} frames", frame_count);
}));
}
}
impl AudioCapture {
fn audio_processing(){
/// Standardise un buffer en mono float32 échantillonné à 48kHz.
fn process_audio_frame(
config: &StreamConfig,
resampler: Option<&mut FftFixedInOut<f32>>,
samples: &[f32],
) -> Vec<f32> {
// 1. Conversion mono d'abord
let mono = Self::sample_to_mono(config.channels as usize, samples);
// 2. Resampling si nécessaire
if let Some(resampler) = resampler {
let input: Vec<&[f32]> = vec![&mono];
match resampler.process(&input, None) {
Ok(mut output) => output.remove(0),
Err(e) => {
println!("Erreur de resampling: {e}");
mono // Fallback
}
}
} else {
mono
}
}
}
fn sample_to_mono(input_channels: usize, samples: &[f32]) -> Vec<f32> {
if input_channels == 1 {
samples.to_vec()
}else{
samples
.chunks_exact(input_channels)
.map(|frame| frame.iter().copied().sum::<f32>() / input_channels as f32)
.collect()
}
}
// ✅ Méthode corrigée avec le bon nom
fn create_resampler(source_rate: usize) -> FftFixedInOut<f32> {
println!("Creating resampler: {}Hz -> 48kHz", source_rate);
FftFixedInOut::<f32>::new(
source_rate,
48_000,
512, // chunk size
1, // mono après conversion
).unwrap()
}
}

View File

@@ -1,20 +1,24 @@
// version pull-based
// 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: Arc<ArcSwap<Vec<i16>>>
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: Arc::new(ArcSwap::from_pointee(Vec::new())),
buffer_writer: Arc::new(buffer_writer),
buffer_reader: Arc::new(buffer_reader)
}
}
pub fn mix_next_frame(&self, size: usize) {
@@ -34,14 +38,18 @@ impl AudioMixer {
Self::mix_frames(&frames, size)
};
self.buffer.store(Arc::new(mixed_frame));
self.buffer_writer.push_slice_overwrite(&mixed_frame);
}
pub fn read(&self, size: usize) -> Vec<i16> {
let mut data = (**self.buffer.load()).clone();
data.resize(size, 0);
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 {

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

View File

@@ -1,229 +0,0 @@
// version non utilisé, c'était un prototype "push-based", jamais testé
use std::sync::{atomic, Arc};
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize};
use arc_swap::ArcSwap;
use bytes::Bytes;
use tokio::sync::{mpsc, Notify};
use crate::utils::ringbuf::{RingBufReader, RingBufWriter, RingBuffer};
pub struct Mixer {
// writer: Arc<RingBufWriter<i16>>,
// reader: Arc<RingBufReader<i16>>,
pre_buffer: Arc<PreBuffer>,
worker_sender: Option<mpsc::UnboundedSender<Vec<i16>>>,
buffer: Arc<ArcSwap<[i16; 1920]>>, // 1920 frames
}
impl Mixer {
pub fn new() -> Self {
let (writer, reader) = RingBuffer::<i16>::new(1024).split();
Self {
// writer: Arc::new(writer),
// reader: Arc::new(reader),
pre_buffer: Arc::new(PreBuffer::new()),
worker_sender: None,
buffer: Arc::new(ArcSwap::from_pointee([0i16; 1920])),
}
}
// Démarrer le worker de pré-traitement
pub async fn start(&mut self){
let (sender, mut receiver) = mpsc::unbounded_channel::<Vec<i16>>();
// let (writer, reader) = RingBuffer::<Bytes>::new(1024).split();
self.worker_sender = Some(sender);
let prebuffer = self.pre_buffer.clone();
// worker de pré-traitement
tokio::spawn(async move {
while let Some(data) = receiver.recv().await {
// data doit exactement faire 960 (mono) ou 1920 (stéréo), 20ms
// si il fait 960, on converti en stéréo
// on écrit dans un buffer de pré-traitement
// si data rempli pas les condition, on ignore
// on vérifie la taille des données
match data.len() {
960 => {
// Mono 20ms @ 48kHz - convertir en stéréo
let stereo_data = Self::mono_to_stereo(data);
// push dans un buffer de pré-traitement
prebuffer.push(stereo_data).await;
},
1920 => {
// push dans un buffer de pré-traitement
prebuffer.push(data).await;
}
_ => {
println!("⚠️ Données audio ignorées - taille incorrecte: {} bytes", data.len());
}
}
}
});
}
// Envoyer des données au pré-traitement
pub async fn write(&self, data: Vec<i16>){
if let Some(sender) = self.worker_sender.as_ref() {
let _ = sender.send(data);
}
}
// S'occupe de générer la trame qui sera lu dans 20ms
pub async fn mix(&self){
// récupérer le buffer de pré-traitement, qui sera tableau de Vec<i16> (le lock ? pour éviter que le worker de pré-traitement continue d'alimenter)
// le mixer (sum de chaque trame ?)
// le mettre dans un buffer, qui sera accessible par AudioPlayback
// écraser le buffer de pré-traitement
// (libérer le lock)
let frames = self.pre_buffer.read_all().await;
let mixed_frame = if frames.is_empty() {
[0i16; 1920] // Silence
} else {
Self::mix_frames(&frames)
};
// ✅ Swap direct - aucune conversion !
self.buffer.store(Arc::new(mixed_frame));
}
// Récupérer la trame présente qui est déjà pré-généré par mix
pub fn read(&self) -> [i16; 1920] {
// vider le buffer
**self.buffer.load()
}
}
impl Mixer {
// Functions helpers
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
}
// Mixer plusieurs trames
fn mix_frames(frames: &[Vec<i16>]) -> [i16; 1920] {
let mut mixed = [0i32; 1920];
for frame in frames {
for (i, &sample) in frame.iter().enumerate() {
if i < 1920 {
mixed[i] += sample as i32;
}
}
}
let mut result = [0i16; 1920];
let count = frames.len() as i32;
for (i, &sample) in mixed.iter().enumerate() {
result[i] = (sample / count).clamp(i16::MIN as i32, i16::MAX as i32) as i16;
}
result
}
}
/// Pre buffer
#[derive(Clone)]
struct PreBuffer {
sender: Arc<kanal::AsyncSender<Vec<i16>>>,
receiver: Arc<kanal::AsyncReceiver<Vec<i16>>>,
is_being_read: Arc<AtomicBool>,
read_done_notify: Arc<Notify>,
}
impl PreBuffer {
fn new() -> Self {
let (sender, reader) = kanal::unbounded_async::<Vec<i16>>();
Self {
sender: Arc::new(sender),
receiver: Arc::new(reader),
is_being_read: Arc::new(AtomicBool::new(false)),
read_done_notify: Arc::new(Notify::new()),
}
}
async fn push(&self, frame: Vec<i16>) {
if self.is_being_read.load(atomic::Ordering::Acquire) {
self.read_done_notify.notified().await;
}
let _ = self.sender.send(frame);
}
async fn read_all(&self) -> Vec<Vec<i16>> {
self.is_being_read.store(true, atomic::Ordering::Release);
let mut frames = Vec::new();
while let Ok(frame) = self.receiver.recv().await {
frames.push(frame);
}
// Libérer et notifier les writers en attente
self.is_being_read.store(false, atomic::Ordering::Release);
self.read_done_notify.notify_waiters();
frames
}
}
// struct PreBuffer {
// // Vec dynamique pour stocker les trames
// frames: Vec<Vec<i16>>,
// // Compteur atomique pour le nombre de trames disponibles
// frame_count: AtomicUsize,
// // Flag atomique pour indiquer si le buffer est en cours de lecture
// is_being_read: AtomicBool,
// read_done_notify: Arc<Notify>,
// }
//
// impl PreBuffer {
// fn new() -> Self {
// Self {
// frames: Vec::new(),
// frame_count: AtomicUsize::new(0),
// is_being_read: AtomicBool::new(false),
// read_done_notify: Arc::new(Notify::new()),
// }
// }
//
// async fn push(&mut self, frame: Vec<i16>) {
// if self.is_being_read.load(atomic::Ordering::Acquire) {
// self.read_done_notify.notified().await;
// }
//
// self.frames.push(frame);
// self.frame_count.fetch_add(1, atomic::Ordering::Release);
// }
//
// fn reset(&mut self) {
// self.frame_count.store(0, atomic::Ordering::Release);
// }
//
// fn len(&self) -> usize {
// self.frame_count.load(atomic::Ordering::Acquire)
// }
//
// fn read_all(&mut self) -> Vec<Vec<i16>> {
// self.is_being_read.store(true, atomic::Ordering::Release);
//
// let frames = std::mem::take(&mut self.frames);
// self.frame_count.store(0, atomic::Ordering::Release);
//
// // Libérer et notifier les writers en attente
// self.is_being_read.store(false, atomic::Ordering::Release);
// self.read_done_notify.notify_waiters();
//
// frames
// }
// }
//

View File

@@ -65,7 +65,8 @@ impl AudioOpusEncoder {
Ok(Self{audio_opus, encoder})
}
pub fn encode(&mut self, frames: &[i16]) -> Result<Vec<u8>, String> {
// old version i16 en input, on garde au cas ou ...
pub fn encode_i16(&mut self, frames: &[i16]) -> Result<Vec<u8>, String> {
let mut output = vec![0u8; 1276]; // 1276 octets (la vraie worst-case recommandée par Opus).
let len = self.encoder.encode(frames, output.as_mut_slice())
.map_err(|e| format!("Erreur encodage: {:?}", e))?;
@@ -73,6 +74,21 @@ impl AudioOpusEncoder {
Ok(output)
}
pub fn encode(&mut self, frames: &[f32]) -> Result<Vec<u8>, String> {
// Conversion f32 -> i16 seulement ici
let frames_i16: Vec<i16> = frames.iter()
.map(|&sample| (sample * i16::MAX as f32)
.clamp(i16::MIN as f32, i16::MAX as f32) as i16)
.collect();
let mut output = vec![0u8; 1276];
let len = self.encoder.encode(&frames_i16, output.as_mut_slice())
.map_err(|e| format!("Erreur encodage: {:?}", e))?;
output.truncate(len);
Ok(output)
}
// 🔄 Approche avec buffer réutilisable (encore plus optimal)
fn encode_reuse(&mut self, frames: &[i16], output: &mut Vec<u8>) -> Result<usize, String> {
output.clear();
@@ -101,10 +117,24 @@ impl AudioOpusDecoder {
Ok(Self{audio_opus, decoder})
}
pub fn decode(&mut self, frames: &[u8]) -> Result<Vec<i16>, String> {
pub fn decode_i16(&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);
Ok(output)
}
pub fn decode(&mut self, frames: &[u8]) -> Result<Vec<f32>, String> {
let mut output_i16 = vec![0i16; 5760];
let len = self.decoder.decode(frames, &mut output_i16, false)
.map_err(|e| format!("Erreur décodage: {:?}", e))?;
// Conversion i16 -> f32
let output_f32: Vec<f32> = output_i16[..len].iter()
.map(|&sample| sample as f32 / i16::MAX as f32)
.collect();
Ok(output_f32)
}
}

View File

@@ -43,8 +43,8 @@ impl Speaker {
let config = self.get_output_config();
let mut stream_config: StreamConfig = config.into();
stream_config.channels = 2;
stream_config.sample_rate = SampleRate(48000);
stream_config.buffer_size = BufferSize::Fixed(960);
stream_config.sample_rate = SampleRate(44100);
// stream_config.buffer_size = BufferSize::Fixed(960);
stream_config
}

View File

@@ -6,7 +6,7 @@ pub enum Event {
AppStarted,
AppStopped,
AudioIn(Vec<i16>),
AudioIn(Vec<f32>),
AudioEncoded(Vec<u8>),
PlaybackTick(usize),