use std::sync::OnceLock;
use symphonia::core::codecs::CodecParameters;
use symphonia::core::codecs::audio::well_known::{CODEC_ID_AAC, CODEC_ID_OPUS, CODEC_ID_VORBIS};
use symphonia::core::codecs::audio::{AudioCodecParameters, AudioDecoderOptions};
use symphonia::core::codecs::registry::CodecRegistry;
use symphonia::core::errors::Error as SymphoniaError;
use symphonia::core::formats::probe::Hint;
use symphonia::core::formats::{FormatOptions, Track, TrackType};
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use crate::error::{Error, Result};
use super::frames::{IntScale, push_frame};
use super::layout::ffmpeg_layout;
use super::opus::{OpusDecoder, pre_skip};
use super::trims::Trims;
use crate::wav::decode::WavData;
fn codecs() -> &'static CodecRegistry {
static CODECS: OnceLock<CodecRegistry> = OnceLock::new();
CODECS.get_or_init(|| {
let mut registry = CodecRegistry::new();
symphonia::default::register_enabled_codecs(&mut registry);
registry.register_audio_decoder::<OpusDecoder>();
registry
})
}
const SBR_CORE_MAX: u32 = 24_000;
fn unsupported(reason: String) -> Error {
Error::Unsupported { reason }
}
fn symphonia_error(what: &str, e: SymphoniaError) -> Error {
match e {
SymphoniaError::Unsupported(_) => unsupported(format!("{what}: {e}")),
e => bad(format!("{what}: {e}")),
}
}
pub(super) fn bad(reason: String) -> Error {
Error::InvalidArgument {
arg: "audio bytes",
reason,
}
}
fn decodable(track: &Track) -> Option<(&Track, AudioCodecParameters)> {
track
.codec_params
.as_ref()
.and_then(CodecParameters::audio)
.filter(|p| codecs().get_audio_decoder(p.codec).is_some())
.map(|p| (track, p.clone()))
}
pub(super) fn decode_stream(
mss: MediaSourceStream<'_>,
hint: Option<&str>,
trims: Trims,
int_scale: IntScale,
) -> Result<WavData> {
let mut probe_hint = Hint::new();
if let Some(ext) = hint {
probe_hint.with_extension(ext);
}
let mut format = symphonia::default::get_probe()
.probe(
&probe_hint,
mss,
FormatOptions::default(),
MetadataOptions::default(),
)
.map_err(|e| symphonia_error("probing audio format", e))?;
let (track, codec_params) = format
.default_track(TrackType::Audio)
.and_then(decodable)
.or_else(|| format.tracks().iter().find_map(decodable))
.ok_or_else(|| {
unsupported("audio file has no audio track symphonia decodes".to_string())
})?;
let track_id = track.id;
let (track_delay, track_padding) = (track.delay, track.padding);
let mut decoder = codecs()
.make_audio_decoder(
&codec_params,
&AudioDecoderOptions::default().gapless(false),
)
.map_err(|e| {
let what = format!(
"creating the {} decoder",
codecs()
.get_audio_decoder(codec_params.codec)
.map_or("audio", |d| d.codec.info.short_name)
);
symphonia_error(&what, e)
})?;
let opus = codec_params.codec == CODEC_ID_OPUS;
let vorbis = codec_params.codec == CODEC_ID_VORBIS;
let aac = codec_params.codec == CODEC_ID_AAC;
let delay = if opus {
pre_skip(&codec_params, track_delay)
} else {
track_delay.map_or(0, |d| d as usize)
};
let padding = track_padding.map_or(0, |p| p as usize);
let mut samples: Vec<f32> = Vec::new();
let mut spec: Option<(u32, usize)> = None;
let mut layout: Option<(Option<u64>, Vec<usize>)> = None;
let mut first_packet: Option<usize> = None;
loop {
let packet = match format.next_packet() {
Ok(Some(p)) => p,
Ok(None) | Err(SymphoniaError::ResetRequired) => break,
Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
break;
}
Err(e) => return Err(symphonia_error("reading packet", e)),
};
if packet.track_id != track_id {
continue;
}
let audio = match decoder.decode(&packet) {
Ok(a) => a,
Err(SymphoniaError::DecodeError(_)) => continue, Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
break;
}
Err(e) => return Err(symphonia_error("decoding packet", e)),
};
let here = (audio.spec().rate(), audio.spec().channels().count());
match spec {
None => {
if aac && here.0 <= SBR_CORE_MAX && trims.media_scale != Some(here.0) {
return Err(unsupported(format!(
"AAC at a core rate of {} Hz can carry SBR (HE-AAC), which symphonia does not decode: it would play at half the rate. Decode it with another decoder",
here.0
)));
}
spec = Some(here);
}
Some(s) if s != here => {
return Err(unsupported(format!(
"the audio changes from {} Hz, {} channel(s) to {} Hz, {} channel(s) mid-stream",
s.0, s.1, here.0, here.1
)));
}
Some(_) => {}
}
let (_, order) = layout.get_or_insert_with(|| match trims.wav {
Some(header) => (header.layout, (0..here.1).collect()),
None => ffmpeg_layout(audio.spec().channels(), here.1),
});
first_packet.get_or_insert(audio.frames());
push_frame(&audio, order, int_scale, 0, audio.frames(), &mut samples);
}
let Some((sample_rate, channels)) = spec.filter(|&(rate, ch)| rate > 0 && ch > 0) else {
return Err(bad("decoded audio is empty".to_string()));
};
let channels_u16 = u16::try_from(channels)
.map_err(|_| bad(format!("audio file declares {channels} channels, too many")))?;
let frames = samples.len() / channels;
let at_rate = |ns: u64| {
usize::try_from((u128::from(ns) * u128::from(sample_rate) + 500_000_000) / 1_000_000_000)
.unwrap_or(usize::MAX)
};
let mkv = trims.matroska.unwrap_or_default();
let padding = padding.saturating_add(at_rate(mkv.discard_ns));
let delay = if opus {
delay
} else if vorbis {
delay.max(first_packet.unwrap_or(0))
} else {
delay.max(at_rate(mkv.codec_delay_ns))
};
let (skip, keep) = match trims.edit {
Some(edit) if !opus => (
edit.skip_frames(sample_rate),
edit.keep_frames(sample_rate).unwrap_or(frames),
),
_ => (delay, frames.saturating_sub(delay + padding)),
};
let from = skip.min(frames);
let to = from.saturating_add(keep).min(frames);
samples.truncate(to * channels);
samples.drain(..from * channels);
if samples.is_empty() {
return Err(bad("decoded audio is empty".to_string()));
}
Ok(WavData {
samples,
sample_rate,
channels: channels_u16,
layout: layout.and_then(|(mask, _)| mask),
})
}