///|
/// STREAMINFO MD5 validation at the end of a complete stream.
pub(all) enum Integrity {
Md5Verified
Md5Unavailable
FrameCrcOnly
} derive(Show, Debug, Eq)
///|
/// Pull one event at a time; PCM arrays belong to the caller.
pub(all) enum DecodeEvent {
MetadataReady(Metadata)
AudioFrame(Frame)
LinkEnd(Integrity)
NeedInput
Done(Integrity)
}
///|
/// An incremental decoder. push accepts a prefix; drain next before retrying
/// the remainder. finish declares EOF, then next validates length and MD5.
struct Decoder {
mut pending : Bytes
incoming : Array[Byte]
mut retry_at : Int32
mut offset : Int64
mut metadata : Metadata?
mut ended : Bool
mut completed : Integrity?
mut failed : FlacError?
mut samples : Int64
mut frame_count : Int64
mut strategy : Bool?
mut short_block : Bool
mut first_block : Int32
mut previous_block : Int32
mut properties_changed : Bool
mut digest : Md5
limit : Int32
mut peak : Int32
mut container : Int32
mut ogg : OggDemux?
}
///|
/// max_buffer_bytes limits retained compressed native bytes; Ogg also bounds
/// its partial packet to this value. A caller can raise it for large metadata.
pub fn Decoder::new(max_buffer_bytes~ : Int32 = 33554432) -> Decoder {
{
pending: b"",
incoming: [],
retry_at: 0,
offset: 0L,
metadata: None,
ended: false,
completed: None,
failed: None,
samples: 0L,
frame_count: 0L,
strategy: None,
short_block: false,
first_block: 0,
previous_block: 0,
properties_changed: false,
digest: Md5::new(),
limit: max_buffer_bytes,
peak: 0,
container: 0,
ogg: None,
}
}
///|
/// Current retained compressed native bytes (not output PCM).
pub fn Decoder::buffered_bytes(self : Decoder) -> Int32 {
self.pending.length() + self.incoming.length()
}
///|
/// Peak retained native compressed bytes, for resource accounting.
pub fn Decoder::peak_buffered_bytes(self : Decoder) -> Int32 {
self.peak
}
///|
/// Native compressed staging plus the Ogg page and partial packet, if present.
pub fn Decoder::compressed_storage_bytes(self : Decoder) -> Int64 {
let ogg_bytes = match self.ogg {
Some(ogg) => ogg.buffered_bytes()
None => 0
}
self.buffered_bytes().to_int64() + ogg_bytes.to_int64()
}
///|
/// Conservative upper bound on peak retained compressed bytes. Independent
/// native and Ogg peaks can occur at different times; PCM is caller-owned.
pub fn Decoder::peak_compressed_storage_bytes(self : Decoder) -> Int64 {
let ogg_bytes = match self.ogg {
Some(ogg) => ogg.peak_buffered_bytes()
None => 0
}
self.peak.to_int64() + ogg_bytes.to_int64()
}
///|
/// Supply input and return the number of bytes accepted. Input is copied.
pub fn Decoder::push(self : Decoder, input : Bytes) -> Int32 raise FlacError {
self.push_input(input) catch {
error => {
self.failed = Some(error)
raise error
}
}
}
///|
fn Decoder::push_input(self : Decoder, input : Bytes) -> Int32 raise FlacError {
if self.failed is Some(error) {
raise error
}
if self.ended {
raise Invalid(self.offset, "input supplied after finish")
}
if self.limit < 4 {
raise Limit("compressed buffer limit must be at least four")
}
let room = self.limit - self.buffered_bytes()
if room <= 0 {
raise Limit("compressed buffer full; drain frames or raise limit")
}
let capacity = if room < 16384 { room } else { 16384 }
let count = if input.length() < capacity { input.length() } else { capacity }
if count == 0 {
return 0
}
let bytes = input[:count].to_owned()
if self.container == 2 {
if self.ogg is Some(ogg) {
for packet in ogg.push(bytes) {
self.append_native(packet)
}
}
} else {
self.append_native(bytes)
if self.container == 0 && self.buffered_bytes() >= 4 {
self.flush_input()
if self.pending[:4] == b"OggS" {
let ogg = OggDemux::new(max_packet_bytes=self.limit)
self.ogg = Some(ogg)
self.container = 2
let initial = self.pending
self.pending = b""
for packet in ogg.push(initial) {
self.append_native(packet)
}
} else {
self.container = 1
}
}
}
count
}
///|
fn Decoder::append_native(
self : Decoder,
bytes : Bytes,
) -> Unit raise FlacError {
if bytes.length() > self.limit - self.buffered_bytes() {
raise Limit("compressed buffer limit exceeded")
}
for byte in bytes {
self.incoming.push(byte)
}
if self.buffered_bytes() > self.peak {
self.peak = self.buffered_bytes()
}
}
///|
/// Declare end of input. Call next until Done or an explicit error.
pub fn Decoder::finish(self : Decoder) -> Unit raise FlacError {
if self.failed is Some(error) {
raise error
}
if !self.ended {
if self.ogg is Some(ogg) {
ogg.finish() catch {
error => {
self.failed = Some(error)
raise error
}
}
}
self.ended = true
}
}
///|
/// Errors are terminal and repeated on later calls.
pub fn Decoder::next(self : Decoder) -> DecodeEvent raise FlacError {
if self.failed is Some(error) {
raise error
}
if !self.ended && self.buffered_bytes() < self.retry_at {
return NeedInput
}
self.flush_input()
let event = self.read_event() catch {
NeedMore => {
if self.ended {
let error = Invalid(
self.offset + self.pending.length().to_int64(),
"truncated FLAC stream",
)
self.failed = Some(error)
raise error
}
let size = self.pending.length()
self.retry_at = if size == 0 {
1
} else if size > self.limit / 2 {
self.limit
} else {
size * 2
}
return NeedInput
}
error => {
self.failed = Some(error)
raise error
}
}
self.retry_at = 0
event
}
///|
/// Materialize only when a parse attempt is due. Geometric retry thresholds
/// bound re-reading under one-byte input; finish forces the final attempt.
fn Decoder::flush_input(self : Decoder) -> Unit {
if !self.incoming.is_empty() {
self.pending = self.pending + Bytes::from_array(self.incoming)
self.incoming.clear()
}
}
///|
fn Decoder::read_event(self : Decoder) -> DecodeEvent raise FlacError {
if self.completed is Some(integrity) {
return Done(integrity)
}
let metadata = match self.metadata {
Some(value) => value
None => {
let (metadata, consumed) = parse_metadata(
self.pending,
max_metadata_bytes=self.limit,
)
self.pending = self.pending[consumed:].to_owned()
self.offset += consumed.to_int64()
self.metadata = Some(metadata)
return MetadataReady(metadata)
}
}
if self.container == 2 && !self.pending.is_empty() && self.pending[0] == 102 {
if self.pending.length() < 4 {
raise NeedMore
}
if self.pending[:4] == b"fLaC" {
let integrity = self.verify_end(metadata)
self.metadata = None
self.samples = 0L
self.frame_count = 0L
self.strategy = None
self.short_block = false
self.first_block = 0
self.previous_block = 0
self.properties_changed = false
self.digest = Md5::new()
return LinkEnd(integrity)
}
}
if self.pending.is_empty() {
if !self.ended {
return NeedInput
}
let integrity = self.verify_end(metadata)
self.completed = Some(integrity)
return Done(integrity)
}
let (frame, consumed) = parse_frame(
self.pending,
0,
metadata.info,
self.offset,
)
if frame.block_size > metadata.info.max_block_size {
raise Invalid(self.offset, "block exceeds STREAMINFO maximum")
}
let different_properties = frame.channels.length() != metadata.info.channels ||
frame.bits_per_sample != metadata.info.bits_per_sample ||
frame.sample_rate != metadata.info.sample_rate
if self.frame_count > 0L && different_properties {
self.properties_changed = true
}
// Changing-format streams may retain the original format's byte-size hints
// (Appendix C.8; public uncommon 01-04). Buffers never depend on these hints.
if !self.properties_changed &&
(
(
metadata.info.max_frame_size != 0 &&
consumed > metadata.info.max_frame_size
) ||
(
metadata.info.min_frame_size != 0 &&
consumed < metadata.info.min_frame_size
)
) {
raise Invalid(self.offset, "frame size outside STREAMINFO range")
}
if self.frame_count == 0L && different_properties {
raise Invalid(
self.offset,
"initial frame properties disagree with STREAMINFO",
)
}
if self.frame_count > 0L && self.previous_block < metadata.info.min_block_size {
raise Invalid(
self.offset,
"block below STREAMINFO minimum is only valid as final frame",
)
}
if self.strategy is Some(strategy) {
if strategy != frame.variable_block {
raise Invalid(self.offset, "blocking strategy changed")
}
} else {
self.strategy = Some(frame.variable_block)
self.first_block = frame.block_size
}
if frame.variable_block {
if frame.number != self.samples {
raise Invalid(self.offset, "noncontiguous sample number")
}
} else {
if frame.number != self.frame_count {
raise Invalid(self.offset, "noncontiguous frame number")
}
if self.short_block || frame.block_size > self.first_block {
raise Invalid(self.offset, "invalid fixed-block sequence")
}
if frame.block_size < self.first_block {
self.short_block = true
}
}
let frame = { ..frame, sample_number: self.samples, }
self.samples += frame.block_size.to_int64()
if metadata.info.total_samples != 0L &&
self.samples > metadata.info.total_samples {
raise Invalid(self.offset, "decoded samples exceed STREAMINFO")
}
self.frame_count += 1L
self.previous_block = frame.block_size
md5_update_frame(self.digest, frame)
self.pending = self.pending[consumed:].to_owned()
self.offset += consumed.to_int64()
AudioFrame(frame)
}
///|
/// Whole-file convenience API. Frame buffers are owned by the returned value.
pub(all) struct Audio {
metadata : Metadata
frames : Array[Frame]
integrity : Integrity
}
///|
fn Decoder::verify_end(
self : Decoder,
metadata : Metadata,
) -> Integrity raise FlacError {
if self.frame_count == 0L {
raise Invalid(self.offset, "FLAC stream contains no audio frame")
}
if metadata.info.total_samples != 0L &&
metadata.info.total_samples != self.samples {
raise Invalid(self.offset, "sample count does not match STREAMINFO")
}
let mut available = false
for byte in metadata.info.md5 {
if byte != 0 {
available = true
}
}
if available {
if self.digest.digest() != metadata.info.md5 {
raise Invalid(self.offset, "STREAMINFO MD5 mismatch")
}
Md5Verified
} else {
Md5Unavailable
}
}
///|
/// Decode native FLAC or Ogg FLAC with complete integrity validation.
pub fn decode(data : Bytes) -> Audio raise FlacError {
let links = decode_chained(data)
if links.length() != 1 {
raise Invalid(0L, "multiple Ogg links; use decode_chained")
}
links[0]
}
///|
/// Decode every logical FLAC link of an Ogg chain, or one native stream.
pub fn decode_chained(data : Bytes) -> Array[Audio] raise FlacError {
let decoder = Decoder::new()
let mut frames = []
let links = []
let mut metadata = None
let mut cursor = 0
while true {
match decoder.next() {
MetadataReady(value) => metadata = Some(value)
AudioFrame(frame) => frames.push(frame)
LinkEnd(integrity) => {
match metadata {
Some(metadata) => links.push({ metadata, frames, integrity, })
None => raise Invalid(0L, "missing link metadata")
}
metadata = None
frames = []
}
Done(integrity) =>
match metadata {
Some(metadata) => {
links.push({ metadata, frames, integrity, })
return links
}
None => raise Invalid(0L, "missing STREAMINFO")
}
NeedInput =>
if cursor < data.length() {
let end = if data.length() - cursor > 16384 {
cursor + 16384
} else {
data.length()
}
cursor += decoder.push(data[cursor:end].to_owned())
} else {
decoder.finish()
}
}
} nobreak {
raise Invalid(0L, "decoder ended without result")
}
}