Files
craft-libs/crates/audio/src/alac/decoder.rs
T
Aleksander HelgakerandDylan M. Taylor 7e40e12989 craft-audio: ALAC decoder and encoder (#731)
* craft-audio: ALAC decoder and encoder, ported from Apple's reference

Add `craft_audio::alac`, a Rust port of Apple's ALAC reference
implementation (macosforge/alac@c38887c, Apache-2.0):

- `alac::Decoder`: packets to planar i32, bit depths 1-32, 1-8 channels,
  cookies in MP4/Matroska (24-byte config), CAF and old QuickTime form.
  Hostile input returns errors; arithmetic wraps where C would overflow.
- `alac::Encoder`: 16/20/24/32 bits, 1-8 channels, fast mode, any frame
  length; same packets as the reference for the same input.
- `SpecificConfig` (cookie parse/write), `wav_order`/`alac_order`,
  `channel_mask`.
- Features `alac-decode` (in `decode-native`) and `alac-encode` (in
  `encode`).

`TrackDecoder` decodes ALAC natively, so ALAC in MP4 and Matroska no
longer goes through symphonia (CAF still does).

Apple's reference writes the cookie's channel layout tag in host byte
order; this writes it big-endian as its cookie description specifies and
reads either.

Tests: unit tests for each part, an MP4 decode-and-seek test without
symphonia, and ffmpeg oracles both ways (bit-exact, 1-8 channels).
NOTICE and each file's header record the Apache-2.0 origin.

* craft-audio: ALAC review fixes (#731)

- Decode the full-width residual escape 0xffffffff as i32::MIN; the
  reference's `nd + 1` wraps it to 0.
- Require a whole element tag after the last channel (the end tag, after
  any fill or data stream elements), so a packet that lost its last byte
  is an error instead of decoding without damage. Other elements there
  end the packet, as in the reference.
- Refuse an ALAC track with no sample rate in its cookie or container.
- Conceal a damaged packet with the length its header gives
  (`alac::Decoder::packet_frames`), falling back to the last packet's.
- Matroska: place ALAC packet starts at multiples of the frame length
  instead of the rounded block timestamps, so seeks land on the exact
  frame. MP4 keeps its exact sample-table timing.

* craft-audio: exact Matroska ALAC seeks after a short packet (#731)

Packet starts were multiples of the frame length, so a short packet
before the end moved every later packet and seeks after it returned
the wrong frames. When a packet's timestamp is further from that
multiple than its rounding allows, the starts are now counted from the
earlier packets' headers (read once and kept). Other codecs with a
fixed packet length fall back to the timestamp, as before.

---------

Co-authored-by: Dylan M. Taylor <dylan@dylanmtaylor.com>
2026-11-11 08:18:09 -05:00

389 lines
16 KiB
Rust

// Ported from Apple's ALAC reference implementation (ALACDecoder.cpp, macosforge/alac@c38887c,
// https://github.com/macosforge/alac). Copyright (c) 2011 Apple Inc.
// Licensed under the Apache License, Version 2.0 (LICENSE-APACHE at the repository root).
// Modified: translated to Rust and changed as the `alac` module docs describe.
// SPDX-License-Identifier: Apache-2.0
//! The packet decoder, from Apple's `ALACDecoder.cpp`.
use super::ag::{self, AgParams};
use super::bits::BitReader;
use super::dp::{unpc_block, unpc_first_order_in_place};
use super::matrix::{Mix, unmix, unshift};
use super::{Error, ID_CCE, ID_CPE, ID_DSE, ID_END, ID_FIL, ID_LFE, ID_PCE, ID_SCE, MAX_CHANNELS, MAX_FRAME_LENGTH, Result, SpecificConfig};
/// Decodes ALAC packets to planar `i32` (see the [module docs](super) for the sample format).
///
/// Packets decode independently of each other: there is no state to reset after a seek.
#[derive(Debug, Clone)]
pub struct Decoder {
config: SpecificConfig,
}
/// One channel's prediction parameters from an element header.
struct ChannelParams {
mode: u32,
den_shift: u32,
pb_factor: u32,
coefs: [i16; 32],
num_coefs: usize,
}
impl Decoder {
/// A decoder for the stream `cookie` (the magic cookie, [`SpecificConfig::parse`]) describes.
pub fn new(cookie: &[u8]) -> Result<Self> {
Self::from_config(SpecificConfig::parse(cookie)?)
}
/// A decoder for `config`.
pub fn from_config(config: SpecificConfig) -> Result<Self> {
if config.compatible_version > 0 {
return Err(Error::Unsupported("a compatible version above 0"));
}
if !(1..=32).contains(&config.bit_depth) {
return Err(Error::InvalidConfig("bit depth outside 1 to 32"));
}
if config.channels == 0 || config.channels > MAX_CHANNELS {
return Err(Error::InvalidConfig("channel count outside 1 to 8"));
}
if config.frame_length == 0 || config.frame_length > MAX_FRAME_LENGTH {
return Err(Error::InvalidConfig("frame length outside 1 to 2^20"));
}
Ok(Self { config })
}
/// The stream configuration.
pub fn config(&self) -> &SpecificConfig {
&self.config
}
/// Channel count.
pub fn channels(&self) -> usize {
usize::from(self.config.channels)
}
/// Sample rate from the cookie.
pub fn sample_rate(&self) -> u32 {
self.config.sample_rate
}
/// Bits per sample.
pub fn bit_depth(&self) -> u32 {
u32::from(self.config.bit_depth)
}
/// The frames `packet` holds, from its first element's header alone (for concealing a packet
/// that fails to decode): the partial-frame count when the header has one, else the frame
/// length. `None` when the packet does not start with an audio element or its header is cut
/// short or out of range.
pub fn packet_frames(&self, packet: &[u8]) -> Option<usize> {
header_frames(packet, u64::from(self.config.frame_length)).map(|n| n as usize)
}
/// Decode one packet into one `Vec` per channel, in ALAC channel order. Channels the packet
/// has no element for are silent, as in the reference decoder.
///
/// After the last channel the packet must hold a whole element tag (the end tag, after any
/// fill and data stream elements): a packet cut short after its samples is an error. Other
/// elements there end the packet, as in the reference decoder.
pub fn decode(&mut self, packet: &[u8]) -> Result<Vec<Vec<i32>>> {
let channels = self.channels();
let mut out: Vec<Vec<i32>> = vec![Vec::new(); channels];
let mut r = BitReader::new(packet);
let mut num_samples = self.config.frame_length as usize;
// the sample count of the elements decoded so far
let mut frames: Option<usize> = None;
let mut index = 0;
// whether the packet ended (or stopped as the reference stops) before the last channel
let mut stopped = false;
while index < channels {
if r.position() / 8 >= packet.len() {
return Err(Error::Eof);
}
match r.read(3)? {
tag @ (ID_SCE | ID_LFE | ID_CPE) => {
let pair = tag == ID_CPE;
if pair && index + 2 > channels {
// a pair past the channel count: the reference stops here
stopped = true;
break;
}
let (a, b) = self.element(&mut r, pair, &mut num_samples)?;
if frames.is_some_and(|f| f != a.len()) {
return Err(Error::Bitstream("the elements of a frame differ in length"));
}
frames = Some(a.len());
out[index] = a;
index += 1;
if let Some(b) = b {
out[index] = b;
index += 1;
}
}
ID_CCE | ID_PCE => return Err(Error::Unsupported("coupling channel or program config element")),
ID_DSE => data_stream_element(&mut r)?,
ID_FIL => fill_element(&mut r)?,
ID_END => {
r.byte_align();
stopped = true;
break;
}
_ => return Err(Error::Bitstream("element tag")),
}
}
if !stopped {
trailer(&mut r)?;
}
let frames = frames.unwrap_or(0);
for ch in out.iter_mut().filter(|c| c.len() != frames) {
*ch = vec![0; frames];
}
Ok(out)
}
/// One SCE/LFE (`pair` false) or CPE element after its tag: the channel's (or both channels')
/// samples. A partial-frame header changes `num_samples` for this and later elements.
fn element(&self, r: &mut BitReader, pair: bool, num_samples: &mut usize) -> Result<(Vec<i32>, Option<Vec<i32>>)> {
let cfg = &self.config;
let bit_depth = u32::from(cfg.bit_depth);
let _instance = r.read(4)?;
if r.read(12)? != 0 {
return Err(Error::Bitstream("unused element header bits are set"));
}
let header = r.read(4)?;
let partial = header >> 3 != 0;
let mut bytes_shifted = (header >> 1) & 3;
if bytes_shifted == 3 {
return Err(Error::Bitstream("three shifted-off bytes"));
}
let escape = header & 1 != 0;
if partial {
let n = (r.read(16)? << 16) | r.read(16)?;
if n > cfg.frame_length {
return Err(Error::Bitstream("a partial frame longer than the frame length"));
}
*num_samples = n as usize;
}
let n = *num_samples;
let nch = if pair { 2 } else { 1 };
// the prediction (or escaped sample) width
let chan_bits =
|bits: i64| u32::try_from(bits).ok().filter(|b| (1..=32).contains(b)).ok_or(Error::Bitstream("sample width outside 1 to 32 bits"));
let mut mixed = [vec![0i32; n], if pair { vec![0i32; n] } else { Vec::new() }];
let mut shift_buf: Vec<u16> = Vec::new();
let mut mix = Mix { bits: 0, res: 0 };
if !escape {
mix = Mix { bits: r.read(8)?, res: i32::from(r.read(8)? as u8 as i8) };
let mut params = Vec::with_capacity(nch);
for _ in 0..nch {
let a = r.read(8)?;
let b = r.read(8)?;
let mut p = ChannelParams { mode: a >> 4, den_shift: a & 0xF, pb_factor: b >> 5, coefs: [0; 32], num_coefs: (b & 0x1F) as usize };
for c in &mut p.coefs[..p.num_coefs] {
*c = r.read(16)? as u16 as i16;
}
params.push(p);
}
let bits = chan_bits(i64::from(bit_depth) - 8 * i64::from(bytes_shifted) + i64::from(pair))?;
// the shifted-off low bytes come before the residuals; read them after
let shift_at = r.position();
if bytes_shifted != 0 {
r.skip((bytes_shifted as usize * 8).saturating_mul(nch).saturating_mul(n))?;
}
let mut pred = vec![0i32; n];
for (p, out) in params.iter_mut().zip(mixed.iter_mut()) {
let pb = u32::from(cfg.pb) * p.pb_factor / 4;
ag::decompress(AgParams::new(u32::from(cfg.mb), pb, u32::from(cfg.kb)), r, &mut pred, bits)?;
if p.mode != 0 {
unpc_first_order_in_place(&mut pred, n, bits);
}
unpc_block(&pred, out, n, &mut p.coefs, p.num_coefs, bits, p.den_shift);
}
if bytes_shifted != 0 {
let mut sr = *r;
sr.set_position(shift_at)?;
shift_buf = Vec::with_capacity(n * nch);
for _ in 0..n * nch {
shift_buf.push(sr.read(bytes_shifted * 8)? as u16);
}
}
} else {
// uncompressed: a pair's samples are always full width
let bits = chan_bits(if pair { i64::from(bit_depth) } else { i64::from(bit_depth) - 8 * i64::from(bytes_shifted) })?;
for i in 0..n {
for ch in mixed.iter_mut().take(nch) {
ch[i] = r.read_signed(bits)?;
}
}
bytes_shifted = 0;
}
let [u, v] = mixed;
if pair {
let (mut l, mut rr) = (vec![0i32; n], vec![0i32; n]);
unmix((&u, &v), (&mut l, &mut rr), mix, &shift_buf, bytes_shifted, bit_depth);
Ok((l, Some(rr)))
} else {
let mut out = vec![0i32; n];
unshift(&u, &shift_buf, &mut out, n, bytes_shifted, bit_depth);
Ok((out, None))
}
}
}
/// What follows the last channel: fill and data stream elements, then the end tag. The
/// reference decoder stops after the last channel; this needs the next tag to be whole, so a
/// packet missing its last byte is found, and like the reference ignores any other element.
fn trailer(r: &mut BitReader) -> Result<()> {
loop {
match r.read(3)? {
ID_DSE => data_stream_element(r)?,
ID_FIL => fill_element(r)?,
_ => return Ok(()),
}
}
}
/// Skip a fill element (`FillElement`): a 4-bit count, 15 meaning 14 plus an 8-bit count.
fn fill_element(r: &mut BitReader) -> Result<()> {
let mut count = r.read(4)? as usize;
if count == 15 {
count = (count + r.read(8)? as usize).saturating_sub(1);
}
r.skip(count * 8)
}
/// Skip a data stream element (`DataStreamElement`).
fn data_stream_element(r: &mut BitReader) -> Result<()> {
let _instance = r.read(4)?;
let align = r.read(1)? != 0;
let mut count = r.read(8)? as usize;
if count == 255 {
count += r.read(8)? as usize;
}
if align {
r.byte_align();
}
r.skip(count * 8)
}
/// [`Decoder::packet_frames`] for a stream of `frame_length` frames a packet.
pub(crate) fn header_frames(packet: &[u8], frame_length: u64) -> Option<u64> {
let mut r = BitReader::new(packet);
if !matches!(r.read(3).ok()?, ID_SCE | ID_LFE | ID_CPE) {
return None;
}
let _instance_and_unused = r.read(16).ok()?;
let flags = r.read(4).ok()?;
let frames = if flags & 0b1000 != 0 { u64::from(r.read(32).ok()?) } else { frame_length };
(frames <= frame_length).then_some(frames)
}
#[cfg(test)]
mod tests {
use super::*;
fn stereo16() -> Vec<u8> {
SpecificConfig::new(44_100, 2, 16, 4096).to_cookie()
}
#[test]
fn cookies_out_of_range_are_refused() {
let ok = SpecificConfig::new(44_100, 2, 16, 4096);
for bad in [
SpecificConfig { compatible_version: 1, ..ok },
SpecificConfig { bit_depth: 0, ..ok },
SpecificConfig { bit_depth: 33, ..ok },
SpecificConfig { channels: 0, ..ok },
SpecificConfig { channels: 9, ..ok },
SpecificConfig { frame_length: 0, ..ok },
SpecificConfig { frame_length: MAX_FRAME_LENGTH + 1, ..ok },
] {
assert!(Decoder::from_config(bad).is_err(), "{bad:?}");
}
}
#[test]
fn an_escaped_stereo_frame() {
// CPE, instance 0, 12 zero bits, flags: partial + escape, 2 frames, then L R L R.
let mut w = super::super::bits::BitWriter::new();
use super::super::bits::BitSink;
w.put(ID_CPE, 3);
w.put(0, 4);
w.put(0, 12);
w.put(0b1001, 4);
w.put(2, 32);
for s in [1i32, -1, 32767, -32768] {
w.put(s as u32, 16);
}
w.put(ID_END, 3);
let mut dec = Decoder::new(&stereo16()).unwrap();
assert_eq!(dec.decode(&w.into_bytes()).unwrap(), vec![vec![1, 32767], vec![-1, -32768]]);
}
/// An escaped mono packet that lost its last byte keeps every sample but not its end tag.
#[test]
fn a_packet_without_its_end_tag_is_an_error() {
use super::super::bits::{BitSink, BitWriter};
let mut w = BitWriter::new();
for (value, bits) in [(ID_SCE, 3), (0, 4), (0, 12), (0b1001, 4), (3, 32)] {
w.put(value, bits);
}
for s in [5u32, 6, 7] {
w.put(s, 16);
}
w.put(ID_END, 3);
let packet = w.into_bytes();
let mut dec = Decoder::new(&SpecificConfig::new(44_100, 1, 16, 4096).to_cookie()).unwrap();
assert_eq!(dec.decode(&packet).unwrap(), vec![vec![5, 6, 7]]);
assert_eq!(dec.decode(&packet[..packet.len() - 1]), Err(Error::Eof));
// a fill element before the end tag is skipped
let mut w = BitWriter::new();
for (value, bits) in [(ID_SCE, 3), (0, 4), (0, 12), (0b1001, 4), (1, 32), (9, 16), (ID_FIL, 3), (1, 4), (0xA5, 8), (ID_END, 3)] {
w.put(value, bits);
}
assert_eq!(dec.decode(&w.into_bytes()).unwrap(), vec![vec![9]]);
}
#[test]
fn the_frames_of_a_packet_come_from_its_header() {
use super::super::bits::{BitSink, BitWriter};
let dec = Decoder::new(&SpecificConfig::new(44_100, 2, 16, 4096).to_cookie()).unwrap();
let header = |flags: u32, frames: Option<u32>| {
let mut w = BitWriter::new();
for (value, bits) in [(ID_CPE, 3), (0, 4), (0, 12), (flags, 4)] {
w.put(value, bits);
}
if let Some(n) = frames {
w.put(n, 32);
}
w.into_bytes()
};
assert_eq!(dec.packet_frames(&header(0b1001, Some(300))), Some(300));
assert_eq!(dec.packet_frames(&header(0b0001, None)), Some(4096));
assert_eq!(dec.packet_frames(&header(0b1001, Some(5000))), None);
assert_eq!(dec.packet_frames(&header(0b1001, None)), None);
assert_eq!(dec.packet_frames(&[0xE0]), None); // ID_END first
}
#[test]
fn hostile_packets_fail_or_decode_without_panicking() {
let mut dec = Decoder::new(&SpecificConfig::new(48_000, 8, 24, 4096).to_cookie()).unwrap();
let mut seed = 0x1234_5678u32;
for len in 0..400 {
let packet: Vec<u8> = (0..len)
.map(|_| {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
seed as u8
})
.collect();
if let Ok(chans) = dec.decode(&packet) {
assert_eq!(chans.len(), 8);
assert!(chans.iter().all(|c| c.len() == chans[0].len() && c.len() <= 4096));
}
}
assert_eq!(dec.decode(&[]), Err(Error::Eof));
}
}