use std::io::{self, Read};
use derive_new::new;
use symphonia::{
core::{
audio::Signal,
codecs::{Decoder, CODEC_TYPE_NULL},
conv::FromSample,
formats::FormatReader,
io::{MediaSource, MediaSourceStream},
probe::Hint,
sample::{i24, u24},
},
default,
};
use thiserror::Error;
use crate::IntoDataBytes;
use super::{sample::Sample, RawSamples};
#[derive(new)]
pub struct Audio {
reader: Box<dyn FormatReader>,
decoder: Box<dyn Decoder>,
}
#[derive(Debug, Error)]
pub enum AudioOpenError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("symphonia can't open this file: {0}")]
Symphonia(#[from] symphonia::core::errors::Error),
}
impl PartialEq for AudioOpenError {
fn eq(&self, other: &Self) -> bool {
match self {
AudioOpenError::Io(_) => matches!(other, AudioOpenError::Io(_)),
AudioOpenError::Symphonia(_) => matches!(other, AudioOpenError::Symphonia(_)),
}
}
}
impl Audio {
pub fn open(
source: impl MediaSource + 'static,
extension: Option<&str>,
) -> Result<Audio, AudioOpenError> {
let registry = default::get_codecs();
let probe = default::get_probe();
let mss = MediaSourceStream::new(Box::new(source), Default::default());
let reader = probe
.format(
&{
let mut hint = Hint::new();
if let Some(e) = extension {
hint.with_extension(e);
}
hint
},
mss,
&Default::default(),
&Default::default(),
)?
.format;
let decoder = registry.make(
reader
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.map(|t| &t.codec_params)
.unwrap_or(&Default::default()),
&Default::default(),
)?;
Ok(Audio::new(reader, decoder))
}
}
impl IntoDataBytes for Audio {
fn into_data_bytes(self) -> crate::Bytes {
self.reader.into_inner().bytes().flatten().collect()
}
}
macro_rules! dynamic_map(
($dynimage: expr, $image: pat => $action: expr) => ({
match $dynimage {
symphonia::core::audio::AudioBufferRef::U8($image) => symphonia::core::audio::AudioBufferRef::U8($action),
symphonia::core::audio::AudioBufferRef::U16($image) => symphonia::core::audio::AudioBufferRef::U16($action),
symphonia::core::audio::AudioBufferRef::U24($image) => symphonia::core::audio::AudioBufferRef::U24($action),
symphonia::core::audio::AudioBufferRef::U32($image) => symphonia::core::audio::AudioBufferRef::U32($action),
symphonia::core::audio::AudioBufferRef::S8($image) => symphonia::core::audio::AudioBufferRef::S8($action),
symphonia::core::audio::AudioBufferRef::S16($image) => symphonia::core::audio::AudioBufferRef::S16($action),
symphonia::core::audio::AudioBufferRef::S24($image) => symphonia::core::audio::AudioBufferRef::S24($action),
symphonia::core::audio::AudioBufferRef::S32($image) => symphonia::core::audio::AudioBufferRef::S32($action),
symphonia::core::audio::AudioBufferRef::F32($image) => symphonia::core::audio::AudioBufferRef::F32($action),
symphonia::core::audio::AudioBufferRef::F64($image) => symphonia::core::audio::AudioBufferRef::F64($action),
}
});
($dynimage: expr, $image:pat_param, $action: expr) => (
match $dynimage {
symphonia::core::audio::AudioBufferRef::U8($image) => $action,
symphonia::core::audio::AudioBufferRef::U16($image) => $action,
symphonia::core::audio::AudioBufferRef::U24($image) => $action,
symphonia::core::audio::AudioBufferRef::U32($image) => $action,
symphonia::core::audio::AudioBufferRef::S8($image) => $action,
symphonia::core::audio::AudioBufferRef::S16($image) => $action,
symphonia::core::audio::AudioBufferRef::S24($image) => $action,
symphonia::core::audio::AudioBufferRef::S32($image) => $action,
symphonia::core::audio::AudioBufferRef::F32($image) => $action,
symphonia::core::audio::AudioBufferRef::F64($image) => $action,
}
);
);
impl<S> From<Audio> for RawSamples<S>
where
S: Sample
+ FromSample<u8>
+ FromSample<u16>
+ FromSample<u24>
+ FromSample<u32>
+ FromSample<i8>
+ FromSample<i16>
+ FromSample<i24>
+ FromSample<i32>
+ FromSample<f32>
+ FromSample<f64>,
{
fn from(mut value: Audio) -> Self {
RawSamples::from({
let mut result = Vec::new();
while let Ok(packet) = value.reader.next_packet() {
if let Ok(src) = value.decoder.decode(&packet) {
dynamic_map!(
src,
ref audio_buffer,
result.append(
&mut (0usize..src.spec().channels.count())
.flat_map(|ch| audio_buffer
.chan(ch)
.iter()
.map(|s| S::from_sample(*s))
.collect::<Vec<S>>())
.collect::<Vec<S>>()
)
)
}
}
result
})
}
}
#[cfg(test)]
mod tests {
use std::fs::File;
use project_root::get_project_root;
use crate::IntoDataBytes;
use super::Audio;
#[test]
fn open_sample_file() {
let original =
&include_bytes!("../../../testing material/sound/sample-3s.mp3")[52079 - 51826..];
let path = get_project_root()
.expect("can't find project root!")
.join("testing material")
.join("sound")
.join("sample-3s.mp3");
let result = Audio::open(
File::open(&path).expect("can't find test file!"),
path.extension().and_then(|s| s.to_str()),
)
.map(|audio| audio.into_data_bytes());
dbg!(original.len());
if let Ok(ref r) = result {
dbg!(r.len());
}
assert_eq!(Ok(original), result.as_ref().map(Vec::as_slice));
}
}