#![forbid(unsafe_code)]
#![deny(clippy::arithmetic_side_effects)]
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::audio::{AudioDecoder, AudioDecoderOptions};
use symphonia::core::codecs::registry::CodecRegistry;
use symphonia::core::formats::probe::{Hint, Probe};
use symphonia::core::formats::{FormatOptions, FormatReader, Track, TrackType};
use symphonia::core::io::{MediaSource, MediaSourceStream};
use symphonia::core::meta::MetadataOptions;
#[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 const 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 format_reader = probe
.probe(&hint, mss, format_opts, metadata_opts)
.map_err(LoadError::UnkownFormat)?;
let track = format_reader
.default_track(TrackType::Audio)
.ok_or(LoadError::NoAudioTrackFound)?;
let audio_params = track
.codec_params
.as_ref()
.and_then(|p| p.audio())
.ok_or(LoadError::NoAudioCodecFound)?;
let num_channels = if let Some(ch) = audio_params.channels.as_ref().map(|ch| ch.count())
&& ch > 0
{
ch
} else {
return Err(LoadError::NoAudioChannelsFound);
};
let sample_rate = if let Some(sr) = audio_params.sample_rate {
let sr = NonZeroU32::new(sr);
#[cfg(any(feature = "tracing", feature = "log"))]
{
if sr.is_none() {
warn!("MediaSource returned a sample rate of 0");
}
}
sr
} else {
None
};
Ok(ProbedAudioSource {
format_reader,
sample_rate,
num_channels: NonZeroUsize::new(num_channels).unwrap(),
})
}
#[cfg(feature = "decode-native")]
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 AudioDecoder,
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(
probed.format_reader.as_mut(),
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 AudioDecoder,
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(
probed.format_reader.as_mut(),
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 AudioDecoder,
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(
probed.format_reader.as_mut(),
&config.config,
out_sample_rate,
original_sample_rate,
probed.num_channels,
&mut resampler,
decoder,
))
} else {
Some(decode::decode_f32(
probed.format_reader.as_mut(),
&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 AudioDecoder, 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 options = AudioDecoderOptions::default()
.verify(config.verify)
.gapless(config.gapless);
if let Some(cache) = cache
&& config.cache_decoder
{
cache.with_decoder_mut(
probed,
&options,
codec_registry,
config.track_index,
&mut |probed, decoder| (f)(probed, decoder, original_sample_rate),
)?;
} else {
let mut decoder = create_decoder(&probed, config.track_index, &options, codec_registry)?;
(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 AudioDecoder,
) -> 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(
probed.format_reader.as_mut(),
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(
probed.format_reader.as_mut(),
config,
target_sample_rate,
original_sample_rate,
probed.num_channels,
&mut resampler,
decoder,
));
}
pcm.unwrap()
}
pub struct ProbedAudioSource {
format_reader: Box<dyn FormatReader>,
sample_rate: Option<NonZeroU32>,
num_channels: NonZeroUsize,
}
impl ProbedAudioSource {
pub fn new(
format_reader: Box<dyn FormatReader>,
sample_rate: Option<NonZeroU32>,
num_channels: NonZeroUsize,
) -> Self {
Self {
format_reader,
sample_rate,
num_channels,
}
}
pub fn format_reader(&self) -> &dyn FormatReader {
self.format_reader.as_ref()
}
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
}
}
impl From<ProbedAudioSource> for Box<dyn FormatReader> {
fn from(value: ProbedAudioSource) -> Self {
value.format_reader
}
}
#[derive(Clone, Copy)]
pub struct DecodeConfig {
pub max_bytes: usize,
pub default_alloc_frames: usize,
pub verify: bool,
pub gapless: bool,
pub cache_decoder: bool,
pub cache_resampler: bool,
pub track_index: Option<usize>,
#[cfg(feature = "resampler")]
pub resample_quality: ResampleQuality,
}
impl Default for DecodeConfig {
fn default() -> Self {
Self {
max_bytes: DEFAULT_MAX_BYTES,
default_alloc_frames: 32768,
verify: false,
gapless: true,
cache_decoder: true,
cache_resampler: true,
track_index: None,
#[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,
}
}
}
fn get_track(
format_reader: &dyn FormatReader,
track_index: Option<usize>,
) -> Result<&Track, LoadError> {
if let Some(i) = track_index {
let tracks = format_reader.tracks();
let track = tracks.get(i).ok_or(LoadError::TrackIndexOutOfBounds {
index: i,
num_tracks: tracks.len(),
})?;
if track
.codec_params
.as_ref()
.and_then(|p| p.audio())
.is_none()
{
return Err(LoadError::NoAudioCodecFound);
}
Ok(track)
} else {
format_reader
.default_track(TrackType::Audio)
.ok_or(LoadError::NoAudioTrackFound)
}
}
fn create_decoder(
probed: &ProbedAudioSource,
track_index: Option<usize>,
options: &AudioDecoderOptions,
codec_registry: &CodecRegistry,
) -> Result<Box<dyn AudioDecoder>, LoadError> {
let track = get_track(probed.format_reader(), track_index)?;
let codec_params = track.codec_params.as_ref().and_then(|p| p.audio()).unwrap();
codec_registry
.make_audio_decoder(codec_params, options)
.map_err(LoadError::CouldNotCreateDecoder)
}