use std::fs::File;
use std::num::{NonZeroU32, NonZeroUsize};
use std::path::Path;
#[cfg(feature = "stretch-sinc-resampler")]
use fixed_resample::PacketResampler;
use symphonia::core::codecs::{CodecRegistry, Decoder};
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::{MediaSource, MediaSourceStream};
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::{Hint, Probe, ProbeResult};
#[cfg(all(feature = "log", not(feature = "tracing")))]
use log::warn;
#[cfg(feature = "tracing")]
use tracing::warn;
pub use symphonia;
#[cfg(feature = "resampler")]
pub mod resample;
#[cfg(feature = "resampler")]
pub use resample::ResampleQuality;
pub mod cache;
pub mod error;
mod decode;
mod resource;
pub use resource::*;
use error::LoadError;
use crate::cache::Cache;
#[cfg(feature = "resampler")]
use crate::resample::ResamplerKey;
pub static DEFAULT_MAX_BYTES: usize = 1_000_000_000;
pub fn probe_from_file<P: AsRef<Path>>(
path: P,
custom_probe: Option<&Probe>,
) -> Result<ProbedAudioSource, LoadError> {
let path: &Path = path.as_ref();
let file = File::open(path)?;
let mut hint = Hint::new();
if let Some(extension) = path.extension()
&& let Some(extension_str) = extension.to_str()
{
hint.with_extension(extension_str);
}
probe_from_source(Box::new(file), Some(hint), custom_probe)
}
pub fn probe_from_source(
source: Box<dyn MediaSource>,
hint: Option<Hint>,
custom_probe: Option<&Probe>,
) -> Result<ProbedAudioSource, LoadError> {
let probe = custom_probe.unwrap_or_else(|| symphonia::default::get_probe());
let mss = MediaSourceStream::new(source, Default::default());
let format_opts: FormatOptions = Default::default();
let metadata_opts: MetadataOptions = Default::default();
let hint = hint.unwrap_or_default();
let probed = probe
.format(&hint, mss, &format_opts, &metadata_opts)
.map_err(LoadError::UnkownFormat)?;
let track = probed
.format
.default_track()
.ok_or_else(|| LoadError::NoTrackFound)?;
let sample_rate = track.codec_params.sample_rate.and_then(|sr| {
let sr = NonZeroU32::new(sr);
#[cfg(any(feature = "tracing", feature = "log"))]
{
if sr.is_none() {
warn!("Audio source returned a sample rate of 0");
}
}
sr
});
let num_channels = track
.codec_params
.channels
.ok_or_else(|| LoadError::NoChannelsFound)?
.count();
if num_channels == 0 {
return Err(LoadError::NoChannelsFound);
}
Ok(ProbedAudioSource {
probed,
sample_rate,
num_channels: NonZeroUsize::new(num_channels).unwrap(),
})
}
pub fn decode(
probed: ProbedAudioSource,
config: &DecodeConfig,
target_sample_rate: Option<NonZeroU32>,
cache: Option<&dyn Cache>,
custom_registry: Option<&CodecRegistry>,
) -> Result<DecodedAudio, LoadError> {
#[cfg(not(feature = "resampler"))]
let _ = target_sample_rate;
let mut pcm = None;
with_decoder(
probed,
config,
custom_registry,
cache,
|mut probed: ProbedAudioSource,
decoder: &mut dyn Decoder,
original_sample_rate: NonZeroU32| {
#[cfg(feature = "resampler")]
if let Some(target_sample_rate) = target_sample_rate
&& original_sample_rate != target_sample_rate
{
pcm = Some(
resample(
probed,
config,
target_sample_rate,
original_sample_rate,
cache,
decoder,
)
.map(|pcm| pcm.into()),
);
return;
}
pcm = Some(decode::decode_native_bitdepth(
&mut probed.probed,
config,
probed.num_channels,
original_sample_rate,
original_sample_rate,
decoder,
));
},
)?;
pcm.unwrap()
}
pub fn decode_f32(
probed: ProbedAudioSource,
config: &DecodeConfig,
target_sample_rate: Option<NonZeroU32>,
cache: Option<&dyn Cache>,
custom_registry: Option<&CodecRegistry>,
) -> Result<DecodedAudioF32, LoadError> {
#[cfg(not(feature = "resampler"))]
let _ = target_sample_rate;
let mut pcm = None;
with_decoder(
probed,
config,
custom_registry,
cache,
|mut probed: ProbedAudioSource,
decoder: &mut dyn Decoder,
original_sample_rate: NonZeroU32| {
#[cfg(feature = "resampler")]
if let Some(target_sample_rate) = target_sample_rate
&& original_sample_rate != target_sample_rate
{
pcm = Some(resample(
probed,
config,
target_sample_rate,
original_sample_rate,
cache,
decoder,
));
return;
}
pcm = Some(decode::decode_f32(
&mut probed.probed,
config,
probed.num_channels,
original_sample_rate,
original_sample_rate,
decoder,
));
},
)?;
pcm.unwrap()
}
#[cfg(feature = "stretch-sinc-resampler")]
pub fn decode_stretched(
probed: ProbedAudioSource,
stretch: f64,
target_sample_rate: Option<NonZeroU32>,
config: &DecodeStretchedConfig,
cache: Option<&dyn Cache>,
custom_registry: Option<&CodecRegistry>,
) -> Result<DecodedAudioF32, LoadError> {
use fixed_resample::rubato;
let mut pcm = None;
with_decoder(
probed,
&config.config,
custom_registry,
cache,
|mut probed: ProbedAudioSource,
decoder: &mut dyn Decoder,
original_sample_rate: NonZeroU32| {
let mut needs_resample = stretch != 1.0;
if let Some(target_sample_rate) = target_sample_rate
&& !needs_resample
{
needs_resample = original_sample_rate != target_sample_rate;
}
pcm = if needs_resample {
let out_sample_rate = target_sample_rate.unwrap_or(original_sample_rate);
let ratio =
(out_sample_rate.get() as f64 / original_sample_rate.get() as f64) * stretch;
let mut resampler = PacketResampler::from_custom(Box::new(
rubato::Async::new_sinc(
ratio,
1.0,
&rubato::SincInterpolationParameters {
sinc_len: config.sinc_len,
f_cutoff: rubato::calculate_cutoff(config.sinc_len, config.window),
oversampling_factor: config.oversampling_factor,
interpolation: config.interpolation,
window: config.window,
},
512,
probed.num_channels.get(),
rubato::FixedAsync::Input,
)
.unwrap(),
));
Some(decode::decode_resampled(
&mut probed.probed,
&config.config,
out_sample_rate,
original_sample_rate,
probed.num_channels,
&mut resampler,
decoder,
))
} else {
Some(decode::decode_f32(
&mut probed.probed,
&config.config,
probed.num_channels,
original_sample_rate,
original_sample_rate,
decoder,
))
};
},
)?;
pcm.unwrap()
}
fn with_decoder(
probed: ProbedAudioSource,
config: &DecodeConfig,
custom_registry: Option<&CodecRegistry>,
cache: Option<&dyn Cache>,
mut f: impl FnMut(ProbedAudioSource, &mut dyn Decoder, NonZeroU32),
) -> Result<(), LoadError> {
let original_sample_rate = probed.sample_rate.unwrap_or_else(|| {
#[cfg(any(feature = "tracing", feature = "log"))]
warn!("Audio resource has an unkown sample rate. Assuming a sample rate of 44100...");
NonZeroU32::new(44100).unwrap()
});
let codec_registry = custom_registry.unwrap_or_else(|| symphonia::default::get_codecs());
let opts = symphonia::core::codecs::DecoderOptions {
verify: config.verify,
};
if let Some(cache) = cache
&& config.cache_decoder
{
cache.with_decoder_mut(probed, &opts, codec_registry, &mut |probed, decoder| {
(f)(probed, decoder, original_sample_rate)
})?;
} else {
let track = probed
.probed
.format
.default_track()
.ok_or_else(|| LoadError::NoTrackFound)?;
let mut decoder = codec_registry
.make(&track.codec_params, &opts)
.map_err(LoadError::CouldNotCreateDecoder)?;
(f)(probed, &mut *decoder, original_sample_rate);
}
Ok(())
}
#[cfg(feature = "resampler")]
fn resample(
mut probed: ProbedAudioSource,
config: &DecodeConfig,
target_sample_rate: NonZeroU32,
original_sample_rate: NonZeroU32,
cache: Option<&dyn Cache>,
decoder: &mut dyn Decoder,
) -> Result<DecodedAudioF32, LoadError> {
let mut pcm = None;
let key = ResamplerKey {
source_sample_rate: original_sample_rate,
target_sample_rate,
channels: probed.num_channels.get() as u16,
quality: match config.resample_quality {
ResampleQuality::VeryLow => 0,
ResampleQuality::Low => 1,
ResampleQuality::High => 2,
ResampleQuality::HighWithLowLatency => 3,
},
};
if let Some(cache) = cache
&& config.cache_resampler
{
cache.with_resampler_mut(key, probed, &mut |mut probed, resampler| {
if resampler.nbr_channels() != probed.num_channels.get() {
pcm = Some(Err(LoadError::InvalidResampler {
needed_channels: probed.num_channels.get(),
got_channels: resampler.nbr_channels(),
}));
return;
}
pcm = Some(decode::decode_resampled(
&mut probed.probed,
config,
target_sample_rate,
original_sample_rate,
probed.num_channels,
resampler,
decoder,
));
resampler.reset();
});
} else {
let mut resampler = key.create_resampler();
pcm = Some(decode::decode_resampled(
&mut probed.probed,
config,
target_sample_rate,
original_sample_rate,
probed.num_channels,
&mut resampler,
decoder,
));
}
pcm.unwrap()
}
pub struct ProbedAudioSource {
probed: ProbeResult,
sample_rate: Option<NonZeroU32>,
num_channels: NonZeroUsize,
}
impl ProbedAudioSource {
pub fn probe_result(&self) -> &ProbeResult {
&self.probed
}
pub fn probe_result_mut(&mut self) -> &mut ProbeResult {
&mut self.probed
}
pub fn sample_rate(&self) -> Option<NonZeroU32> {
self.sample_rate
}
pub fn override_sample_rate(&mut self, sample_rate: NonZeroU32) {
self.sample_rate = Some(sample_rate);
}
pub fn num_channels(&self) -> NonZeroUsize {
self.num_channels
}
}
#[derive(Clone, Copy)]
pub struct DecodeConfig {
pub max_bytes: usize,
pub verify: bool,
pub cache_decoder: bool,
pub cache_resampler: bool,
#[cfg(feature = "resampler")]
pub resample_quality: ResampleQuality,
}
impl Default for DecodeConfig {
fn default() -> Self {
Self {
max_bytes: DEFAULT_MAX_BYTES,
verify: false,
cache_decoder: true,
cache_resampler: true,
#[cfg(feature = "resampler")]
resample_quality: ResampleQuality::default(),
}
}
}
#[cfg(feature = "stretch-sinc-resampler")]
pub struct DecodeStretchedConfig {
pub config: DecodeConfig,
pub sinc_len: usize,
pub window: fixed_resample::rubato::WindowFunction,
pub oversampling_factor: usize,
pub interpolation: fixed_resample::rubato::SincInterpolationType,
}
#[cfg(feature = "stretch-sinc-resampler")]
impl Default for DecodeStretchedConfig {
fn default() -> Self {
Self {
config: DecodeConfig::default(),
sinc_len: 256,
oversampling_factor: 256,
interpolation: fixed_resample::rubato::SincInterpolationType::Quadratic,
window: fixed_resample::rubato::WindowFunction::Blackman2,
}
}
}