Working good quality audio

This commit is contained in:
2026-08-27 01:03:14 +02:00
parent d450e5e66e
commit f28c9c0bdf
+60 -44
View File
@@ -6,25 +6,24 @@ use std::{
Arc, Mutex, Arc, Mutex,
}, },
thread, thread,
time::Duration, time::{Duration, Instant},
}; };
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use ringbuf::{ use ringbuf::{
traits::{Consumer, Producer, Split}, traits::{Consumer, Observer, Producer, Split},
HeapRb, HeapRb,
}; };
use tauri::State; use tauri::State;
use crate::commands::config::ConfigState; use crate::commands::config::ConfigState;
// Standardize to 48kHz (Native for WebAudio and low-latency VoIP)
const SAMPLE_RATE: u32 = 48000; const SAMPLE_RATE: u32 = 48000;
const PACKET_SAMPLES: usize = 960; // 20ms @ 48kHz const PACKET_SAMPLES: usize = 960; // 20ms @ 48kHz
const HEADER_SIZE: usize = 4; const HEADER_SIZE: usize = 4;
const MAX_PACKET_SIZE: usize = 4096; const MAX_PACKET_SIZE: usize = 4096;
const INITIAL_PACKET_CUSHION: usize = 5; const INITIAL_PACKET_CUSHION: usize = 3; // ~60ms cushion
// ============================================================================ // ============================================================================
// STATE // STATE
@@ -108,7 +107,7 @@ pub fn connect_to_vc(
.connect(&hostname) .connect(&hostname)
.map_err(|e| format!("Failed to connect UDP socket: {e}"))?; .map_err(|e| format!("Failed to connect UDP socket: {e}"))?;
socket socket
.set_read_timeout(Some(Duration::from_millis(10))) .set_read_timeout(Some(Duration::from_millis(5)))
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
socket socket
.send(&pin.to_be_bytes()) .send(&pin.to_be_bytes())
@@ -151,8 +150,8 @@ pub fn connect_to_vc(
let shutdown = Arc::new(AtomicBool::new(false)); let shutdown = Arc::new(AtomicBool::new(false));
// Lock-Free Ring Buffer setup (19.2k samples ~ 400ms buffer capacity at 48kHz) // Lock-Free Ring Buffer setup (19.2k samples ~ 400ms buffer capacity at 48kHz)
let rb = HeapRb::<f32>::new(19200); let rb_out = HeapRb::<f32>::new(19200);
let (mut producer, consumer) = rb.split(); let (mut producer_out, consumer_out) = rb_out.split();
let output_config = cpal::StreamConfig { let output_config = cpal::StreamConfig {
channels: 2, channels: 2,
@@ -160,35 +159,49 @@ pub fn connect_to_vc(
buffer_size: cpal::BufferSize::Default, buffer_size: cpal::BufferSize::Default,
}; };
let output_stream = build_output_stream(&output_device, &output_config, consumer)?; let output_stream = build_output_stream(&output_device, output_config, consumer_out)?;
output_stream output_stream
.play() .play()
.map_err(|e| format!("Failed to start output: {e}"))?; .map_err(|e| format!("Failed to start output: {e}"))?;
let output_stream = Arc::new(Mutex::new(output_stream)); let output_stream = Arc::new(Mutex::new(output_stream));
// Configure Input Stream // Configure Input Stream with dedicated thread ring-buffer
let input_config = cpal::StreamConfig { let input_config = cpal::StreamConfig {
channels: 1, channels: 1,
sample_rate: SAMPLE_RATE, sample_rate: SAMPLE_RATE,
buffer_size: cpal::BufferSize::Default, buffer_size: cpal::BufferSize::Default,
}; };
let input_socket = socket.clone(); let rb_in = HeapRb::<f32>::new(19200);
let mut packet_buffer = Vec::with_capacity(PACKET_SAMPLES * 2); let (mut producer_in, mut consumer_in) = rb_in.split();
let mut sequence = 0u32;
let mut net_packet = vec![0u8; HEADER_SIZE + PACKET_SAMPLES * 2];
let input_stream = input_device let input_stream = input_device
.build_input_stream( .build_input_stream(
input_config, input_config,
move |data: &[f32], _| { move |data: &[f32], _| {
packet_buffer.extend_from_slice(data); let _ = producer_in.push_slice(data);
},
|err| eprintln!("[vc] input error: {err}"),
None,
)
.map_err(|e| e.to_string())?;
while packet_buffer.len() >= PACKET_SAMPLES { // UDP Sender Thread - Pumps exact 20ms frames smoothly
let samples: Vec<f32> = packet_buffer.drain(..PACKET_SAMPLES).collect(); {
let input_socket = socket.clone();
let shutdown = shutdown.clone();
thread::spawn(move || {
let mut sequence = 0u32;
let mut net_packet = vec![0u8; HEADER_SIZE + PACKET_SAMPLES * 2];
let mut frame_buf = vec![0.0f32; PACKET_SAMPLES];
while !shutdown.load(Ordering::Relaxed) {
if consumer_in.occupied_len() >= PACKET_SAMPLES {
let _ = consumer_in.pop_slice(&mut frame_buf);
net_packet[0..4].copy_from_slice(&sequence.to_be_bytes()); net_packet[0..4].copy_from_slice(&sequence.to_be_bytes());
for (i, sample) in samples.iter().enumerate() { for (i, sample) in frame_buf.iter().enumerate() {
let pcm = (sample.clamp(-1.0, 1.0) * 32767.0) as i16; let pcm = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
let offset = HEADER_SIZE + i * 2; let offset = HEADER_SIZE + i * 2;
net_packet[offset..offset + 2].copy_from_slice(&pcm.to_be_bytes()); net_packet[offset..offset + 2].copy_from_slice(&pcm.to_be_bytes());
@@ -196,14 +209,14 @@ pub fn connect_to_vc(
let _ = input_socket.send(&net_packet); let _ = input_socket.send(&net_packet);
sequence = sequence.wrapping_add(1); sequence = sequence.wrapping_add(1);
} else {
thread::sleep(Duration::from_millis(2));
}
}
});
} }
},
|err| eprintln!("[vc] input error: {err}"),
None,
)
.map_err(|e| e.to_string())?;
// UDP Receiver & Jitter Buffer Thread // UDP Receiver Thread - Paced Jitter Buffer
{ {
let socket = socket.clone(); let socket = socket.clone();
let shutdown = shutdown.clone(); let shutdown = shutdown.clone();
@@ -214,9 +227,10 @@ pub fn connect_to_vc(
let mut is_prebuffering = true; let mut is_prebuffering = true;
let mut last_good_frame = vec![0.0f32; PACKET_SAMPLES]; let mut last_good_frame = vec![0.0f32; PACKET_SAMPLES];
let mut udp_buffer = [0u8; MAX_PACKET_SIZE]; let mut udp_buffer = [0u8; MAX_PACKET_SIZE];
let mut next_frame_time = Instant::now();
while !shutdown.load(Ordering::Relaxed) { while !shutdown.load(Ordering::Relaxed) {
// Read incoming packets // Non-blocking UDP receive
if let Ok(len) = socket.recv(&mut udp_buffer) { if let Ok(len) = socket.recv(&mut udp_buffer) {
if len > HEADER_SIZE { if len > HEADER_SIZE {
let seq = u32::from_be_bytes(udp_buffer[0..4].try_into().unwrap()); let seq = u32::from_be_bytes(udp_buffer[0..4].try_into().unwrap());
@@ -234,46 +248,48 @@ pub fn connect_to_vc(
if packets.len() >= INITIAL_PACKET_CUSHION { if packets.len() >= INITIAL_PACKET_CUSHION {
expected = packets.keys().next().copied(); expected = packets.keys().next().copied();
is_prebuffering = false; is_prebuffering = false;
next_frame_time = Instant::now();
} else { } else {
thread::sleep(Duration::from_millis(1));
continue; continue;
} }
} }
// Drain ordered frames into ring buffer // Paced playback frame-by-frame (20ms target interval)
while let Some(seq) = expected { if Instant::now() >= next_frame_time {
if let Some(seq) = expected {
if let Some(samples) = packets.remove(&seq) { if let Some(samples) = packets.remove(&seq) {
// Dynamically match any incoming frame length without crashing
if samples.len() == PACKET_SAMPLES { if samples.len() == PACKET_SAMPLES {
last_good_frame.copy_from_slice(&samples); last_good_frame.copy_from_slice(&samples);
} else { } else {
// Resize/truncate safely if source length differs
last_good_frame.clear(); last_good_frame.clear();
last_good_frame.extend(samples.iter().take(PACKET_SAMPLES).copied()); last_good_frame
.extend(samples.iter().take(PACKET_SAMPLES).copied());
if last_good_frame.len() < PACKET_SAMPLES { if last_good_frame.len() < PACKET_SAMPLES {
last_good_frame.resize(PACKET_SAMPLES, 0.0); last_good_frame.resize(PACKET_SAMPLES, 0.0);
} }
} }
let _ = producer.push_slice(&samples); let _ = producer_out.push_slice(&last_good_frame);
expected = Some(seq.wrapping_add(1)); expected = Some(seq.wrapping_add(1));
} else if packets.keys().any(|&x| x > seq) { } else if packets.keys().any(|&x| x > seq) {
// Packet Loss Concealment (PLC): Decay previous packet amplitude instead of absolute zeros // PLC (Packet Loss Concealment)
let mut plc_frame = last_good_frame.clone(); for sample in last_good_frame.iter_mut() {
for sample in plc_frame.iter_mut() { *sample *= 0.65;
*sample *= 0.65; // Quick fade out for missing frame
} }
last_good_frame.copy_from_slice(&plc_frame); let _ = producer_out.push_slice(&last_good_frame);
let _ = producer.push_slice(&plc_frame);
expected = Some(seq.wrapping_add(1)); expected = Some(seq.wrapping_add(1));
} else { } else {
// Out of continuous frames, wait for network // Re-buffer if packet stream dropped
if packets.is_empty() { if packets.is_empty() {
is_prebuffering = true; is_prebuffering = true;
} }
break;
} }
} }
next_frame_time += Duration::from_millis(20);
}
thread::sleep(Duration::from_millis(1));
} }
}); });
} }
@@ -294,12 +310,12 @@ pub fn connect_to_vc(
} }
// ============================================================================ // ============================================================================
// AUDIO CALLBACK (Pops Avoidance & Degraded Fill) // AUDIO CALLBACK
// ============================================================================ // ============================================================================
fn build_output_stream<C>( fn build_output_stream<C>(
device: &cpal::Device, device: &cpal::Device,
config: &cpal::StreamConfig, config: cpal::StreamConfig,
mut consumer: C, mut consumer: C,
) -> Result<cpal::Stream, String> ) -> Result<cpal::Stream, String>
where where
@@ -310,7 +326,7 @@ where
device device
.build_output_stream( .build_output_stream(
*config, config,
move |data: &mut [f32], _| { move |data: &mut [f32], _| {
let mut idx = 0; let mut idx = 0;
while idx < data.len() { while idx < data.len() {
@@ -321,9 +337,9 @@ where
} }
idx += channels; idx += channels;
} else { } else {
// Smooth de-zippering / anti-pop decay on buffer underruns // Smooth fade out to avoid clicks on underruns
while idx < data.len() { while idx < data.len() {
last_sample *= 0.92; // Rapid smooth fade to silence last_sample *= 0.92;
for ch in 0..channels { for ch in 0..channels {
data[idx + ch] = last_sample; data[idx + ch] = last_sample;
} }