Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/capture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ impl SCStreamOutputTrait for AudioOutputHandler {

if num_buffers == 0 { return; }

// Get first buffer to check channels
let first_buffer = audio_data.get(0).unwrap();
// Get first buffer to check channels; skip malformed empty payloads.
let Some(first_buffer) = audio_data.get(0) else { return; };
let channels_per_buffer = first_buffer.number_channels as usize;

if num_buffers == 1 {
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use pyo3::prelude::*;

#[pyclass]
#[pyclass(from_py_object)]
#[derive(Clone, Debug)]
pub struct AudioProcessingConfig {
#[pyo3(get, set)]
Expand Down
2 changes: 2 additions & 0 deletions src/modular_pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use numpy::ToPyArray;
use std::time::Instant;

/// Modular pipeline that processes audio through a chain of processors
#[allow(dead_code)]
pub struct ModularPipeline {
rx: Receiver<AudioFrame>,
stop_rx: Receiver<()>,
Expand All @@ -20,6 +21,7 @@ pub struct ModularPipeline {
stats: RuntimeStatsHandle,
}

#[allow(dead_code)]
impl ModularPipeline {
fn select_total_pipeline_delay(input_timestamp: u64, output_timestamp: u64, processing_delay: u64) -> u64 {
let timestamp_delay = output_timestamp.saturating_sub(input_timestamp);
Expand Down
14 changes: 10 additions & 4 deletions src/processors/aec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl AecProcessor {

pub fn new(config: AudioProcessingConfig, stats: RuntimeStatsHandle) -> Self {
let apm = if config.enable_aec {
Some(Self::create_apm(&config))
Self::create_apm(&config)
} else {
None
};
Expand Down Expand Up @@ -265,11 +265,17 @@ impl AecProcessor {
apm_config
}

fn create_apm(config: &AudioProcessingConfig) -> Processor {
let apm = Processor::new(48_000).expect("Failed to create WebRTC Processor for AEC");
fn create_apm(config: &AudioProcessingConfig) -> Option<Processor> {
let apm = match Processor::new(48_000) {
Ok(apm) => apm,
Err(err) => {
eprintln!("Warning: failed to create AEC processor: {}", err);
return None;
}
};
let delay_ms = config.aec_stream_delay_ms.max(0);
apm.set_config(Self::build_apm_config(delay_ms));
apm
Some(apm)
}
}

Expand Down
16 changes: 11 additions & 5 deletions src/processors/noise_suppression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct NoiseSuppressionProcessor {
impl NoiseSuppressionProcessor {
pub fn new(config: AudioProcessingConfig) -> Self {
let apm = if config.enable_ns {
Some(Self::create_ns_apm(&config))
Self::create_ns_apm(&config)
} else {
None
};
Expand All @@ -25,8 +25,14 @@ impl NoiseSuppressionProcessor {
}
}

fn create_ns_apm(_config: &AudioProcessingConfig) -> Processor {
let apm = Processor::new(48_000).expect("Failed to create WebRTC Processor for Noise Suppression");
fn create_ns_apm(_config: &AudioProcessingConfig) -> Option<Processor> {
let apm = match Processor::new(48_000) {
Ok(apm) => apm,
Err(err) => {
eprintln!("Warning: failed to create NS processor: {}", err);
return None;
}
};

let mut apm_config = Config::default();

Expand All @@ -44,7 +50,7 @@ impl NoiseSuppressionProcessor {
apm_config.gain_controller = None;

apm.set_config(apm_config);
apm
Some(apm)
}
}

Expand Down Expand Up @@ -88,7 +94,7 @@ impl AudioProcessor for NoiseSuppressionProcessor {
fn reset(&mut self) {
// Reset NS processor state if needed
if self.config.enable_ns {
self.apm = Some(Self::create_ns_apm(&self.config));
self.apm = Self::create_ns_apm(&self.config);
} else {
self.apm = None;
}
Expand Down
43 changes: 36 additions & 7 deletions src/processors/resample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ impl<'a> Adapter<'a, f32> for PlanarBuffer<'a> {

struct StreamState {
resampler: Option<Fft<f32>>,
source_rate: u32,
input_buffer: VecDeque<f32>,
output_queue: VecDeque<f32>,
current_timestamp: u64,
Expand All @@ -36,20 +37,30 @@ struct StreamState {
impl StreamState {
fn new(source_rate: u32, target_rate: u32, target_channels: u16, chunk_size: usize) -> Self {
let resampler = if source_rate != target_rate {
Some(Fft::<f32>::new(
match Fft::<f32>::new(
source_rate as usize,
target_rate as usize,
chunk_size,
1,
target_channels as usize,
FixedSync::Input,
).expect("Failed to create resampler"))
) {
Ok(resampler) => Some(resampler),
Err(err) => {
eprintln!(
"Warning: failed to create resampler {}->{}Hz: {}. Falling back to passthrough.",
source_rate, target_rate, err
);
None
}
}
} else {
None
};

Self {
resampler,
source_rate,
input_buffer: VecDeque::with_capacity(chunk_size * 4),
output_queue: VecDeque::with_capacity(chunk_size * 4),
current_timestamp: 0,
Expand Down Expand Up @@ -158,12 +169,18 @@ impl ResampleProcessor {

if channels == 1 {
for _ in 0..chunk_size {
planar_data[0].push(state.input_buffer.pop_front().unwrap());
let Some(sample) = state.input_buffer.pop_front() else {
return results;
};
planar_data[0].push(sample);
}
} else {
for _ in 0..chunk_size {
for channel_buf in &mut planar_data {
channel_buf.push(state.input_buffer.pop_front().unwrap());
let Some(sample) = state.input_buffer.pop_front() else {
return results;
};
channel_buf.push(sample);
}
}
}
Expand All @@ -178,12 +195,20 @@ impl ResampleProcessor {
let mut samples = Vec::new();
if channels == 1 {
for i in 0..output.frames() {
samples.push(output.read_sample(0, i).unwrap());
if let Some(sample) = output.read_sample(0, i) {
samples.push(sample);
} else {
return results;
}
}
} else {
for i in 0..output.frames() {
for ch in 0..channels {
samples.push(output.read_sample(ch, i).unwrap());
if let Some(sample) = output.read_sample(ch, i) {
samples.push(sample);
} else {
return results;
}
}
}
}
Expand Down Expand Up @@ -214,7 +239,11 @@ impl ResampleProcessor {
results.push(AudioFrame {
source,
samples,
sample_rate: target_rate,
sample_rate: if state.source_rate == target_rate {
target_rate
} else {
state.source_rate
},
channels: target_channels,
timestamp: frame_ts,
});
Expand Down
1 change: 1 addition & 0 deletions src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ impl RuntimeStatsHandle {
}
}

#[allow(dead_code)]
pub fn reset(&self) {
if let Ok(mut stats) = self.inner.lock() {
*stats = RuntimeStats::default();
Expand Down
Loading