use crate::error::GigasttError;
use crate::inference::{ENCODER_SUBSAMPLING, HOP_LENGTH};
#[cfg(feature = "file-decode")]
use anyhow::{Context, Result};
#[cfg(feature = "file-decode")]
use bytes::Bytes;
#[cfg(feature = "file-decode")]
use symphonia::core::codecs::audio::well_known::CODEC_ID_OPUS;
#[cfg(feature = "file-decode")]
use symphonia::core::codecs::audio::{AudioDecoder, AudioDecoderOptions};
#[cfg(feature = "file-decode")]
use symphonia::core::formats::probe::Hint;
#[cfg(feature = "file-decode")]
use symphonia::core::formats::{FormatOptions, FormatReader, TrackType};
#[cfg(feature = "file-decode")]
use symphonia::core::io::MediaSourceStream;
#[cfg(feature = "file-decode")]
use symphonia::core::meta::MetadataOptions;
#[cfg(feature = "file-decode")]
use super::decode::BytesMediaSource;
#[cfg(feature = "file-decode")]
use super::opus::{OPUS_DECODE_RATE, OpusStream, next_demux_packet};
#[cfg(feature = "file-decode")]
use super::resample::{RESAMPLE_STAGING_FRAMES, ResampleTo16k, SampleRate};
#[cfg(feature = "file-decode")]
use super::telephony::{sniffs_as_g722_wav, try_decode_g722_wav};
#[cfg(feature = "file-decode")]
use super::{MAX_SAMPLE_RATE, audio_too_long_err, decode_error, resolve_budget};
const FRAME_SAMPLES: usize = HOP_LENGTH * ENCODER_SUBSAMPLING;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct WindowSpec {
single_pass_max: usize,
window: usize,
stride: usize,
}
impl WindowSpec {
pub(crate) fn new(single_pass_max: usize, window: usize, overlap: usize) -> Self {
let stride =
(window.saturating_sub(overlap) / FRAME_SAMPLES * FRAME_SAMPLES).max(FRAME_SAMPLES);
Self {
single_pass_max,
window,
stride,
}
}
pub(crate) fn window(&self) -> usize {
self.window
}
pub(crate) fn stride(&self) -> usize {
self.stride
}
pub(crate) fn single_pass_max(&self) -> usize {
self.single_pass_max
}
pub(crate) fn overlap(&self) -> usize {
self.window.saturating_sub(self.stride)
}
pub(crate) fn is_single_pass(&self, total: usize) -> bool {
total <= self.single_pass_max
}
#[cfg(feature = "file-decode")]
pub(crate) fn flat() -> Self {
Self::new(usize::MAX, usize::MAX, 0)
}
}
#[cfg(feature = "file-decode")]
pub(crate) struct WindowCursor {
spec: WindowSpec,
next_start: usize,
first: bool,
done: bool,
}
#[cfg(feature = "file-decode")]
impl WindowCursor {
pub(crate) fn new(spec: WindowSpec) -> Self {
Self {
spec,
next_start: 0,
first: true,
done: false,
}
}
pub(crate) fn is_done(&self) -> bool {
self.done
}
pub(crate) fn next_start(&self) -> usize {
self.next_start
}
pub(crate) fn spec(&self) -> WindowSpec {
self.spec
}
pub(crate) fn fill_target(&self) -> usize {
if self.first {
self.spec
.single_pass_max()
.saturating_add(1)
.max(self.spec.window().saturating_add(1))
} else {
self.next_start + self.spec.window() + 1
}
}
pub(crate) fn take(&mut self, avail_end: usize, eof: bool) -> Option<(usize, usize)> {
if self.done {
return None;
}
let start = self.next_start;
if self.first {
self.first = false;
if eof && avail_end <= self.spec.single_pass_max() {
self.done = true;
return Some((start, avail_end));
}
}
if start >= avail_end {
self.done = true;
return None;
}
let end = (start + self.spec.window()).min(avail_end);
if eof && end == avail_end {
self.done = true;
} else {
self.next_start = start + self.spec.stride();
}
Some((start, end))
}
}
#[cfg(feature = "file-decode")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ChannelSelect {
Mono,
One(usize),
}
pub(crate) struct PcmWindow<'a> {
pub(crate) start_sample: usize,
pub(crate) samples: &'a [f32],
}
pub(crate) trait PcmWindows {
fn spec(&self) -> WindowSpec;
fn next_window(&mut self) -> Result<Option<PcmWindow<'_>>, GigasttError>;
}
pub(crate) struct SliceWindows<'a> {
samples: &'a [f32],
spec: WindowSpec,
next_start: usize,
done: bool,
}
impl<'a> SliceWindows<'a> {
pub(crate) fn new(samples: &'a [f32], spec: WindowSpec) -> Self {
Self {
samples,
spec,
next_start: 0,
done: false,
}
}
}
impl PcmWindows for SliceWindows<'_> {
fn spec(&self) -> WindowSpec {
self.spec
}
fn next_window(&mut self) -> Result<Option<PcmWindow<'_>>, GigasttError> {
let total = self.samples.len();
if self.done || self.next_start >= total {
return Ok(None);
}
let start = self.next_start;
let end = (start + self.spec.window()).min(total);
if end == total {
self.done = true;
} else {
self.next_start = start + self.spec.stride();
}
Ok(Some(PcmWindow {
start_sample: start,
samples: &self.samples[start..end],
}))
}
}
#[cfg(feature = "file-decode")]
enum Source {
Streaming {
format: Box<dyn FormatReader>,
decoder: Box<dyn AudioDecoder>,
track_id: u32,
sample_rate: u32,
source_frames: usize,
max_samples: usize,
limit_secs: f64,
resampler: Box<ResampleTo16k>,
interleaved: Vec<f32>,
},
Opus {
format: Box<dyn FormatReader>,
track_id: u32,
stream: Box<OpusStream>,
decoded_48k: usize,
max_samples: usize,
limit_secs: f64,
resampler: Box<ResampleTo16k>,
pending: Vec<f32>,
},
Eager,
}
#[cfg(feature = "file-decode")]
pub(crate) struct FileWindows {
src: Source,
eof: bool,
finished: bool,
buf: Vec<f32>,
buf_start_abs: usize,
decoded_16k_total: usize,
spec: WindowSpec,
channel: ChannelSelect,
cursor: WindowCursor,
}
#[cfg(feature = "file-decode")]
impl FileWindows {
pub(crate) fn open(path: &str, spec: WindowSpec, max_audio_secs: Option<f64>) -> Result<Self> {
if sniffs_as_g722_wav(path)? {
let bytes = std::fs::read(path)
.with_context(|| format!("Failed to read audio file: {path}"))?;
if let Some(result) = try_decode_g722_wav(&bytes, max_audio_secs) {
return Ok(Self::eager(result?, spec));
}
}
let file = std::fs::File::open(path)
.with_context(|| format!("Failed to open audio file: {path}"))?;
let mss = MediaSourceStream::new(Box::new(file), Default::default());
let mut hint = Hint::new();
if let Some(ext) = std::path::Path::new(path)
.extension()
.and_then(|e| e.to_str())
{
hint.with_extension(ext);
}
Self::from_mss(mss, hint, spec, max_audio_secs, ChannelSelect::Mono)
}
pub(crate) fn from_bytes(
data: Bytes,
spec: WindowSpec,
max_audio_secs: Option<f64>,
) -> Result<Self> {
if let Some(result) = try_decode_g722_wav(&data, max_audio_secs) {
return Ok(Self::eager(result?, spec));
}
let source = BytesMediaSource::new(data);
let mss = MediaSourceStream::new(Box::new(source), Default::default());
Self::from_mss(mss, Hint::new(), spec, max_audio_secs, ChannelSelect::Mono)
}
fn from_mss(
mss: MediaSourceStream<'static>,
hint: Hint,
spec: WindowSpec,
max_audio_secs: Option<f64>,
channel: ChannelSelect,
) -> Result<Self> {
let format = symphonia::default::get_probe()
.probe(
&hint,
mss,
FormatOptions::default(),
MetadataOptions::default(),
)
.context("Unsupported audio format")?;
let (track_id, sample_rate, channels, decoder_opt) = {
let track = format
.default_track(TrackType::Audio)
.context("No audio track found")?;
let track_id = track.id;
let audio_params = track
.codec_params
.as_ref()
.and_then(|p| p.audio())
.context("No audio codec parameters")?;
let sample_rate = audio_params.sample_rate.context("Unknown sample rate")?;
if sample_rate == 0 || sample_rate > MAX_SAMPLE_RATE {
anyhow::bail!("Unsupported sample rate: {sample_rate}Hz");
}
let channels = audio_params
.channels
.as_ref()
.map(|c| c.count())
.unwrap_or(1);
let decoder_opt = if audio_params.codec == CODEC_ID_OPUS {
None
} else {
Some(
symphonia::default::get_codecs()
.make_audio_decoder(audio_params, &AudioDecoderOptions::default())
.context("Unsupported audio codec")?,
)
};
(track_id, sample_rate, channels, decoder_opt)
};
let (max_samples, limit_secs) = resolve_budget(max_audio_secs, sample_rate);
match decoder_opt {
None => {
let (max_samples, limit_secs) = resolve_budget(max_audio_secs, OPUS_DECODE_RATE);
tracing::info!("Audio (opus): {sample_rate}Hz, {channels}ch (streaming windows)");
Ok(Self {
src: Source::Opus {
format,
track_id,
stream: Box::new(OpusStream::new(channels, channel)?),
decoded_48k: 0,
max_samples,
limit_secs,
resampler: Box::new(ResampleTo16k::new(SampleRate(sample_rate), None)),
pending: Vec::with_capacity(RESAMPLE_STAGING_FRAMES),
},
eof: false,
finished: false,
buf: Vec::new(),
buf_start_abs: 0,
decoded_16k_total: 0,
spec,
channel,
cursor: WindowCursor::new(spec),
})
}
Some(decoder) => {
tracing::info!("Audio: {sample_rate}Hz, {channels}ch (streaming windows)");
Ok(Self {
src: Source::Streaming {
format,
decoder,
track_id,
sample_rate,
source_frames: 0,
max_samples,
limit_secs,
resampler: Box::new(ResampleTo16k::new(SampleRate(sample_rate), None)),
interleaved: Vec::new(),
},
eof: false,
finished: false,
buf: Vec::new(),
buf_start_abs: 0,
decoded_16k_total: 0,
spec,
channel,
cursor: WindowCursor::new(spec),
})
}
}
}
pub(crate) fn from_bytes_channel(
data: Bytes,
spec: WindowSpec,
max_audio_secs: Option<f64>,
channel: usize,
) -> Result<Self> {
if let Some(result) = try_decode_g722_wav(&data, max_audio_secs) {
return Ok(Self::eager(result?, spec));
}
let source = BytesMediaSource::new(data);
let mss = MediaSourceStream::new(Box::new(source), Default::default());
Self::from_mss(
mss,
Hint::new(),
spec,
max_audio_secs,
ChannelSelect::One(channel),
)
}
fn eager(buf: Vec<f32>, spec: WindowSpec) -> Self {
let total = buf.len();
Self {
src: Source::Eager,
eof: true,
finished: true,
buf,
buf_start_abs: 0,
decoded_16k_total: total,
spec,
channel: ChannelSelect::Mono,
cursor: WindowCursor::new(spec),
}
}
pub(crate) fn total_16k_samples(&self) -> usize {
self.decoded_16k_total
}
pub(crate) fn drain_to_vec(mut self) -> Result<Vec<f32>> {
self.fill_to(usize::MAX)?;
Ok(std::mem::take(&mut self.buf))
}
pub(crate) fn decode_file(path: &str, max_audio_secs: Option<f64>) -> Result<Vec<f32>> {
Self::open(path, WindowSpec::flat(), max_audio_secs)?.drain_to_vec()
}
pub(crate) fn decode_bytes(data: Bytes, max_audio_secs: Option<f64>) -> Result<Vec<f32>> {
Self::from_bytes(data, WindowSpec::flat(), max_audio_secs)?.drain_to_vec()
}
fn fill_to(&mut self, target: usize) -> Result<()> {
match self.src {
Source::Streaming { .. } => self.fill_streaming(target),
Source::Opus { .. } => self.fill_opus(target),
Source::Eager => Ok(()),
}
}
fn fill_streaming(&mut self, target: usize) -> Result<()> {
let channel = self.channel;
let Source::Streaming {
format,
decoder,
track_id,
sample_rate,
source_frames,
max_samples,
limit_secs,
resampler,
interleaved,
} = &mut self.src
else {
return Ok(()); };
while !self.eof && self.decoded_16k_total < target {
let have_pcm = *source_frames > 0;
let packet = match next_demux_packet(&mut **format, have_pcm)? {
Some(p) => p,
None => {
self.eof = true;
break;
}
};
if packet.track_id != *track_id {
continue;
}
let decoded = decoder.decode(&packet).context("Decode error")?;
let num_frames = decoded.frames();
let ch = decoded.spec().channels().count();
if ch > 1 {
interleaved.clear();
decoded.copy_to_vec_interleaved(interleaved);
let stage = resampler.stage();
match channel {
ChannelSelect::Mono => {
for frame in 0..num_frames {
let mut sum = 0.0_f32;
for c in 0..ch {
sum += interleaved[frame * ch + c];
}
stage.push(sum / ch as f32);
}
}
ChannelSelect::One(k) if k < ch => {
for frame in 0..num_frames {
stage.push(interleaved[frame * ch + k]);
}
}
ChannelSelect::One(_) => {}
}
} else if matches!(channel, ChannelSelect::Mono | ChannelSelect::One(0)) {
let stage = resampler.stage();
let offset = stage.len();
stage.resize(offset + num_frames, 0.0);
decoded.copy_to_slice_interleaved(&mut stage[offset..]);
}
*source_frames += num_frames;
if *source_frames > *max_samples {
return Err(audio_too_long_err(
*source_frames,
*sample_rate,
*limit_secs,
));
}
resampler.flush_full()?;
let before = self.buf.len();
resampler.drain_ready_into(&mut self.buf);
self.decoded_16k_total += self.buf.len() - before;
}
if self.eof && !self.finished {
let before = self.buf.len();
resampler.finish_into(&mut self.buf)?;
self.decoded_16k_total += self.buf.len() - before;
self.finished = true;
}
Ok(())
}
fn fill_opus(&mut self, target: usize) -> Result<()> {
let Source::Opus {
format,
track_id,
stream,
decoded_48k,
max_samples,
limit_secs,
resampler,
pending,
} = &mut self.src
else {
return Ok(());
};
while !self.eof && self.decoded_16k_total < target {
let have_pcm = *decoded_48k > 0;
let packet = match next_demux_packet(&mut **format, have_pcm)? {
Some(p) => p,
None => {
self.eof = true;
break;
}
};
if packet.track_id != *track_id {
continue;
}
*decoded_48k += stream.decode_packet(&packet.data, pending)?;
if *decoded_48k > *max_samples {
return Err(audio_too_long_err(
*decoded_48k,
OPUS_DECODE_RATE,
*limit_secs,
));
}
while pending.len() >= RESAMPLE_STAGING_FRAMES {
resampler
.stage()
.extend_from_slice(&pending[..RESAMPLE_STAGING_FRAMES]);
pending.drain(..RESAMPLE_STAGING_FRAMES);
resampler.flush_full()?;
}
let before = self.buf.len();
resampler.drain_ready_into(&mut self.buf);
self.decoded_16k_total += self.buf.len() - before;
}
if self.eof && !self.finished {
resampler.stage().extend_from_slice(pending);
pending.clear();
let before = self.buf.len();
resampler.finish_into(&mut self.buf)?;
self.decoded_16k_total += self.buf.len() - before;
self.finished = true;
}
Ok(())
}
}
#[cfg(feature = "file-decode")]
impl PcmWindows for FileWindows {
fn spec(&self) -> WindowSpec {
self.spec
}
fn next_window(&mut self) -> Result<Option<PcmWindow<'_>>, GigasttError> {
if self.cursor.is_done() {
return Ok(None);
}
let drop = self
.cursor
.next_start()
.saturating_sub(self.buf_start_abs)
.min(self.buf.len());
if drop > 0 {
self.buf.drain(0..drop);
self.buf_start_abs += drop;
}
self.fill_to(self.cursor.fill_target())
.map_err(decode_error)?;
let Some((start, end)) = self.cursor.take(self.decoded_16k_total, self.eof) else {
return Ok(None);
};
let s = start - self.buf_start_abs;
let e = end - self.buf_start_abs;
Ok(Some(PcmWindow {
start_sample: start,
samples: &self.buf[s..e],
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn legacy_windows(total: usize, window: usize, stride: usize) -> Vec<(usize, usize)> {
let mut out = Vec::new();
let mut start = 0usize;
while start < total {
let end = (start + window).min(total);
out.push((start, end - start));
if end == total {
break;
}
start += stride;
}
out
}
fn observed(total: usize, spec: WindowSpec) -> Vec<(usize, usize)> {
let samples = vec![0.0f32; total];
let mut src = SliceWindows::new(&samples, spec);
let mut out = Vec::new();
while let Some(w) = src.next_window().expect("slice source never fails") {
out.push((w.start_sample, w.samples.len()));
}
out
}
fn ort_spec() -> WindowSpec {
WindowSpec::new(16000 * 30, 16000 * 24, 16000 * 2)
}
#[test]
fn test_window_spec_stride_is_frame_aligned() {
let spec = ort_spec();
assert_eq!(spec.window(), 384_000);
assert_eq!(spec.stride(), 352_000);
assert_eq!(spec.stride() % FRAME_SAMPLES, 0);
assert_eq!(spec.overlap(), 32_000);
let ane = WindowSpec::new(16000 * 30, 16000 * 30, 16000 * 2);
assert_eq!(ane.stride(), 448_000);
assert_eq!(ane.stride() % FRAME_SAMPLES, 0);
assert_eq!(ane.overlap(), 32_000);
}
#[test]
fn test_window_spec_single_pass_boundary() {
let spec = ort_spec();
assert!(spec.is_single_pass(0));
assert!(spec.is_single_pass(479_999));
assert!(spec.is_single_pass(480_000)); assert!(!spec.is_single_pass(480_001));
}
#[test]
fn test_window_spec_degenerate_overlap_still_advances() {
let spec = WindowSpec::new(0, 1000, 4000);
assert_eq!(spec.stride(), FRAME_SAMPLES);
assert_eq!(observed(5000, spec).len(), 8);
}
#[test]
fn test_slice_windows_matches_legacy_loop_swept() {
let spec = ort_spec();
let (window, stride) = (spec.window(), spec.stride());
let mut lengths: Vec<usize> = Vec::new();
let mut n = 0usize;
while n <= 3 * window {
lengths.push(n);
n += 4_001; }
for anchor in [
0,
1,
FRAME_SAMPLES,
window,
stride,
stride + window,
2 * stride,
2 * stride + window,
480_000, ] {
for d in [-1isize, 0, 1] {
let v = anchor as isize + d;
if v >= 0 {
lengths.push(v as usize);
}
}
}
lengths.extend(704_000..=704_320); lengths.sort_unstable();
lengths.dedup();
for total in lengths {
assert_eq!(
observed(total, spec),
legacy_windows(total, window, stride),
"window sequence diverged at total={total}"
);
}
}
#[test]
fn test_slice_windows_empty_yields_nothing() {
assert!(observed(0, ort_spec()).is_empty());
}
#[test]
fn test_slice_windows_stop_exactly_at_the_end() {
let spec = ort_spec();
let total = 1_440_000; let seq = observed(total, spec);
assert!(!seq.is_empty());
assert_eq!(
seq.iter()
.filter(|(start, len)| start + len == total)
.count(),
1
);
let (start, len) = seq[seq.len() - 1];
assert_eq!(start + len, total);
for (start, _) in &seq {
assert_eq!(start % FRAME_SAMPLES, 0);
}
}
}
#[cfg(all(test, feature = "file-decode"))]
mod file_windows_tests {
use super::*;
use crate::inference::audio::encode_wav_pcm16;
use bytes::Bytes;
fn ort_spec() -> WindowSpec {
WindowSpec::new(16000 * 30, 16000 * 24, 16000 * 2)
}
fn signal(n: usize, seed: f32) -> Vec<f32> {
(0..n)
.map(|i| {
let t = i as f32;
0.4 * ((t * 0.017 + seed).sin() + 0.5 * (t * 0.0031 + seed).sin())
})
.collect()
}
fn stereo_wav_pcm16(left: &[f32], right: &[f32], rate: u32) -> Vec<u8> {
let frames = left.len().min(right.len());
let data_bytes = (frames * 2 * 2) as u32;
let byte_rate = rate * 2 * 2;
let mut w = Vec::with_capacity(44 + data_bytes as usize);
w.extend_from_slice(b"RIFF");
w.extend_from_slice(&(36 + data_bytes).to_le_bytes());
w.extend_from_slice(b"WAVE");
w.extend_from_slice(b"fmt ");
w.extend_from_slice(&16u32.to_le_bytes());
w.extend_from_slice(&1u16.to_le_bytes()); w.extend_from_slice(&2u16.to_le_bytes()); w.extend_from_slice(&rate.to_le_bytes());
w.extend_from_slice(&byte_rate.to_le_bytes());
w.extend_from_slice(&4u16.to_le_bytes()); w.extend_from_slice(&16u16.to_le_bytes()); w.extend_from_slice(b"data");
w.extend_from_slice(&data_bytes.to_le_bytes());
let q = |s: f32| (s.clamp(-1.0, 1.0) * i16::MAX as f32) as i16;
for i in 0..frames {
w.extend_from_slice(&q(left[i]).to_le_bytes());
w.extend_from_slice(&q(right[i]).to_le_bytes());
}
w
}
fn window_seq(mut fw: FileWindows) -> Vec<(usize, Vec<f32>)> {
let mut out = Vec::new();
while let Some(w) = fw.next_window().expect("window") {
out.push((w.start_sample, w.samples.to_vec()));
}
out
}
fn slice_seq(buf: &[f32], spec: WindowSpec) -> Vec<(usize, Vec<f32>)> {
let mut sw = SliceWindows::new(buf, spec);
let mut out = Vec::new();
while let Some(w) = sw.next_window().expect("slice window") {
out.push((w.start_sample, w.samples.to_vec()));
}
out
}
fn expected_seq(flat: &[f32], spec: WindowSpec) -> Vec<(usize, Vec<f32>)> {
if flat.len() <= spec.single_pass_max() {
vec![(0, flat.to_vec())]
} else {
slice_seq(flat, spec)
}
}
#[test]
fn test_file_windows_16k_geometry_matches_decode_words() {
let spec = ort_spec();
for &n in &[1usize, 8_000, 480_000, 480_001, 560_000, 900_000] {
let src = signal(n, 1.0);
let wav = encode_wav_pcm16(&src, 16000);
let flat = FileWindows::from_bytes(Bytes::copy_from_slice(&wav), spec, None)
.expect("open flat")
.drain_to_vec()
.expect("drain");
assert_eq!(flat.len(), n, "passthrough length changed at n={n}");
let got = window_seq(
FileWindows::from_bytes(Bytes::copy_from_slice(&wav), spec, None)
.expect("open windows"),
);
assert_eq!(got, expected_seq(&flat, spec), "geometry mismatch at n={n}");
}
}
#[test]
fn test_file_windows_48k_stereo_matches_slice_over_drain() {
let spec = ort_spec();
let n = 48_000 * 40;
let left = signal(n, 0.3);
let right = signal(n, 2.1);
let wav = stereo_wav_pcm16(&left, &right, 48_000);
let flat = FileWindows::from_bytes(Bytes::copy_from_slice(&wav), spec, None)
.expect("open flat")
.drain_to_vec()
.expect("drain");
assert!(
flat.len() > spec.single_pass_max(),
"expected the chunked regime, got {} samples",
flat.len()
);
let got = window_seq(
FileWindows::from_bytes(Bytes::copy_from_slice(&wav), spec, None)
.expect("open windows"),
);
assert_eq!(got, slice_seq(&flat, spec));
}
#[test]
fn test_file_windows_channel_select_matches_batch_per_channel_decode() {
for rate in [16_000u32, 48_000] {
let n = rate as usize * 3;
let left = signal(n, 0.3);
let right = signal(n, 2.1);
let wav = stereo_wav_pcm16(&left, &right, rate);
let batch = crate::inference::audio::decode_audio_bytes_shared_channels(
Bytes::copy_from_slice(&wav),
)
.expect("batch per-channel decode");
assert_eq!(batch.len(), 2, "expected a stereo decode at {rate}Hz");
assert_ne!(batch[0], batch[1]);
for (k, want) in batch.iter().enumerate() {
let streamed = FileWindows::from_bytes_channel(
Bytes::copy_from_slice(&wav),
WindowSpec::flat(),
None,
k,
)
.expect("open channel")
.drain_to_vec()
.expect("drain channel");
assert_eq!(&streamed, want, "rate={rate} channel={k}");
}
}
}
#[test]
fn test_file_windows_channel_select_out_of_range_is_empty() {
let src = signal(16_000, 1.0);
let wav = encode_wav_pcm16(&src, 16000); let streamed = FileWindows::from_bytes_channel(
Bytes::copy_from_slice(&wav),
WindowSpec::flat(),
None,
5,
)
.expect("open")
.drain_to_vec()
.expect("drain");
assert!(streamed.is_empty(), "got {} samples", streamed.len());
}
#[test]
fn test_file_windows_single_pass_yields_one_window() {
let spec = ort_spec();
let src = signal(10_000, 0.7);
let wav = encode_wav_pcm16(&src, 16000);
let got = window_seq(
FileWindows::from_bytes(Bytes::copy_from_slice(&wav), spec, None).expect("open"),
);
assert_eq!(got.len(), 1);
assert_eq!(got[0].0, 0);
assert_eq!(got[0].1.len(), 10_000);
}
#[test]
fn test_file_windows_total_16k_samples_is_exact_at_16k() {
let spec = ort_spec();
let n = 700_000; let src = signal(n, 1.3);
let wav = encode_wav_pcm16(&src, 16000);
let mut fw =
FileWindows::from_bytes(Bytes::copy_from_slice(&wav), spec, None).expect("open");
while fw.next_window().expect("window").is_some() {}
assert_eq!(fw.total_16k_samples(), n);
}
#[test]
#[ignore = "decode-only peak-RSS instrument; drive with GIGASTT_PEAK_SECONDS under /usr/bin/time"]
fn zzz_streaming_decode_peak_instrument() {
let secs: usize = std::env::var("GIGASTT_PEAK_SECONDS")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(5);
let path = std::env::temp_dir().join(format!("gigastt_peak_{secs}s.wav"));
{
use std::io::Write;
let rate = 48_000u32;
let frames = secs * rate as usize;
let data_bytes = (frames * 2 * 2) as u32;
let f = std::fs::File::create(&path).expect("create temp wav");
let mut w = std::io::BufWriter::new(f);
w.write_all(b"RIFF").unwrap();
w.write_all(&(36 + data_bytes).to_le_bytes()).unwrap();
w.write_all(b"WAVE").unwrap();
w.write_all(b"fmt ").unwrap();
w.write_all(&16u32.to_le_bytes()).unwrap();
w.write_all(&1u16.to_le_bytes()).unwrap();
w.write_all(&2u16.to_le_bytes()).unwrap();
w.write_all(&rate.to_le_bytes()).unwrap();
w.write_all(&(rate * 4).to_le_bytes()).unwrap();
w.write_all(&4u16.to_le_bytes()).unwrap();
w.write_all(&16u16.to_le_bytes()).unwrap();
w.write_all(b"data").unwrap();
w.write_all(&data_bytes.to_le_bytes()).unwrap();
for sec in 0..secs {
let base = (sec * rate as usize) as f32;
for i in 0..rate as usize {
let t = base + i as f32;
let s = (0.4 * (t * 0.02).sin() * i16::MAX as f32) as i16;
w.write_all(&s.to_le_bytes()).unwrap();
w.write_all(&s.to_le_bytes()).unwrap();
}
}
w.flush().unwrap();
}
let p = path.to_str().unwrap();
let total = if std::env::var("GIGASTT_PEAK_MODE").as_deref() == Ok("drain") {
FileWindows::decode_file(p, None).expect("drain").len()
} else {
let mut fw = FileWindows::open(p, ort_spec(), None).expect("open temp wav");
let mut counted = 0usize;
while let Some(win) = fw.next_window().expect("window") {
counted += win.samples.len();
}
assert!(counted >= fw.total_16k_samples());
fw.total_16k_samples()
};
let _ = std::fs::remove_file(&path);
let expected = secs * 16_000;
assert!(
(total as i64 - expected as i64).unsigned_abs() < 16_000,
"total {total} not within 1 s of {expected}"
);
}
}