///|
/// Strict is the default. Compatible recovers only the explicitly documented
/// EOF, reserved-emphasis, initial-history and channel-transition cases.
pub(all) enum DecodeMode {
Strict
Compatible
} derive(Eq, Show, Debug)
///|
/// Offsets refer to the original compressed input. A skipped history frame
/// contributes main data to the reservoir but produces no PCM.
pub(all) enum Recovery {
TruncatedTail(offset~ : Int64, discarded_bytes~ : Int)
ReservedEmphasis(offset~ : Int64, value~ : Int)
MissingHistory(
offset~ : Int64,
frame_bytes~ : Int,
required~ : Int,
available~ : Int
)
ChannelChange(offset~ : Int64, previous~ : Int, current~ : Int)
} derive(Eq, Show, Debug)
///|
/// Whole-input result preserving each frame's format without channel conversion.
/// Samples and recovery records belong to the caller.
pub struct DecodedStream {
frames : Array[PcmFrame]
recoveries : Array[Recovery]
} derive(Show, Debug)
///|
/// Collect frames, including a mixed-channel stream in Compatible mode.
/// The output limit counts all interleaved samples across all frames.
pub fn decode_frames(
data : Bytes,
mode? : DecodeMode = Strict,
limits? : Limits = Limits::default(),
) -> DecodedStream raise Mp3Error {
let recoveries : Array[Recovery] = []
let decoder = Decoder::new(mode~, limits~, on_recovery=event => {
recoveries.push(event)
})
let frames : Array[PcmFrame] = []
let mut position = 0
let mut samples = 0
while true {
position += decoder.push(data, offset=position)
if position == data.length() {
decoder.finish_input()
}
while true {
match decoder.next_frame() {
NeedMoreInput => break
EndOfInput => {
if frames.is_empty() {
raise NoAudio
}
return { frames, recoveries, }
}
Frame(frame) => {
if frame.samples.length() > limits.max_output_samples - samples {
raise OutputLimit
}
samples += frame.samples.length()
frames.push(frame)
}
}
}
} nobreak {
raise NoAudio
}
}