use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use ff_decode::{AudioDecoder, SeekMode};
use ff_format::SampleFormat;
use crate::audio::AudioTrackHandle;
use super::state::AudioFadeConfig;
const AUDIO_MAX_BUF: usize = 96_000;
const AUDIO_SAMPLE_RATE: f64 = 48_000.0;
#[allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
fn resample_linear(input: &[f32], speed: f64, phase: &mut f64) -> Vec<f32> {
let capacity = ((input.len() as f64 / speed) + 1.0) as usize;
let mut out = Vec::with_capacity(capacity);
let mut pos = *phase;
let len = input.len();
while pos < len as f64 {
let idx = pos as usize;
let frac = (pos - idx as f64) as f32;
let s = if idx + 1 < len {
input[idx] * (1.0 - frac) + input[idx + 1] * frac
} else {
input[idx]
};
out.push(s);
pos += speed;
}
*phase = pos - len as f64;
out
}
pub(super) fn spawn_audio_track_thread(
path: PathBuf,
start_pts: Duration,
track: AudioTrackHandle,
cancel: Arc<AtomicBool>,
fades: AudioFadeConfig,
) -> JoinHandle<()> {
thread::spawn(move || {
let mut decoder = match AudioDecoder::open(&path)
.output_format(SampleFormat::F32)
.output_sample_rate(48_000)
.output_channels(1) .build()
{
Ok(d) => d,
Err(e) => {
log::warn!("timeline audio thread open failed error={e}");
return;
}
};
if start_pts > Duration::ZERO
&& let Err(e) = decoder.seek(start_pts, SeekMode::Backward)
{
log::warn!("timeline audio seek failed pts={start_pts:?} error={e}");
}
let speed = fades.speed.max(0.01);
let apply_speed = (speed - 1.0).abs() > 1e-6;
let mut speed_phase: f64 = 0.0;
let inv_speed = 1.0 / speed;
let fade_in_secs = fades.fade_in.as_secs_f64() * inv_speed;
let fade_out_secs = fades.fade_out.as_secs_f64() * inv_speed;
let total_secs = fades.clip_dur.as_secs_f64() * inv_speed;
let seek_offset_secs = start_pts.saturating_sub(fades.in_point).as_secs_f64() * inv_speed;
let apply_fades = fade_in_secs > 0.0 || fade_out_secs > 0.0;
let mut samples_pushed: u64 = 0;
loop {
if cancel.load(Ordering::Acquire) {
break;
}
if track.buffered_samples() >= AUDIO_MAX_BUF {
thread::sleep(Duration::from_millis(1));
continue;
}
match decoder.decode_one() {
Ok(Some(frame)) => {
if let Some(raw) = frame.as_f32()
&& !raw.is_empty()
{
let samples: &[f32];
let resampled: Vec<f32>;
if apply_speed {
resampled = resample_linear(raw, speed, &mut speed_phase);
samples = &resampled;
} else {
samples = raw;
}
if apply_fades {
let mut buf: Vec<f32> = samples.to_vec();
for (i, s) in buf.iter_mut().enumerate() {
#[allow(clippy::cast_precision_loss)]
let pos_secs = seek_offset_secs
+ (samples_pushed + i as u64) as f64 / AUDIO_SAMPLE_RATE;
#[allow(clippy::cast_possible_truncation)]
let gain_in = if fade_in_secs > 0.0 && pos_secs < fade_in_secs {
(pos_secs / fade_in_secs) as f32
} else {
1.0_f32
};
#[allow(clippy::cast_possible_truncation)]
let gain_out = if fade_out_secs > 0.0
&& total_secs > 0.0
&& pos_secs >= total_secs - fade_out_secs
{
let elapsed = pos_secs - (total_secs - fade_out_secs);
(1.0 - elapsed / fade_out_secs).clamp(0.0, 1.0) as f32
} else {
1.0_f32
};
*s *= gain_in * gain_out;
}
samples_pushed += buf.len() as u64;
track.push_samples(&buf);
} else {
#[allow(clippy::cast_possible_truncation)]
{
samples_pushed += samples.len() as u64;
}
track.push_samples(samples);
}
}
}
Ok(None) => break,
Err(e) => {
log::warn!("timeline audio decode error error={e}");
break;
}
}
}
})
}