This commit is contained in:
2025-07-18 01:57:18 +02:00
commit 21164df8cd
60 changed files with 9988 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::thread::JoinHandle;
use cpal::{default_host, BufferSize, Device, SampleRate, Stream, StreamConfig, SupportedStreamConfig};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use crate::core::opus::AudioOpus;
use crate::domain::event::{Event, EventBus};
use crate::utils::ringbuf::RingBuffer;
#[derive(Clone)]
pub struct Microphone {
device: Device,
}
pub struct AudioCapture {
event_bus: EventBus,
microphone: Microphone,
running: Arc<AtomicBool>,
ring_buffer: RingBuffer<i16>,
steam: Option<Stream>,
worker: Option<JoinHandle<()>>,
}
impl Microphone {
pub fn new(device: Device) -> Self {
println!("Initializing microphone with device: {}", device.name().unwrap_or_else(|_| "Unknown".to_string()));
Self {
device
}
}
pub fn default() -> Self {
println!("Creating default microphone");
let host = default_host();
let device = host.default_input_device().unwrap();
Self::new(device)
}
pub fn get_input_config(&self) -> SupportedStreamConfig {
self.device.default_input_config().unwrap()
}
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
}
pub fn build_stream<F>(&self, callback: F) -> Stream
where
F: FnMut(&[i16], &cpal::InputCallbackInfo) + Send + 'static,
{
let config = self.get_stream_config();
self.device.build_input_stream(
&config,
callback,
|err| println!("Error input stream: {err}"),
None
).unwrap()
}
}
impl AudioCapture {
pub fn new(event_bus: EventBus, microphone: Microphone) -> Self {
println!("Creating new AudioCapture instance");
Self {
event_bus,
microphone,
running: Arc::new(AtomicBool::new(false)),
ring_buffer: RingBuffer::new(4096),
steam: None,
worker: None,
}
}
pub fn default(event_bus: EventBus) -> Self {
println!("Creating default AudioCapture");
Self::new(event_bus, Microphone::default())
}
pub async fn start(&mut self) {
println!("Starting audio capture");
self.running.store(true, Ordering::Relaxed);
// stream cpal
println!("Setting up audio stream");
let writer = self.ring_buffer.writer();
let stream_running = self.running.clone();
let stream = self.microphone.build_stream(move |data, _| {
if !stream_running.load(Ordering::Relaxed){
return;
}
writer.push_slice_overwrite(data);
});
stream.play().unwrap();
self.steam = Some(stream);
println!("Audio stream started");
// Audio processing worker
println!("Starting audio processing worker");
self.run_processing_worker();
println!("Audio capture fully initialized");
}
pub async fn stop(&mut self) {
println!("Stopping audio capture");
self.running.store(false, Ordering::Relaxed);
println!("Releasing audio stream");
self.steam = None;
self.ring_buffer.force_wake_up();
// code possiblement bloquant, wrap vers un thread tokio bloquant
if let Some(worker) = self.worker.take() {
println!("Waiting for audio processing worker to finish");
tokio::task::spawn_blocking(move || {
worker.join().unwrap();
}).await.unwrap();
}
println!("Clearing ring buffer");
self.ring_buffer.clear();
println!("Audio capture stopped");
}
fn run_processing_worker(&mut self){
println!("Configuring audio processing worker");
let worker_running = self.running.clone();
let event_bus = self.event_bus.clone();
let input_config = self.microphone.get_input_config();
println!("Audio input config: sample rate: {}, channels: {}", input_config.sample_rate().0, input_config.channels());
let opus = AudioOpus::new(input_config.sample_rate().0, input_config.channels(), "voip");
let mut encoder = opus.create_encoder().unwrap();
let reader = self.ring_buffer.reader();
println!("Spawning audio processing thread");
self.worker = Some(thread::spawn(move || {
println!("Audio processing thread started");
let mut frame = [0i16; 960];
let mut frame_count = 0;
while worker_running.load(Ordering::Relaxed) {
let _ = reader.pop_slice_blocking(&mut frame);
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);
}
let raw_data = frame.to_vec();
event_bus.emit_sync(Event::AudioIn(raw_data));
match encoder.encode(&frame){
Ok(encoded_data) => {
event_bus.emit_sync(Event::AudioEncoded(encoded_data))
}
Err(e) => {
println!("Error encoding: {e}");
}
}
}
println!("Audio processing thread finished after processing {} frames", frame_count);
}));
}
}
impl AudioCapture {
fn audio_processing(){
}
}

View File

@@ -0,0 +1 @@
// aller pick l'audio des clients

View File

@@ -0,0 +1,6 @@
pub mod capture;
pub mod mixer;
pub mod opus;
pub mod playback;
pub mod rms;
pub mod stats;

110
src-tauri/src/core/opus.rs Normal file
View File

@@ -0,0 +1,110 @@
use opus::{Application, Channels, Decoder, Encoder};
#[derive(Clone)]
pub struct AudioOpus{
sample_rate: u32,
channels: u16,
application: Application
}
impl AudioOpus {
pub fn new(sample_rate: u32, channels: u16, application: &str) -> Self {
let application = match application {
"voip" => Application::Voip,
"audio" => Application::Audio,
"lowdelay" => Application::LowDelay,
_ => Application::Voip,
};
Self{sample_rate, channels, application}
}
pub fn create_encoder(&self) -> Result<AudioOpusEncoder, String> {
AudioOpusEncoder::new(self.clone())
}
pub fn create_decoder(&self) -> Result<AudioOpusDecoder, String> {
AudioOpusDecoder::new(self.clone())
}
}
pub struct AudioOpusEncoder{
audio_opus: AudioOpus,
encoder: opus::Encoder,
}
impl AudioOpusEncoder {
fn new(audio_opus: AudioOpus) -> Result<Self, String> {
let opus_channel = match audio_opus.channels {
1 => Channels::Mono,
2 => Channels::Stereo,
_ => Channels::Mono,
};
let mut encoder = Encoder::new(audio_opus.sample_rate, opus_channel, audio_opus.application)
.map_err(|e| format!("Échec de création de l'encodeur: {:?}", e))?;
match audio_opus.application {
Application::Voip => {
// Paramètres optimaux pour VoIP: bonne qualité vocale, CPU modéré
let _ = encoder.set_bitrate(opus::Bitrate::Bits(24000)); // 24kbps est bon pour la voix
let _ = encoder.set_vbr(true); // Variable bitrate économise du CPU
let _ = encoder.set_vbr_constraint(false); // Sans contrainte stricte de débit
// Pas de set_complexity (non supporté par la crate)
},
Application::Audio => {
// Musique: priorité à la qualité
let _ = encoder.set_bitrate(opus::Bitrate::Bits(64000));
let _ = encoder.set_vbr(true);
},
Application::LowDelay => {
// Priorité à la latence et l'efficacité CPU
let _ = encoder.set_bitrate(opus::Bitrate::Bits(18000));
let _ = encoder.set_vbr(true);
},
}
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())
.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();
output.resize(1276, 0);
let len = self.encoder.encode(frames, output.as_mut_slice()).unwrap();
output.truncate(len);
Ok(len)
}
}
pub struct AudioOpusDecoder{
audio_opus: AudioOpus,
decoder: opus::Decoder,
}
impl AudioOpusDecoder {
fn new(audio_opus: AudioOpus) -> Result<Self, String> {
let opus_channel = match audio_opus.channels {
1 => Channels::Mono,
2 => Channels::Stereo,
_ => Channels::Mono,
};
let decoder = Decoder::new(audio_opus.sample_rate, opus_channel)
.map_err(|e| format!("Échec de création du décodeur: {:?}", e))?;
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);
Ok(output)
}
}

View File

@@ -0,0 +1,100 @@
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::JoinHandle;
use cpal::{default_host, BufferSize, Device, SampleRate, Stream, StreamConfig, SupportedStreamConfig};
use cpal::traits::{DeviceTrait, HostTrait};
use crate::domain::event::EventBus;
use crate::utils::real_time_event::RealTimeEvent;
#[derive(Clone)]
pub struct Speaker {
device: Device
}
pub struct AudioPlayback {
event_bus: EventBus,
speaker: Speaker,
running: Arc<AtomicBool>,
stream: Option<Stream>,
worker: Option<JoinHandle<()>>,
next_tick: RealTimeEvent
}
impl Speaker {
pub fn new(device: Device) -> Self {
Speaker {
device
}
}
pub fn default() -> Self {
let host = default_host();
let device = host.default_output_device().unwrap();
Speaker::new(device)
}
pub fn get_input_config(&self) -> SupportedStreamConfig {
self.device.default_output_config().unwrap()
}
pub fn get_stream_config(&self) -> StreamConfig {
let config = self.get_input_config();
let mut stream_config: StreamConfig = config.into();
stream_config.channels = 2;
stream_config.sample_rate = SampleRate(48000);
stream_config.buffer_size = BufferSize::Fixed(1920);
stream_config
}
pub fn build_stream<F>(&self, callback: F) -> Stream
where
F: FnMut(&mut [i16], &cpal::OutputCallbackInfo) + Send + 'static,
{
let config = self.get_stream_config();
self.device.build_output_stream(
&config,
callback,
|err| println!("Error output stream: {err}"),
None
).unwrap()
}
}
impl AudioPlayback {
pub fn new(event_bus: EventBus, speaker: Speaker) -> Self {
Self {
event_bus,
speaker,
running: Arc::new(AtomicBool::new(false)),
stream: None,
worker: None,
next_tick: RealTimeEvent::new(),
}
}
pub fn default(event_bus: EventBus) -> Self {
let speaker = Speaker::default();
AudioPlayback::new(event_bus, speaker)
}
pub async fn start(&mut self) {
}
pub async fn stop(&mut self) {
self.running.store(false, std::sync::atomic::Ordering::SeqCst);
// stream cpal
println!("Setting up audio playback stream...");
let stream_running = self.running.clone();
let stream = self.speaker.build_stream(move |data, _| {
if !stream_running.load(Ordering::Relaxed){
return;
}
// aller récupérer 1920 sur un buffer
// écrire le contenu dans data
});
}
}

View File

View File