use std::error::Error;
use std::io::{Read, Seek, SeekFrom};
use crate::buffer::{AudioBuffer, ChannelData};
use symphonia::core::audio::GenericAudioBufferRef;
use symphonia::core::codecs::audio::{
AudioCodecId, AudioDecoder, AudioDecoderOptions, FinalizeResult,
};
use symphonia::core::errors::Error as SymphoniaError;
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, FormatReader, TrackType};
use symphonia::core::meta::MetadataOptions;
pub(crate) fn decode_media_data<R: std::io::Read + Send + Sync + 'static>(
input: R,
target_sample_rate: f32,
) -> Result<AudioBuffer, Box<dyn std::error::Error + Send + Sync>> {
let mut sample_rate = None;
let mut buffer: Option<AudioBuffer> = None;
for chunk in MediaDecoder::try_new(input)? {
let chunk = chunk?;
match sample_rate {
Some(rate) if rate != chunk.sample_rate() => {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"decoded audio sample rate changed midstream",
)));
}
Some(_) => {}
None => sample_rate = Some(chunk.sample_rate()),
}
match buffer {
Some(ref mut buffer) if buffer.number_of_channels() != chunk.number_of_channels() => {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"decoded audio channel count changed midstream",
)));
}
Some(ref mut buffer) => buffer.extend(&chunk),
None => buffer = Some(chunk),
}
}
let mut buffer = buffer.unwrap_or_else(|| AudioBuffer::from(vec![vec![]], target_sample_rate));
buffer.resample(target_sample_rate);
Ok(buffer)
}
struct MediaInput<R> {
input: R,
}
impl<R: Read> MediaInput<R> {
pub fn new(input: R) -> Self {
Self { input }
}
}
impl<R: Read> Read for MediaInput<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.input.read(buf)
}
}
impl<R> Seek for MediaInput<R> {
fn seek(&mut self, _pos: SeekFrom) -> std::io::Result<u64> {
Err(std::io::Error::new(
std::io::ErrorKind::Unsupported,
"MediaInput does not support seeking",
))
}
}
impl<R: Read + Send + Sync> symphonia::core::io::MediaSource for MediaInput<R> {
fn is_seekable(&self) -> bool {
false
}
fn byte_len(&self) -> Option<u64> {
None
}
}
pub(crate) struct MediaDecoder {
format: Box<dyn FormatReader>,
decoder: Box<dyn AudioDecoder>,
track_index: usize,
packet_count: usize,
}
impl MediaDecoder {
pub fn try_new<R: std::io::Read + Send + Sync + 'static>(
input: R,
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let input = Box::new(MediaInput::new(input));
let stream = symphonia::core::io::MediaSourceStream::new(input, Default::default());
let hint = Hint::new();
let format_opts: FormatOptions = Default::default();
let metadata_opts: MetadataOptions = Default::default();
let decoder_opts = AudioDecoderOptions::default().verify(true);
let format =
symphonia::default::get_probe().probe(&hint, stream, format_opts, metadata_opts)?;
let track = format
.default_track(TrackType::Audio)
.ok_or(SymphoniaError::Unsupported(
"no default media track available",
))?;
let track_index = format
.tracks()
.iter()
.position(|t| t.id == track.id)
.unwrap();
let codec_params = track
.codec_params
.as_ref()
.and_then(|params| params.audio())
.ok_or(SymphoniaError::Unsupported(
"default media track is not an audio track",
))?;
let decoder = symphonia::default::get_codecs()
.make_audio_decoder(codec_params, &decoder_opts)
.map_err(|err| {
if matches!(
err,
SymphoniaError::Unsupported("core (codec): unsupported audio codec")
) {
Box::new(UnsupportedAudioCodecError {
codec: codec_params.codec,
}) as Box<dyn std::error::Error + Send + Sync>
} else {
Box::new(err) as Box<dyn std::error::Error + Send + Sync>
}
})?;
Ok(Self {
format,
decoder,
track_index,
packet_count: 0,
})
}
}
#[derive(Debug)]
struct UnsupportedAudioCodecError {
codec: AudioCodecId,
}
impl std::fmt::Display for UnsupportedAudioCodecError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unsupported audio codec: {}", self.codec)
}
}
impl std::error::Error for UnsupportedAudioCodecError {}
impl Iterator for MediaDecoder {
type Item = Result<AudioBuffer, Box<dyn Error + Send + Sync>>;
fn next(&mut self) -> Option<Self::Item> {
let Self {
format,
decoder,
track_index,
packet_count,
} = self;
let track = format.tracks().get(*track_index)?;
let track_id = track.id;
loop {
let packet = match format.next_packet() {
Ok(None) => {
log::debug!("Decoding finished after {packet_count} packet(s)");
let FinalizeResult { verify_ok } = decoder.finalize();
if verify_ok == Some(false) {
log::warn!("Verification of decoded data failed");
}
return None;
}
Err(err) => {
if let SymphoniaError::IoError(err) = &err {
if err.kind() == std::io::ErrorKind::UnexpectedEof {
log::debug!(
"Decoding finished after {packet_count} packet(s) at unexpected EOF"
);
let FinalizeResult { verify_ok } = decoder.finalize();
if verify_ok == Some(false) {
log::warn!("Verification of decoded data failed");
}
return None;
}
}
log::warn!(
"Failed to fetch next packet following packet #{packet_count}: {err}"
);
return Some(Err(Box::new(err)));
}
Ok(Some(packet)) => {
*packet_count += 1;
packet
}
};
let packet_track_id = packet.track_id;
if packet_track_id != track_id {
log::debug!(
"Skipping packet from other track {packet_track_id} while decoding track {track_id}"
);
continue;
}
match decoder.decode(&packet) {
Ok(input) => {
let output = input.into();
return Some(Ok(output));
}
Err(SymphoniaError::DecodeError(err)) => {
log::warn!("Failed to decode packet #{packet_count}: {err}");
}
Err(SymphoniaError::IoError(err)) => {
log::warn!("I/O error while decoding packet #{packet_count}: {err}");
}
Err(err) => {
return Some(Err(Box::new(err)));
}
};
}
}
}
impl From<GenericAudioBufferRef<'_>> for AudioBuffer {
fn from(input: GenericAudioBufferRef<'_>) -> Self {
let sample_rate = input.spec().rate() as f32;
let mut data = Vec::new();
input.copy_to_vecs_planar::<f32>(&mut data);
let channels = data.into_iter().map(ChannelData::from).collect();
AudioBuffer::from_channels(channels, sample_rate)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_media_decoder() {
let input = Cursor::new(vec![0; 32]);
let media = MediaDecoder::try_new(input);
assert!(media.is_err()); }
#[test]
fn test_unsupported_audio_codec_error_includes_codec_id() {
let input = std::fs::File::open("samples/sample.webm").unwrap();
let media = MediaDecoder::try_new(input);
let err = match media {
Ok(_) => panic!("expected unsupported codec error"),
Err(err) => err.to_string(),
};
assert_eq!(err, "unsupported audio codec: 0x1001");
}
}