459 lines
16 KiB
Rust
459 lines
16 KiB
Rust
use crate::{constants::*, driver::tasks::mixer::mix_logic, input::Parsed};
|
|
|
|
use byteorder::{LittleEndian, WriteBytesExt};
|
|
use rubato::{FftFixedOut, Resampler};
|
|
use std::{
|
|
io::{ErrorKind as IoErrorKind, Read, Result as IoResult, Seek, Write},
|
|
mem,
|
|
ops::Range,
|
|
};
|
|
use symphonia_core::{
|
|
audio::{AudioBuffer, AudioBufferRef, Layout, Signal, SignalSpec},
|
|
conv::IntoSample,
|
|
io::MediaSource,
|
|
sample::Sample,
|
|
};
|
|
|
|
const SAMPLE_LEN: usize = mem::size_of::<f32>();
|
|
|
|
/// Adapter for Symphonia sources into an interleaved f32 bytestream.
|
|
///
|
|
/// This will output `f32`s in LE byte order, matching the channel count
|
|
/// of the input.
|
|
pub struct ToAudioBytes {
|
|
chan_count: usize,
|
|
chan_limit: usize,
|
|
parsed: Parsed,
|
|
/// Position with parsed's last decoded frame.
|
|
inner_pos: Range<usize>,
|
|
resample: Option<ResampleState>,
|
|
done: bool,
|
|
|
|
interrupted_samples: Vec<f32>,
|
|
interrupted_byte_pos: Range<usize>,
|
|
}
|
|
|
|
struct ResampleState {
|
|
/// Used to hold outputs from resampling, *ready to be used*.
|
|
resampled_data: Vec<Vec<f32>>,
|
|
/// The actual resampler.
|
|
resampler: FftFixedOut<f32>,
|
|
/// Used to hold inputs to resampler across packet boundaries.
|
|
scratch: AudioBuffer<f32>,
|
|
/// The range of floats in `resampled_data` which have not yet
|
|
/// been read.
|
|
resample_pos: Range<usize>,
|
|
}
|
|
|
|
impl ToAudioBytes {
|
|
pub fn new(parsed: Parsed, chan_limit: Option<usize>) -> Self {
|
|
let track_info = parsed.decoder.codec_params();
|
|
let sample_rate = track_info.sample_rate.unwrap_or(SAMPLE_RATE_RAW as u32);
|
|
let maybe_layout = track_info.channel_layout;
|
|
let maybe_chans = track_info.channels;
|
|
|
|
let chan_count = if let Some(chans) = maybe_chans {
|
|
chans.count()
|
|
} else if let Some(layout) = maybe_layout {
|
|
match layout {
|
|
Layout::Mono => 1,
|
|
Layout::Stereo => 2,
|
|
Layout::TwoPointOne => 3,
|
|
Layout::FivePointOne => 6,
|
|
}
|
|
} else {
|
|
2
|
|
};
|
|
|
|
let chan_limit = chan_limit.unwrap_or(chan_count);
|
|
|
|
let resample = (sample_rate != SAMPLE_RATE_RAW as u32).then(|| {
|
|
let spec = if let Some(chans) = maybe_chans {
|
|
SignalSpec::new(SAMPLE_RATE_RAW as u32, chans)
|
|
} else if let Some(layout) = maybe_layout {
|
|
SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, layout)
|
|
} else {
|
|
SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo)
|
|
};
|
|
|
|
let scratch = AudioBuffer::<f32>::new(MONO_FRAME_SIZE as u64, spec);
|
|
|
|
// TODO: integ. error handling here.
|
|
let resampler = FftFixedOut::new(
|
|
sample_rate as usize,
|
|
SAMPLE_RATE_RAW,
|
|
RESAMPLE_OUTPUT_FRAME_SIZE,
|
|
4,
|
|
chan_count,
|
|
)
|
|
.expect("Failed to create resampler.");
|
|
|
|
let resampled_data = resampler.output_buffer_allocate(true);
|
|
|
|
ResampleState {
|
|
resampled_data,
|
|
resampler,
|
|
scratch,
|
|
resample_pos: 0..0,
|
|
}
|
|
});
|
|
|
|
Self {
|
|
chan_count,
|
|
chan_limit,
|
|
parsed,
|
|
inner_pos: 0..0,
|
|
resample,
|
|
done: false,
|
|
|
|
interrupted_samples: Vec::with_capacity(chan_count),
|
|
interrupted_byte_pos: 0..0,
|
|
}
|
|
}
|
|
|
|
pub fn num_channels(&self) -> usize {
|
|
self.chan_count.min(self.chan_limit)
|
|
}
|
|
|
|
fn is_done(&self) -> bool {
|
|
self.done
|
|
&& self.inner_pos.is_empty()
|
|
&& self.resample.as_ref().map_or(true, |v| {
|
|
v.scratch.frames() == 0 && v.resample_pos.is_empty()
|
|
})
|
|
&& self.interrupted_byte_pos.is_empty()
|
|
}
|
|
}
|
|
|
|
impl Read for ToAudioBytes {
|
|
fn read(&mut self, mut buf: &mut [u8]) -> IoResult<usize> {
|
|
// NOTE: this is disturbingly similar to the mixer code, but different enough that we can't
|
|
// just reuse it freely.
|
|
let orig_sz = buf.len();
|
|
let num_chans = self.num_channels();
|
|
|
|
while !buf.is_empty() && !self.is_done() {
|
|
// Work to clear interrupted channel floats.
|
|
while !buf.is_empty() && !self.interrupted_byte_pos.is_empty() {
|
|
let index_of_first_f32 = self.interrupted_byte_pos.start / SAMPLE_LEN;
|
|
let f32_inner_pos = self.interrupted_byte_pos.start % SAMPLE_LEN;
|
|
let f32_bytes_remaining = SAMPLE_LEN - f32_inner_pos;
|
|
let to_write = f32_bytes_remaining.min(buf.len());
|
|
|
|
let bytes = self.interrupted_samples[index_of_first_f32].to_le_bytes();
|
|
let written = buf.write(&bytes[f32_inner_pos..][..to_write])?;
|
|
self.interrupted_byte_pos.start += written;
|
|
}
|
|
|
|
// Clear out already produced resampled floats.
|
|
if let Some(resample) = self.resample.as_mut() {
|
|
if !buf.is_empty() && !resample.resample_pos.is_empty() {
|
|
let bytes_advanced = write_resample_buffer(
|
|
&resample.resampled_data,
|
|
buf,
|
|
&mut resample.resample_pos,
|
|
&mut self.interrupted_samples,
|
|
&mut self.interrupted_byte_pos,
|
|
num_chans,
|
|
);
|
|
|
|
buf = &mut buf[bytes_advanced..];
|
|
}
|
|
|
|
if !resample.resample_pos.is_empty() {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Now work with new packets.
|
|
let source_packet = if !self.inner_pos.is_empty() {
|
|
Some(self.parsed.decoder.last_decoded())
|
|
} else if let Ok(pkt) = self.parsed.format.next_packet() {
|
|
if pkt.track_id() != self.parsed.track_id {
|
|
continue;
|
|
}
|
|
|
|
self.parsed
|
|
.decoder
|
|
.decode(&pkt)
|
|
.map(|pkt| {
|
|
self.inner_pos = 0..pkt.frames();
|
|
pkt
|
|
})
|
|
.ok()
|
|
} else {
|
|
// EOF.
|
|
None
|
|
};
|
|
|
|
if source_packet.is_none() {
|
|
self.done = true;
|
|
|
|
if let Some(resample) = self.resample.as_mut() {
|
|
if resample.scratch.frames() != 0 {
|
|
let data = &mut resample.resampled_data;
|
|
let resampler = &mut resample.resampler;
|
|
let in_len = resample.scratch.frames();
|
|
let to_render = resampler.input_frames_next().saturating_sub(in_len);
|
|
|
|
if to_render != 0 {
|
|
resample.scratch.render_reserved(Some(to_render));
|
|
for plane in resample.scratch.planes_mut().planes() {
|
|
for val in &mut plane[in_len..] {
|
|
*val = 0.0f32;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Luckily, we make use of the WHOLE input buffer here.
|
|
resampler
|
|
.process_into_buffer(resample.scratch.planes().planes(), data, None)
|
|
.unwrap();
|
|
|
|
// Calculate true end position using sample rate math
|
|
let ratio = (data[0].len() as f32) / (resample.scratch.frames() as f32);
|
|
let out_samples = (ratio * (in_len as f32)).round() as usize;
|
|
|
|
resample.scratch.clear();
|
|
resample.resample_pos = 0..out_samples;
|
|
}
|
|
}
|
|
|
|
// Now go back and make use of the buffer.
|
|
// We have to do this here because we can't make any guarantees about
|
|
// the read site having enough space to hold all samples etc.
|
|
continue;
|
|
}
|
|
|
|
let source_packet = source_packet.unwrap();
|
|
|
|
if let Some(resample) = self.resample.as_mut() {
|
|
// Do a resample using the newest packet.
|
|
let pkt_frames = source_packet.frames();
|
|
|
|
if pkt_frames == 0 {
|
|
continue;
|
|
}
|
|
|
|
let needed_in_frames = resample.resampler.input_frames_next();
|
|
let available_frames = self.inner_pos.len();
|
|
|
|
let force_copy =
|
|
resample.scratch.frames() != 0 || needed_in_frames > available_frames;
|
|
|
|
if (!force_copy) && matches!(source_packet, AudioBufferRef::F32(_)) {
|
|
// This is the only case where we can pull off a straight resample...
|
|
// I.e., skip scratch.
|
|
|
|
// NOTE: if let needed as if-let && {bool} is nightly only.
|
|
if let AudioBufferRef::F32(s_pkt) = source_packet {
|
|
let refs: Vec<&[f32]> = s_pkt
|
|
.planes()
|
|
.planes()
|
|
.iter()
|
|
.map(|s| &s[self.inner_pos.start..][..needed_in_frames])
|
|
.collect();
|
|
|
|
self.inner_pos.start += needed_in_frames;
|
|
|
|
resample
|
|
.resampler
|
|
.process_into_buffer(&refs, &mut resample.resampled_data, None)
|
|
.unwrap();
|
|
} else {
|
|
unreachable!()
|
|
}
|
|
} else {
|
|
// We either lack enough samples, or have the wrong data format, forcing
|
|
// a conversion/copy into scratch.
|
|
|
|
let old_scratch_len = resample.scratch.frames();
|
|
let missing_frames = needed_in_frames - old_scratch_len;
|
|
let frames_to_take = available_frames.min(missing_frames);
|
|
|
|
resample.scratch.render_reserved(Some(frames_to_take));
|
|
mix_logic::copy_into_resampler(
|
|
&source_packet,
|
|
&mut resample.scratch,
|
|
self.inner_pos.start,
|
|
old_scratch_len,
|
|
frames_to_take,
|
|
);
|
|
|
|
self.inner_pos.start += frames_to_take;
|
|
|
|
if resample.scratch.frames() == needed_in_frames {
|
|
resample
|
|
.resampler
|
|
.process_into_buffer(
|
|
resample.scratch.planes().planes(),
|
|
&mut resample.resampled_data,
|
|
None,
|
|
)
|
|
.unwrap();
|
|
resample.scratch.clear();
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
resample.resample_pos = 0..resample.resampled_data[0].len();
|
|
} else {
|
|
// Newest packet may be used straight away: just convert format
|
|
// to ensure it's f32.
|
|
let bytes_advanced = write_out(
|
|
&source_packet,
|
|
buf,
|
|
&mut self.inner_pos,
|
|
&mut self.interrupted_samples,
|
|
&mut self.interrupted_byte_pos,
|
|
num_chans,
|
|
);
|
|
|
|
buf = &mut buf[bytes_advanced..];
|
|
}
|
|
}
|
|
Ok(orig_sz - buf.len())
|
|
}
|
|
}
|
|
|
|
impl Seek for ToAudioBytes {
|
|
fn seek(&mut self, _pos: std::io::SeekFrom) -> IoResult<u64> {
|
|
Err(IoErrorKind::Unsupported.into())
|
|
}
|
|
}
|
|
|
|
impl MediaSource for ToAudioBytes {
|
|
fn is_seekable(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn byte_len(&self) -> Option<u64> {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn write_out(
|
|
source: &AudioBufferRef<'_>,
|
|
target: &mut [u8],
|
|
source_pos: &mut Range<usize>,
|
|
spillover: &mut Vec<f32>,
|
|
spill_range: &mut Range<usize>,
|
|
num_chans: usize,
|
|
) -> usize {
|
|
match source {
|
|
AudioBufferRef::U8(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::U16(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::U24(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::U32(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::S8(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::S16(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::S24(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::S32(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::F32(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
AudioBufferRef::F64(v) =>
|
|
write_symph_buffer(v, target, source_pos, spillover, spill_range, num_chans),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn write_symph_buffer<S>(
|
|
source: &AudioBuffer<S>,
|
|
buf: &mut [u8],
|
|
source_pos: &mut Range<usize>,
|
|
spillover: &mut Vec<f32>,
|
|
spill_range: &mut Range<usize>,
|
|
num_chans: usize,
|
|
) -> usize
|
|
where
|
|
S: Sample + IntoSample<f32>,
|
|
{
|
|
let float_space = buf.len() / SAMPLE_LEN;
|
|
let interleaved_space = float_space / num_chans;
|
|
let non_contiguous_end = (float_space % num_chans) != 0;
|
|
|
|
let remaining = source_pos.len();
|
|
let to_write = remaining.min(interleaved_space);
|
|
let need_spill = non_contiguous_end && to_write < remaining;
|
|
|
|
let samples_used = to_write + usize::from(need_spill);
|
|
let last_sample = source_pos.start + to_write;
|
|
|
|
if need_spill {
|
|
spillover.clear();
|
|
*spill_range = 0..num_chans * SAMPLE_LEN;
|
|
}
|
|
|
|
for (i, plane) in source.planes().planes()[..num_chans].iter().enumerate() {
|
|
for (j, sample) in plane[source_pos.start..][..to_write].iter().enumerate() {
|
|
// write this into the correct slot of buf.
|
|
let addr = ((j * num_chans) + i) * SAMPLE_LEN;
|
|
(&mut buf[addr..][..SAMPLE_LEN])
|
|
.write_f32::<LittleEndian>((*sample).into_sample())
|
|
.expect("Address known to exist by length checks.");
|
|
}
|
|
|
|
if need_spill {
|
|
spillover.push(plane[last_sample].into_sample());
|
|
}
|
|
}
|
|
|
|
source_pos.start += samples_used;
|
|
|
|
to_write * num_chans * SAMPLE_LEN
|
|
}
|
|
|
|
#[inline]
|
|
fn write_resample_buffer(
|
|
source: &[Vec<f32>],
|
|
buf: &mut [u8],
|
|
source_pos: &mut Range<usize>,
|
|
spillover: &mut Vec<f32>,
|
|
spill_range: &mut Range<usize>,
|
|
num_chans: usize,
|
|
) -> usize {
|
|
let float_space = buf.len() / SAMPLE_LEN;
|
|
let interleaved_space = float_space / num_chans;
|
|
let non_contiguous_end = (float_space % num_chans) != 0;
|
|
|
|
let remaining = source_pos.len();
|
|
let to_write = remaining.min(interleaved_space);
|
|
let need_spill = non_contiguous_end && to_write < remaining;
|
|
|
|
let samples_used = to_write + usize::from(need_spill);
|
|
let last_sample = source_pos.start + to_write;
|
|
|
|
if need_spill {
|
|
spillover.clear();
|
|
*spill_range = 0..num_chans * SAMPLE_LEN;
|
|
}
|
|
|
|
for (i, plane) in source[..num_chans].iter().enumerate() {
|
|
for (j, sample) in plane[source_pos.start..][..to_write].iter().enumerate() {
|
|
// write this into the correct slot of buf.
|
|
let addr = ((j * num_chans) + i) * SAMPLE_LEN;
|
|
(&mut buf[addr..][..SAMPLE_LEN])
|
|
.write_f32::<LittleEndian>(*sample)
|
|
.expect("Address well-formed according to bounds checks.");
|
|
}
|
|
|
|
if need_spill {
|
|
spillover.push(plane[last_sample]);
|
|
}
|
|
}
|
|
|
|
source_pos.start += samples_used;
|
|
|
|
to_write * num_chans * SAMPLE_LEN
|
|
}
|