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>
This commit is contained in:
Aleksander HelgakerandDylan M. Taylor authored and GitHub committed 2026-11-11 08:18:09 -05:00
1 parent ca53e90b18
commit 7e40e12989
17 files changed
+2663 -53

No files matched your search

+7
View File
@@ -252,3 +252,10 @@ Its white-level plateau estimate (saturation_plateau) is LightCraft's vendor::wh
same revision. The CRW compression was derived clean-room from CC0 sample files and the
maintainer's own photographs by black-box comparison with a reference decoder's command-line
output; no raw-decoder source was consulted (crates/crw/clean-room/sources.md).
crates/audio/src/alac/ (the ALAC decoder and encoder) is ported from Apple's ALAC reference
implementation, https://github.com/macosforge/alac at c38887c5c5e64a4b31108733bd79ca9b2496d987
(codec/), Copyright (c) 2011 Apple Inc., licensed under the Apache License, Version 2.0
(LICENSE-APACHE). The files are modified: translated to Rust, with bounds checks on hostile input,
wrapping integer arithmetic, the unrolled predictor and per-width matrix routines folded into one,
and the average bit rate filled in. Those files are Apache-2.0 only.
+9 -6
View File
@@ -1,6 +1,6 @@
[package]
name = "craft-audio"
description = "Audio file I/O and codecs: WAV/AIFF read and write, FLAC encoder, AAC decoder and encoder, Opus, AC-3/E-AC-3 and MP1/MP2 decoders."
description = "Audio file I/O and codecs: WAV/AIFF read and write, FLAC encoder, AAC and ALAC decoders and encoders, Opus, AC-3/E-AC-3 and MP1/MP2 decoders."
version.workspace = true
edition.workspace = true
license.workspace = true
@@ -14,16 +14,16 @@ all-features = true
workspace = true
[features]
# Probe and decode files: WAV and AIFF natively; AAC, Opus, AC-3/E-AC-3, MP1/MP2 and PCM through
# craft-media's demuxers and this crate's decoders; MP3, FLAC, ALAC, Vorbis, CAF, ADPCM, A-law
# Probe and decode files: WAV and AIFF natively; AAC, ALAC, Opus, AC-3/E-AC-3, MP1/MP2 and PCM
# through craft-media's demuxers and this crate's decoders; MP3, FLAC, Vorbis, CAF, ADPCM, A-law
# and µ-law through symphonia.
decode = ["decode-native", "symphonia"]
# `decode` without symphonia: only the formats with a native decoder (others are Unsupported).
decode-native = ["aac-decode", "opus-decode", "ac3-decode", "mpa-decode", "craft-media/isobmff", "craft-media/matroska", "craft-media/ogg", "dep:log"]
decode-native = ["aac-decode", "alac-decode", "opus-decode", "ac3-decode", "mpa-decode", "craft-media/isobmff", "craft-media/matroska", "craft-media/ogg", "dep:log"]
# The symphonia fallback for the formats with no native decoder yet.
symphonia = ["decode-native", "dep:symphonia"]
# Every encoder: the lossless file writers and the AAC encoder.
encode = ["lossless-encode", "aac-encode"]
# Every encoder: the lossless file writers, the AAC encoder and the ALAC packet encoder.
encode = ["lossless-encode", "aac-encode", "alac-encode"]
# Encode WAV/BWF/RF64, AIFF/AIFF-C and FLAC files.
lossless-encode = ["pcm-encode", "flac-encode"]
# Encode WAV/BWF/RF64 and AIFF/AIFF-C files.
@@ -36,6 +36,9 @@ aac-encode = ["craft-media/bitstream"]
opus-decode = []
ac3-decode = []
mpa-decode = ["craft-media/bitstream"]
# ALAC packet decoder and encoder (`alac`), ported from Apple's reference implementation.
alac-decode = []
alac-encode = []
# Multi-resolution waveform overviews (`peaks`).
peaks = []
# Encode AAC channels in parallel (with `aac-encode`).
+26 -11
View File
@@ -1,7 +1,7 @@
# craft-audio
Audio file I/O and codecs for the Craft apps. Native implementations: WAV and AIFF read/write,
FLAC encoder, AAC decoder/encoder, Opus, AC-3/E-AC-3 and MP1/MP2 decoders.
FLAC encoder, AAC and ALAC decoders/encoders, Opus, AC-3/E-AC-3 and MP1/MP2 decoders.
Formats without a native decoder are decoded by [symphonia](#symphonia), which is to be replaced
by native decoders.
@@ -11,7 +11,9 @@ by native decoders.
per channel), in WAV channel order (L R C LFE, back, side) whenever the channel layout is known.
- Malformed input returns an error, never a panic (see [symphonia](#symphonia) for the one
exception). No `unsafe`. Readers and decoders are tested with corrupted and truncated input.
- Implemented from the published specifications; tested against ffmpeg and conformance vectors.
- Implemented from the published specifications, except ALAC, which is ported from Apple's
Apache-2.0 reference implementation (see [Origin](#origin)); tested against ffmpeg and
conformance vectors.
Builds for `wasm32-unknown-unknown`.
- Containers (MP4/MOV, Matroska/WebM, Ogg), bit I/O and the `Container`/`CodecId`/`CodecParams`
types come from [`craft-media`](../media/README.md). This crate never depends on video codecs.
@@ -24,13 +26,15 @@ No default features. The shared types (`AudioBuffer`, `AudioInfo`, `ChannelLayou
| Feature | Provides | Pulls in |
|---|---|---|
| `decode` | `decode`, `probe`, `Decoder`, `tracks`, `TrackDecoder`; every format in [Format support](#format-support) | `decode-native`, `symphonia` |
| `decode-native` | the same, native formats only (others are `Unsupported`) | the four decoders below, craft-media `isobmff`/`matroska`/`ogg`, `log` |
| `decode-native` | the same, native formats only (others are `Unsupported`) | the five decoders below, craft-media `isobmff`/`matroska`/`ogg`, `log` |
| `symphonia` | the fallback for formats with no native decoder; turning it on is the same as `decode` | `decode-native`, symphonia |
| `encode` | every encoder: the WAV, AIFF and FLAC file writers, and the AAC packet encoder | `lossless-encode`, `aac-encode` |
| `encode` | every encoder: the WAV, AIFF and FLAC file writers, and the AAC and ALAC packet encoders | `lossless-encode`, `aac-encode`, `alac-encode` |
| `lossless-encode` | WAV, AIFF and FLAC writing | `pcm-encode`, `flac-encode` |
| `pcm-encode` | `encode`, `FileEncoder` for WAV and AIFF; `wav_float_header` | none |
| `flac-encode` | `encode`, `FileEncoder` for FLAC | craft-media `bitstream` |
| `aac-decode`, `aac-encode` | `aac::Decoder`, `aac::Encoder` | craft-media `bitstream` |
| `alac-decode` | `alac::Decoder` | none |
| `alac-encode` | `alac::Encoder` | none |
| `opus-decode` | `opus::Decoder`/`StreamDecoder` | none |
| `ac3-decode` | `ac3::Decoder` | none |
| `mpa-decode` | `mpa::Decoder` | craft-media `bitstream` |
@@ -38,8 +42,8 @@ No default features. The shared types (`AudioBuffer`, `AudioInfo`, `ChannelLayou
| `rayon` | parallel AAC encoding (with `aac-encode`) | `rayon` |
| `serde` | `Serialize`/`Deserialize` on the shared types | `serde` |
Any one decoder feature (`aac-decode`, `opus-decode`, `ac3-decode`, `mpa-decode`) also gives
`TrackDecoder`, for that codec and PCM.
Any one decoder feature (`aac-decode`, `alac-decode`, `opus-decode`, `ac3-decode`, `mpa-decode`)
also gives `TrackDecoder`, for that codec and PCM.
An app that decodes 32-bit FLAC with `decode` needs a profile override in its workspace; see
[symphonia](#symphonia).
@@ -294,15 +298,16 @@ match craft_audio::decode(&bytes, None) {
| FLAC | symphonia | native |
| AAC-LC | native | native |
| HE-AAC v1/v2 | native, core only (see [Limits](#limits)) | — |
| ALAC | native | native, packets (`alac::Encoder`; no file writer) |
| MP1, MP2 (MPEG audio Layer I/II) | native | — |
| MP3 (Layer III) | symphonia | — |
| Opus | native | — |
| AC-3, E-AC-3 | native | — |
| ALAC, Vorbis, ADPCM, A-law, µ-law | symphonia | — |
| Vorbis, ADPCM, A-law, µ-law | symphonia | — |
| Container | Demuxer | Timing |
|---|---|---|
| MP4, M4A, MOV | `craft_media::isobmff` for native codecs; symphonia for the others (ALAC, MP3, FLAC) | edit lists (AAC priming, Opus pre-skip) |
| MP4, M4A, MOV | `craft_media::isobmff` for native codecs; symphonia for the others (MP3, FLAC) | edit lists (AAC priming, Opus pre-skip) |
| Matroska, WebM | `craft_media::matroska` for native codecs; symphonia for the others (Vorbis, FLAC, MP3) | `CodecDelay`, `DiscardPadding` |
| Ogg | `craft_media::ogg` (Opus); symphonia (Vorbis, FLAC) | pre-skip, end trimming |
| Raw ADTS AAC, AC-3, E-AC-3, MP1/MP2 | native frame sync | — |
@@ -352,8 +357,16 @@ match craft_audio::decode(&bytes, None) {
- `ChannelLayout::unordered` is written to FLAC as mask `0x0000`, as RFC 9639 §8.6.2 allows, but
ffmpeg and `flac -d` ignore that mask and use their default layout for the channel count.
- Not decoded at all: Speex.
- `TrackDecoder` returns `Unsupported` for MP3, FLAC, ALAC and Vorbis; `decode` and `Decoder` read
those through symphonia.
- `TrackDecoder` returns `Unsupported` for MP3, FLAC and Vorbis; `decode` and `Decoder` read
those through symphonia. ALAC in CAF still goes through symphonia (craft-media has no CAF
demuxer).
- ALAC: `EncodeFormat` has no ALAC entry; `alac::Encoder` writes packets and the configuration,
and the caller muxes them (MP4 `alac` box, Matroska `A_ALAC`). craft-media's MP4 muxer writes no
`chan` box, so an MP4 of more than two channels carries no explicit layout; decoders then use the
standard ALAC layout for the count.
- ALAC 32-bit streams: ffmpeg does not decode their uncompressed (escaped) stereo frames, which
Apple's encoder and this one write for audio that does not compress; Apple's decoder and this
one do.
- MP4 edit lists: a single media edit (encoder priming and the end) is applied, alone or after one
empty edit; the empty edit's delay is not added as silence. Other edit lists are not applied, and
the media plays as it is stored.
@@ -390,6 +403,7 @@ Each codec module's rustdoc lists the coding tools it implements.
| Opus (`opus`) | RFC 6716, 8251, 7845 | decoder: SILK, hybrid, CELT, all frame sizes, PLC and FEC, multistream (`Decoder`) and single stream (`StreamDecoder`), 8–48 kHz output | all 36 RFC 8251 conformance cases |
| AC-3/E-AC-3 (`ac3`) | ATSC A/52:2012 | all AC-3 channel modes and frame sizes; E-AC-3 independent substream 0 with spectral extension; output in WAV order, no downmix | ffmpeg: 65–94 dB SNR on tones, 27–48 dB on white noise (see [Limits](#limits)) |
| MP1/MP2 (`mpa`) | ISO/IEC 11172-3, 13818-3 | Layer I and II, MPEG-1/2/2.5, 8–48 kHz; also the Layer II rate/mode pairs the spec disallows (ffmpeg's default mono 384 kbit/s); no free format | ffmpeg-encoded streams, 73–84 dB SNR |
| ALAC (`alac`) | Apple's reference implementation | decode and encode, 1–8 channels, 16/20/24/32 bits (decode: any depth 1–32), fast mode, magic cookie in MP4/Matroska and CAF form | Apple's reference: same packets from the encoder, same samples from both decoders on 766 configurations; ffmpeg: bit-exact both ways |
## symphonia
@@ -456,6 +470,7 @@ the full tests with ffmpeg and the vectors required.
|---|---|
| WAV, AIFF, `decode`/`probe`, peaks | SoundCraft `soundcraft-audio-io` [7dc56a4](https://github.com/storytold/soundcraft/commit/7dc56a4dbeebf57eacece09536726c57844fc59d) |
| FLAC encoder, AAC, Opus decoder, AC-3, MP1/MP2, Ogg | FilmCraft [8fda2f2](https://github.com/storytold/filmcraft/commit/8fda2f232198c4c79ff9cfe5b6a347bb1375fa9e) |
| ALAC | Apple's reference implementation [macosforge/alac@c38887c](https://github.com/macosforge/alac/tree/c38887c5c5e64a4b31108733bd79ca9b2496d987/codec), Copyright © 2011 Apple Inc., Apache-2.0; ported to Rust and modified (see the `alac` module docs and [NOTICE](../../NOTICE)) |
Where apps had overlapping implementations, the one that measured better was kept, with the
tests of both:
@@ -502,7 +517,7 @@ The codec modules' public items keep their names, so apps can re-export them
short slices instead of filling them with silence.
- `DecodeOptions` is not `Copy`.
- The FLAC encoder's vendor string is `craft-audio <version>`.
- `decode` tries native decoders first. AAC, Opus, AC-3 and MP2 no longer go through symphonia, except AAC in
- `decode` tries native decoders first. AAC, ALAC, Opus, AC-3 and MP2 no longer go through symphonia, except AAC in
CAF.
MP4 and Matroska files decode to presentation length. A packet that fails is concealed, not
dropped. A failed source read is `AudioError::Io`.
+296
View File
@@ -0,0 +1,296 @@
// Ported from Apple's ALAC reference implementation (aglib.h, ag_dec.c and ag_enc.c, 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
//! Adaptive Golomb-Rice coding of prediction residuals, from Apple's `aglib.h`, `ag_dec.c` and
//! `ag_enc.c`.
//!
//! Arithmetic is `u32`/`i32` with wrapping, as the C code has on the platforms it targets, so
//! hostile parameters give garbage samples rather than a panic. Peeks past the packet read zero
//! bits; [`decompress`] fails when the codes it consumed end past the packet.
use super::bits::{BitReader, BitSink};
use super::{Error, Result};
pub(crate) const QBSHIFT: u32 = 9;
pub(crate) const QB: u32 = 1 << QBSHIFT;
pub(crate) const PB0: u32 = 40;
pub(crate) const MB0: u32 = 10;
pub(crate) const KB0: u32 = 14;
pub(crate) const MAX_RUN_DEFAULT: u32 = 255;
const MMULSHIFT: u32 = 2;
const MDENSHIFT: u32 = QBSHIFT - MMULSHIFT - 1;
const MOFF: u32 = 1 << (MDENSHIFT - 2);
const BITOFF: u32 = 24;
/// Longest prefix of ones before an escaped value.
const MAX_PREFIX_16: u32 = 9;
const MAX_PREFIX_32: u32 = 9;
/// Width of an escaped zero-run length.
const MAX_DATATYPE_BITS_16: u32 = 16;
const N_MAX_MEAN_CLAMP: u32 = 0xffff;
const N_MEAN_CLAMP_VAL: u32 = 0xffff;
/// `AGParamRec`: the coder's tuning (`fw`/`sw`, the image-row widths, are always the sample
/// count for audio and not kept).
#[derive(Clone, Copy, Debug)]
pub(crate) struct AgParams {
mb0: u32,
pb: u32,
kb: u32,
wb: u32,
}
impl AgParams {
/// `set_ag_params` (`maxrun` is unused by the coder).
pub(crate) fn new(mb: u32, pb: u32, kb: u32) -> Self {
let wb = 1u32.checked_shl(kb).unwrap_or(0).wrapping_sub(1);
Self { mb0: mb, pb, kb, wb }
}
/// `set_standard_ag_params`.
pub(crate) fn standard() -> Self {
Self::new(MB0, PB0, KB0)
}
}
/// Leading zero bits of `m` (`lead`).
fn lead(m: u32) -> u32 {
m.leading_zeros()
}
/// `lg3a`: floor(log2(x + 3)).
fn lg3a(x: u32) -> u32 {
31u32.wrapping_sub(lead(x.wrapping_add(3)))
}
/// `(1 << k) - 1`, 0 for `k` of 32 or more.
fn mask(k: u32) -> u32 {
1u32.checked_shl(k).unwrap_or(0).wrapping_sub(1)
}
/// The Rice parameter of the zero-run length that follows when the mean has dropped this low.
fn zero_run_k(mb: u32) -> u32 {
lead(mb).wrapping_sub(BITOFF).wrapping_add(mb.wrapping_add(MOFF) >> MDENSHIFT)
}
/// The next mean, after a value of `n` (`zmode` is 1 right after a zero run).
fn next_mean(pb: u32, mb: u32, n: u32, zmode: u32) -> u32 {
let mb = pb.wrapping_mul(n.wrapping_add(zmode)).wrapping_add(mb).wrapping_sub(pb.wrapping_mul(mb) >> QBSHIFT);
if n > N_MAX_MEAN_CLAMP { N_MEAN_CLAMP_VAL } else { mb }
}
/// `dyn_get`: a zero-run length, coded with parameter `k` (escape: 16 raw bits).
fn get_run(r: &BitReader, pos: &mut usize, m: u32, k: u32) -> u32 {
let pre = lead(!r.peek_at(*pos, 32));
if pre >= MAX_PREFIX_16 {
let v = r.peek_at(*pos + MAX_PREFIX_16 as usize, MAX_DATATYPE_BITS_16);
*pos += (MAX_PREFIX_16 + MAX_DATATYPE_BITS_16) as usize;
v
} else {
let v = r.peek_at(*pos + pre as usize + 1, k);
*pos += (pre + 1 + k) as usize;
let mut result = pre.wrapping_mul(m).wrapping_add(v).wrapping_sub(1);
if v < 2 {
result = result.wrapping_sub(v.wrapping_sub(1));
*pos -= 1;
}
result
}
}
/// `dyn_get_32bit`: one residual magnitude, coded with parameter `k` (escape: `maxbits` raw bits).
fn get_value(r: &BitReader, pos: &mut usize, m: u32, k: u32, maxbits: u32) -> u32 {
let pre = lead(!r.peek_at(*pos, 32));
if pre >= MAX_PREFIX_32 {
let v = r.peek_at(*pos + MAX_PREFIX_32 as usize, maxbits);
*pos += (MAX_PREFIX_32 + maxbits) as usize;
return v;
}
*pos += pre as usize + 1;
if k == 1 {
return pre;
}
let v = r.peek_at(*pos, k);
// k - 1 bits when the suffix is 0 or 1, else k (k is 0 only in a hostile stream, which
// moves back by one; the caller's loop is bounded by the sample count).
*pos = (*pos + k as usize).saturating_sub(1);
let mut result = pre.wrapping_mul(m);
if v >= 2 {
result = result.wrapping_add(v - 1);
*pos += 1;
}
result
}
/// `dyn_decomp`: `out.len()` residuals of up to `max_size` bits from `r`, which is advanced past
/// them.
pub(crate) fn decompress(params: AgParams, r: &mut BitReader, out: &mut [i32], max_size: u32) -> Result<()> {
let num = out.len();
let max_pos = r.len_bits();
let mut pos = r.position();
let mut mb = params.mb0;
let mut zmode = 0u32;
let mut c = 0usize;
while c < num {
if pos >= max_pos {
return Err(Error::Eof);
}
let k = lg3a(mb >> QBSHIFT).min(params.kb);
let m = mask(k);
let n = get_value(r, &mut pos, m, k, max_size);
// the least significant bit is the sign
let nd = n.wrapping_add(zmode);
let multiplier = (nd & 1).wrapping_neg() | 1;
// ceil(nd / 2) without the reference's `nd + 1`, which wraps 0xffffffff (i32::MIN) to 0
out[c] = ((nd >> 1) + (nd & 1)).wrapping_mul(multiplier) as i32;
c += 1;
mb = next_mean(params.pb, mb, n, zmode);
zmode = 0;
if (mb << MMULSHIFT) < QB && c < num {
zmode = 1;
let k = zero_run_k(mb);
let mz = mask(k) & params.wb;
let run = get_run(r, &mut pos, mz, k);
let run_end = c.checked_add(run as usize).filter(|&e| e <= num).ok_or(Error::Bitstream("zero run past the end of the frame"))?;
out[c..run_end].fill(0);
c = run_end;
if run >= 65535 {
zmode = 0;
}
mb = 0;
}
}
r.set_position(pos)
}
/// `dyn_code`: a zero-run length as (value, bits).
fn code_run(m: u32, k: u32, n: u32) -> (u32, u32) {
let escape = || (((1u32 << MAX_PREFIX_16) - 1) << MAX_DATATYPE_BITS_16).wrapping_add(n);
let div = n / m.max(1);
if div >= MAX_PREFIX_16 {
return (escape(), MAX_PREFIX_16 + MAX_DATATYPE_BITS_16);
}
let rem = n % m.max(1);
let de = u32::from(rem == 0);
let bits = div + k + 1 - de;
if bits > MAX_PREFIX_16 + MAX_DATATYPE_BITS_16 {
return (escape(), MAX_PREFIX_16 + MAX_DATATYPE_BITS_16);
}
let value = (mask(div) << (bits - div)).wrapping_add(rem).wrapping_add(1).wrapping_sub(de);
(value, bits)
}
/// `dyn_code_32bit`: a residual magnitude as (value, bits), plus the raw `maxbits`-bit escape
/// that follows a prefix of nine ones.
fn code_value(maxbits: u32, m: u32, k: u32, n: u32) -> (u32, u32, Option<(u32, u32)>) {
let div = n / m.max(1);
if div < MAX_PREFIX_32 {
let rem = n - m * div;
let de = u32::from(rem == 0);
let bits = div + k + 1 - de;
if bits <= 25 {
let value = (mask(div) << (bits - div)).wrapping_add(rem).wrapping_add(1).wrapping_sub(de);
return (value, bits, None);
}
}
(mask(MAX_PREFIX_32), MAX_PREFIX_32, Some((n, maxbits)))
}
/// `dyn_comp`: code the residuals `pc`, each of up to `bit_size` bits, into `out`.
pub(crate) fn compress(params: AgParams, pc: &[i32], out: &mut impl BitSink, bit_size: u32) -> Result<()> {
if !(1..=32).contains(&bit_size) {
return Err(Error::InvalidInput("residual width outside 1 to 32 bits"));
}
let num = pc.len();
let mut mb = params.mb0;
let mut zmode = 0u32;
let mut c = 0usize;
while c < num {
let k = lg3a(mb >> QBSHIFT).min(params.kb);
let m = mask(k);
let del = pc[c];
let n = (del.unsigned_abs() << 1).wrapping_sub(u32::from(del < 0)).wrapping_sub(zmode);
let (value, bits, escape) = code_value(bit_size, m, k, n);
out.put(value, bits);
if let Some((raw, raw_bits)) = escape {
out.put(raw, raw_bits);
}
c += 1;
mb = next_mean(params.pb, mb, n, zmode);
zmode = 0;
if (mb << MMULSHIFT) < QB && c < num {
zmode = 1;
let mut nz = 0u32;
while c < num && pc[c] == 0 {
nz += 1;
c += 1;
if nz >= 65535 {
zmode = 0;
break;
}
}
let k = zero_run_k(mb);
let mz = mask(k) & params.wb;
let (value, bits) = code_run(mz, k, nz);
out.put(value, bits);
mb = 0;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::bits::{BitReader, BitSink, BitWriter};
use super::*;
fn roundtrip(values: &[i32], bits: u32) {
let mut w = BitWriter::new();
compress(AgParams::standard(), values, &mut w, bits).unwrap();
let len = w.bit_len();
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
let mut out = vec![0; values.len()];
decompress(AgParams::standard(), &mut r, &mut out, bits).unwrap();
assert_eq!(out, values);
assert_eq!(r.position(), len);
}
#[test]
fn residuals_roundtrip() {
roundtrip(&[0, 1, -1, 2, -2, 100, -100, 30000, -32768, 32767], 16);
roundtrip(&[0; 300], 16);
let mut v = vec![0; 70_000];
v[0] = 5;
v[69_999] = -3;
roundtrip(&v, 16);
let wide: Vec<i32> = (0..500).map(|i| ((i * 7919) % 65_536 - 32_768) << 8).collect();
roundtrip(&wide, 25);
roundtrip(&[i32::MAX >> 1, i32::MIN >> 1, 0, 0, 0, 1], 32);
}
/// The largest escaped magnitude, 0xffffffff, is i32::MIN (the reference wraps it to 0).
#[test]
fn the_full_width_escape_decodes_to_the_minimum() {
let mut w = BitWriter::new();
w.put(0x1FF, 9); // nine ones: escape
w.put(u32::MAX, 32);
let bytes = w.into_bytes();
let mut out = [7];
decompress(AgParams::standard(), &mut BitReader::new(&bytes), &mut out, 32).unwrap();
assert_eq!(out, [i32::MIN]);
}
#[test]
fn a_truncated_stream_is_an_error() {
let mut w = BitWriter::new();
let values: Vec<i32> = (0..64).map(|i| i * 1000 - 32_000).collect();
compress(AgParams::standard(), &values, &mut w, 16).unwrap();
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes[..bytes.len() / 2]);
let mut out = vec![0; values.len()];
assert!(decompress(AgParams::standard(), &mut r, &mut out, 16).is_err());
}
}
+232
View File
@@ -0,0 +1,232 @@
// Ported from Apple's ALAC reference implementation (ALACBitUtilities.c, 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
//! MSB-first bit reading and writing, from Apple's `ALACBitUtilities.c`.
//!
//! The reader never reads past its slice: [`BitReader::peek`] pads with zero bits, and every
//! read that would end past the data is [`Error::Eof`]. The writer can rewind, as the reference
//! encoder does when a compressed element turns out larger than its escape (uncompressed) form.
use super::{Error, Result};
/// Reader over one packet.
#[derive(Clone, Copy)]
pub(crate) struct BitReader<'a> {
data: &'a [u8],
/// Position in bits from the start of `data`.
pos: usize,
}
impl<'a> BitReader<'a> {
pub(crate) fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
/// Bit position from the start.
pub(crate) fn position(&self) -> usize {
self.pos
}
/// Bits in the packet.
pub(crate) fn len_bits(&self) -> usize {
self.data.len().saturating_mul(8)
}
/// The next `n` (at most 32) bits at bit `pos`, right-aligned, with zeros past the end.
pub(crate) fn peek_at(&self, pos: usize, n: u32) -> u32 {
if n == 0 {
return 0;
}
let byte = pos / 8;
let mut window: u64 = 0;
for i in 0..8 {
window = (window << 8) | u64::from(self.data.get(byte.wrapping_add(i)).copied().unwrap_or(0));
}
// `pos % 8 + n` is at most 39, so the wanted bits lie in the 64-bit window.
let shift = 64 - (pos % 8) as u32 - n.min(32);
((window >> shift) & ((1u64 << n.min(32)) - 1)) as u32
}
/// The next `n` bits without consuming them.
pub(crate) fn peek(&self, n: u32) -> u32 {
self.peek_at(self.pos, n)
}
/// Read `n` (at most 32) bits.
pub(crate) fn read(&mut self, n: u32) -> Result<u32> {
let end = self.pos.checked_add(n as usize).ok_or(Error::Eof)?;
if end > self.len_bits() {
return Err(Error::Eof);
}
let v = self.peek(n);
self.pos = end;
Ok(v)
}
/// Read `n` bits as a two's-complement value of that width.
pub(crate) fn read_signed(&mut self, n: u32) -> Result<i32> {
let v = self.read(n)?;
Ok(sign_extend(v, n))
}
/// Skip `n` bits; skipping past the end is [`Error::Eof`].
pub(crate) fn skip(&mut self, n: usize) -> Result<()> {
let end = self.pos.checked_add(n).ok_or(Error::Eof)?;
if end > self.len_bits() {
return Err(Error::Eof);
}
self.pos = end;
Ok(())
}
/// Move to `pos`, which the caller has checked lies within the packet.
pub(crate) fn set_position(&mut self, pos: usize) -> Result<()> {
if pos > self.len_bits() {
return Err(Error::Eof);
}
self.pos = pos;
Ok(())
}
/// Skip to the next byte boundary (`BitBufferByteAlign` without zeros).
pub(crate) fn byte_align(&mut self) {
// The packet is whole bytes, so the boundary is never past its end.
self.pos = self.pos.div_ceil(8).saturating_mul(8);
}
}
/// The low `n` bits of `v` as a signed value (`n` of 0 gives 0, 32 or more the value itself).
pub(crate) fn sign_extend(v: u32, n: u32) -> i32 {
match n {
0 => 0,
1..=31 => {
let s = 32 - n;
((v << s) as i32) >> s
}
_ => v as i32,
}
}
/// Where bits go: a [`BitWriter`], or a [`BitCounter`] for the encoder's trial runs.
pub(crate) trait BitSink {
/// Write the low `n` (at most 32) bits of `value`.
fn put(&mut self, value: u32, n: u32);
/// Bits written so far.
fn bit_len(&self) -> usize;
}
/// Growable MSB-first writer.
#[derive(Default)]
pub(crate) struct BitWriter {
buf: Vec<u8>,
bits: usize,
}
impl BitWriter {
pub(crate) fn new() -> Self {
Self::default()
}
/// Drop everything after bit `pos` (no-op when `pos` is past the end).
pub(crate) fn truncate(&mut self, pos: usize) {
if pos >= self.bits {
return;
}
self.bits = pos;
self.buf.truncate(pos.div_ceil(8));
if let Some(last) = self.buf.last_mut()
&& !pos.is_multiple_of(8)
{
*last &= 0xFFu8 << (8 - pos % 8);
}
}
/// Pad with zero bits to a byte boundary.
pub(crate) fn byte_align(&mut self) {
let pad = (8 - self.bits % 8) % 8;
self.put(0, pad as u32);
}
pub(crate) fn into_bytes(mut self) -> Vec<u8> {
self.byte_align();
self.buf
}
}
impl BitSink for BitWriter {
fn put(&mut self, value: u32, n: u32) {
let n = n.min(32);
for i in (0..n).rev() {
let bit = ((value >> i) & 1) as u8;
if self.bits.is_multiple_of(8) {
self.buf.push(0);
}
if let Some(last) = self.buf.last_mut() {
*last |= bit << (7 - self.bits % 8);
}
self.bits += 1;
}
}
fn bit_len(&self) -> usize {
self.bits
}
}
/// Counts bits without storing them.
#[derive(Default)]
pub(crate) struct BitCounter(pub(crate) usize);
impl BitSink for BitCounter {
fn put(&mut self, _value: u32, n: u32) {
self.0 += n.min(32) as usize;
}
fn bit_len(&self) -> usize {
self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writer_and_reader_agree() {
let mut w = BitWriter::new();
w.put(0b101, 3);
w.put(0xABCD, 16);
w.put(0xDEAD_BEEF, 32);
w.put(1, 1);
assert_eq!(w.bit_len(), 52);
let bytes = w.into_bytes();
let mut r = BitReader::new(&bytes);
assert_eq!(r.read(3), Ok(0b101));
assert_eq!(r.read(16), Ok(0xABCD));
assert_eq!(r.read(32), Ok(0xDEAD_BEEF));
assert_eq!(r.read(1), Ok(1));
assert_eq!(r.read(4), Ok(0));
assert_eq!(r.read(1), Err(Error::Eof));
assert_eq!(r.peek(32), 0);
}
#[test]
fn truncate_clears_the_dropped_bits() {
let mut w = BitWriter::new();
w.put(0xFFFF, 16);
w.truncate(5);
w.put(0, 3);
assert_eq!(w.into_bytes(), vec![0xF8]);
}
#[test]
fn sign_extension() {
assert_eq!(sign_extend(0xF, 4), -1);
assert_eq!(sign_extend(0x7, 4), 7);
assert_eq!(sign_extend(0x8000_0000, 32), i32::MIN);
assert_eq!(sign_extend(5, 0), 0);
}
}
+388
View File
@@ -0,0 +1,388 @@
// 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));
}
}
+196
View File
@@ -0,0 +1,196 @@
// Ported from Apple's ALAC reference implementation (dplib.h, dp_dec.c and dp_enc.c, 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 adaptive FIR predictor, from Apple's `dplib.h`, `dp_dec.c` and `dp_enc.c`.
//!
//! The reference has unrolled copies for 4 and 8 taps; they compute the same values as its
//! general loop, which is the one ported. Arithmetic wraps as the C code does on its targets.
//! As in the reference, the warm-up (the first `numactive` samples, first differences) runs to
//! `numactive` even in a shorter block, so the encoder's trial runs leave the buffers as the
//! reference leaves them; here it stops at the end of the buffers.
pub(crate) const DENSHIFT_DEFAULT: u32 = 9;
const AINIT: i32 = 38;
const BINIT: i32 = -29;
const CINIT: i32 = -2;
/// Coefficients of the first-difference shortcut (`numactive == 31`).
pub(crate) const FIRST_ORDER: usize = 31;
/// `init_coefs`.
pub(crate) fn init_coefs(coefs: &mut [i16], denshift: u32) {
let den = 1i32 << denshift;
coefs.fill(0);
for (c, init) in coefs.iter_mut().zip([AINIT, BINIT, CINIT]) {
*c = ((init * den) >> 4) as i16;
}
}
/// `sign_of_int`: -1, 0 or 1.
fn sign(i: i32) -> i32 {
i.signum()
}
/// `(v << shift) >> shift`: `v` wrapped to `chanbits` bits, sign-extended.
fn wrap(v: i32, chanshift: u32) -> i32 {
(v << chanshift) >> chanshift
}
/// 1 << (denshift - 1), 0 when `denshift` is 0 (a shift the reference leaves undefined).
fn half(denshift: u32) -> i32 {
if denshift == 0 { 0 } else { 1i32 << (denshift - 1).min(30) }
}
/// The sign-LMS coefficient update after a residual of `del` (`top`, `prev`: the base sample and
/// the samples before the current one, nearest first).
fn adapt(coefs: &mut [i16], del: i32, top: i32, prev: impl Fn(usize) -> i32, denshift: u32) {
let numactive = coefs.len();
let mut del0 = del;
let shift = denshift.min(31);
if del > 0 {
for k in (0..numactive).rev() {
let dd = top.wrapping_sub(prev(k));
let sgn = sign(dd);
coefs[k] = coefs[k].wrapping_sub(sgn as i16);
del0 = del0.wrapping_sub(((numactive - k) as i32).wrapping_mul(sgn.wrapping_mul(dd) >> shift));
if del0 <= 0 {
break;
}
}
} else if del < 0 {
for k in (0..numactive).rev() {
let dd = top.wrapping_sub(prev(k));
let sgn = sign(dd);
coefs[k] = coefs[k].wrapping_add(sgn as i16);
del0 = del0.wrapping_sub(((numactive - k) as i32).wrapping_mul(sgn.wrapping_neg().wrapping_mul(dd) >> shift));
if del0 >= 0 {
break;
}
}
}
}
/// `pc_block`: residuals `pc` of `input` (both at least `num` long) with `numactive`
/// coefficients ([`FIRST_ORDER`]: first differences, 0: a copy).
pub(crate) fn pc_block(input: &[i32], pc: &mut [i32], num: usize, coefs: &mut [i16], numactive: usize, chanbits: u32, denshift: u32) {
let cap = input.len().min(pc.len());
if cap == 0 {
return;
}
let num = num.min(cap);
let chanshift = 32u32.saturating_sub(chanbits).min(31);
pc[0] = input[0];
if numactive == 0 {
if num > 1 {
pc[1..num].copy_from_slice(&input[1..num]);
}
return;
}
let warm = if numactive == FIRST_ORDER { num } else { (numactive + 1).min(cap) };
for j in 1..warm {
pc[j] = wrap(input[j].wrapping_sub(input[j - 1]), chanshift);
}
if numactive == FIRST_ORDER {
return;
}
let active = numactive.min(coefs.len());
let coefs = &mut coefs[..active];
let numactive = coefs.len();
let lim = numactive + 1;
let denhalf = half(denshift);
let shift = denshift.min(31);
for j in lim..num {
let top = input[j - lim];
let prev = |k: usize| input[j - 1 - k];
let mut sum: i32 = 0;
for (k, &c) in coefs.iter().enumerate() {
sum = sum.wrapping_sub(i32::from(c).wrapping_mul(top.wrapping_sub(prev(k))));
}
let del = wrap(input[j].wrapping_sub(top).wrapping_sub(sum.wrapping_add(denhalf) >> shift), chanshift);
pc[j] = del;
adapt(coefs, del, top, prev, denshift);
}
}
/// `unpc_block`: samples `out` from residuals `pc` (both at least `num` long), the inverse of
/// [`pc_block`] with the same coefficients.
pub(crate) fn unpc_block(pc: &[i32], out: &mut [i32], num: usize, coefs: &mut [i16], numactive: usize, chanbits: u32, denshift: u32) {
let cap = pc.len().min(out.len());
if cap == 0 {
return;
}
let num = num.min(cap);
let chanshift = 32u32.saturating_sub(chanbits).min(31);
out[0] = pc[0];
if numactive == 0 {
if num > 1 {
out[1..num].copy_from_slice(&pc[1..num]);
}
return;
}
let warm = if numactive == FIRST_ORDER { num } else { (numactive + 1).min(cap) };
for j in 1..warm {
out[j] = wrap(pc[j].wrapping_add(out[j - 1]), chanshift);
}
if numactive == FIRST_ORDER {
return;
}
let active = numactive.min(coefs.len());
let coefs = &mut coefs[..active];
let numactive = coefs.len();
let lim = numactive + 1;
let denhalf = half(denshift);
let shift = denshift.min(31);
for j in lim..num {
let top = out[j - lim];
let mut sum: i32 = 0;
for (k, &c) in coefs.iter().enumerate() {
sum = sum.wrapping_add(i32::from(c).wrapping_mul(out[j - 1 - k].wrapping_sub(top)));
}
let del = pc[j];
out[j] = wrap(del.wrapping_add(top).wrapping_add(sum.wrapping_add(denhalf) >> shift), chanshift);
let (done, _) = out.split_at(j);
adapt(coefs, del, top, |k| done[j - 1 - k], denshift);
}
}
/// [`unpc_block`] in place for the first-difference shortcut (mode 1's first stage).
pub(crate) fn unpc_first_order_in_place(buf: &mut [i32], num: usize, chanbits: u32) {
let num = num.min(buf.len());
let chanshift = 32u32.saturating_sub(chanbits).min(31);
for j in 1..num {
buf[j] = wrap(buf[j].wrapping_add(buf[j - 1]), chanshift);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_predictor_inverts() {
let input: Vec<i32> = (0..2000).map(|i: i32| ((i as f64 * 0.05).sin() * 20000.0) as i32 + (i * 31 % 17) - 8).collect();
for numactive in [0, 1, 4, 8, 12, FIRST_ORDER] {
for num in [0, 1, 3, 9, 2000] {
let mut ce = [0i16; 32];
init_coefs(&mut ce, DENSHIFT_DEFAULT);
let mut cd = ce;
let mut pc = vec![0; 2000];
let mut out = vec![0; 2000];
pc_block(&input, &mut pc, num, &mut ce, numactive, 16, DENSHIFT_DEFAULT);
unpc_block(&pc, &mut out, num, &mut cd, numactive, 16, DENSHIFT_DEFAULT);
assert_eq!(out[..num], input[..num], "numactive {numactive}, num {num}");
assert_eq!(ce, cd);
}
}
}
#[test]
fn coefficient_start_values() {
let mut c = [7i16; 16];
init_coefs(&mut c, DENSHIFT_DEFAULT);
assert_eq!(&c[..4], &[1216, -928, -64, 0]);
}
}
+580
View File
@@ -0,0 +1,580 @@
// Ported from Apple's ALAC reference implementation (ALACEncoder.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 encoder, from Apple's `ALACEncoder.cpp`.
//!
//! The search (stereo matrixing, 4 or 8 predictor coefficients) and the state kept across
//! packets (coefficients, the last matrixing, and the buffers the trial runs read stale parts of)
//! follow the reference, so for the same input this writes the same packets as it.
use super::ag::{self, AgParams};
use super::bits::{BitCounter, BitSink, BitWriter};
use super::dp::{DENSHIFT_DEFAULT, init_coefs, pc_block};
use super::matrix::{Mix, mix, shift_mono};
use super::{Error, ID_CPE, ID_END, ID_LFE, MAX_CHANNELS, MAX_FRAME_LENGTH, Result, SpecificConfig, element_map};
const MAX_COEFS: usize = 16;
const MAX_SEARCHES: usize = 16;
const DEFAULT_MIX_BITS: u32 = 2;
const MAX_RES: i32 = 4;
const DEFAULT_NUM_UV: usize = 8;
const MIN_UV: usize = 4;
const MAX_UV: usize = 8;
/// The `pbFactor` every element is coded with (pb = 40 · 4 / 4).
const PB_FACTOR: u32 = 4;
/// Encoder settings.
///
/// `#[non_exhaustive]`: build it with [`EncoderConfig::new`] and the `with_*` methods.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EncoderConfig {
/// Sample rate in Hz, recorded in the cookie.
pub sample_rate: u32,
/// 1 to 8 channels.
pub channels: u8,
/// 16, 20, 24 or 32.
pub bit_depth: u8,
/// Frames per packet (default [`DEFAULT_FRAME_LENGTH`](super::DEFAULT_FRAME_LENGTH)); only the
/// last packet may be shorter.
pub frame_length: u32,
/// The reference's fast mode: a stereo stream skips the matrixing and coefficient search
/// (streams of other channel counts are encoded as without it, as in the reference).
pub fast: bool,
}
impl EncoderConfig {
/// `channels` channels of `bit_depth`-bit samples at `sample_rate`, 4096 frames per packet.
pub fn new(sample_rate: u32, channels: u8, bit_depth: u8) -> Self {
Self { sample_rate, channels, bit_depth, frame_length: super::DEFAULT_FRAME_LENGTH, fast: false }
}
/// Sets `frame_length`.
pub fn with_frame_length(mut self, frame_length: u32) -> Self {
self.frame_length = frame_length;
self
}
/// Sets `fast`.
pub fn with_fast(mut self, fast: bool) -> Self {
self.fast = fast;
self
}
}
/// Per-channel predictor coefficients, one set per search width.
type SearchCoefs = [[i16; MAX_COEFS]; MAX_SEARCHES];
/// Encodes planar `i32` (see the [module docs](super)) into ALAC packets.
pub struct Encoder {
cfg: EncoderConfig,
bit_depth: u32,
last_mix_res: [i32; MAX_CHANNELS as usize],
mix_u: Vec<i32>,
mix_v: Vec<i32>,
pred_u: Vec<i32>,
pred_v: Vec<i32>,
shift_uv: Vec<u16>,
coefs_u: Vec<SearchCoefs>,
coefs_v: Vec<SearchCoefs>,
total_bytes: u64,
total_frames: u64,
max_frame_bytes: u32,
}
impl std::fmt::Debug for Encoder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Encoder").field("cfg", &self.cfg).finish_non_exhaustive()
}
}
/// The bytes of each sample the reference passes through uncompressed below the predicted part.
fn bytes_shifted(bit_depth: u32) -> u32 {
match bit_depth {
32 => 2,
24.. => 1,
_ => 0,
}
}
impl Encoder {
/// An encoder for `cfg`.
pub fn new(cfg: EncoderConfig) -> Result<Self> {
if ![16, 20, 24, 32].contains(&cfg.bit_depth) {
return Err(Error::InvalidInput("bit depth other than 16, 20, 24 or 32"));
}
if cfg.channels == 0 || cfg.channels > MAX_CHANNELS {
return Err(Error::InvalidInput("channel count outside 1 to 8"));
}
if cfg.frame_length == 0 || cfg.frame_length > MAX_FRAME_LENGTH {
return Err(Error::InvalidInput("frame length outside 1 to 2^20"));
}
let n = cfg.frame_length as usize;
let mut coefs: SearchCoefs = [[0; MAX_COEFS]; MAX_SEARCHES];
for c in &mut coefs {
init_coefs(c, DENSHIFT_DEFAULT);
}
let channels = usize::from(cfg.channels);
Ok(Self {
cfg,
bit_depth: u32::from(cfg.bit_depth),
last_mix_res: [0; MAX_CHANNELS as usize],
mix_u: vec![0; n],
mix_v: vec![0; n],
pred_u: vec![0; n],
pred_v: vec![0; n],
shift_uv: vec![0; 2 * n],
coefs_u: vec![coefs; channels],
coefs_v: vec![coefs; channels],
total_bytes: 0,
total_frames: 0,
max_frame_bytes: 0,
})
}
/// The settings.
pub fn encoder_config(&self) -> &EncoderConfig {
&self.cfg
}
/// The stream configuration, with the largest packet and the average bit rate of the packets
/// encoded so far.
pub fn config(&self) -> SpecificConfig {
let mut c = SpecificConfig::new(self.cfg.sample_rate, self.cfg.channels, self.cfg.bit_depth, self.cfg.frame_length);
c.max_frame_bytes = self.max_frame_bytes;
if self.total_frames > 0 {
let rate = u128::from(self.total_bytes) * 8 * u128::from(self.cfg.sample_rate) / u128::from(self.total_frames);
c.avg_bit_rate = u32::try_from(rate).unwrap_or(u32::MAX);
}
c
}
/// The configuration as MP4's `alac` box and Matroska's CodecPrivate hold it:
/// [`Self::config`] as [`SpecificConfig::to_bytes`] writes it (for CAF, use
/// [`SpecificConfig::to_cookie`]). Write it after the last packet for the statistics to cover
/// the whole stream.
pub fn cookie(&self) -> Vec<u8> {
self.config().to_bytes()
}
/// Encode one packet from one slice per channel, in ALAC channel order (for WAV-order audio,
/// pass WAV channel [`alac_order`](super::alac_order)`(n)[j]` as channel `j`), each holding the same
/// number of samples: 1 to the frame length (fewer only for the last packet). Samples must fit
/// the bit depth.
pub fn encode(&mut self, channels: &[&[i32]]) -> Result<Vec<u8>> {
if channels.len() != usize::from(self.cfg.channels) {
return Err(Error::InvalidInput("channel count differs from the configuration"));
}
let n = channels.first().map_or(0, |c| c.len());
if n == 0 || n > self.cfg.frame_length as usize {
return Err(Error::InvalidInput("packet length outside 1 to the frame length"));
}
if channels.iter().any(|c| c.len() != n) {
return Err(Error::InvalidInput("channels differ in length"));
}
if self.bit_depth < 32 {
let max = (1i32 << (self.bit_depth - 1)) - 1;
if channels.iter().flat_map(|c| c.iter()).any(|&s| s > max || s < -max - 1) {
return Err(Error::InvalidInput("a sample outside the bit depth"));
}
}
let mut w = BitWriter::new();
let mut index = 0;
let (mut sce_tag, mut cpe_tag, mut lfe_tag) = (0u32, 0u32, 0u32);
for &element in element_map(channels.len()) {
w.put(element, 3);
match element {
ID_CPE => {
w.put(cpe_tag, 4);
cpe_tag += 1;
let (l, r) = (channels[index], channels[index + 1]);
// as in the reference, fast mode applies to stereo streams only
if self.cfg.fast && channels.len() == 2 {
self.encode_stereo_fast(&mut w, index, l, r)?;
} else {
self.encode_stereo(&mut w, index, l, r)?;
}
index += 2;
}
_ => {
let tag = if element == ID_LFE { &mut lfe_tag } else { &mut sce_tag };
w.put(*tag, 4);
*tag += 1;
self.encode_mono(&mut w, index, channels[index])?;
index += 1;
}
}
}
w.put(ID_END, 3);
let bytes = w.into_bytes();
self.total_bytes += bytes.len() as u64;
self.total_frames += n as u64;
self.max_frame_bytes = self.max_frame_bytes.max(u32::try_from(bytes.len()).unwrap_or(u32::MAX));
Ok(bytes)
}
fn partial(&self, n: usize) -> bool {
n != self.cfg.frame_length as usize
}
/// The element header: 12 zero bits, the flags, the sample count of a partial frame.
fn header(&self, w: &mut BitWriter, n: usize, flags: u32) {
let partial = self.partial(n);
w.put(0, 12);
w.put((u32::from(partial) << 3) | flags, 4);
if partial {
w.put(n as u32, 32);
}
}
/// `EncodeStereo`: a channel pair with the matrixing and coefficient search.
fn encode_stereo(&mut self, w: &mut BitWriter, ch: usize, l: &[i32], r: &[i32]) -> Result<()> {
let start = w.bit_len();
let n = l.len();
let bs = bytes_shifted(self.bit_depth);
let chan_bits = self.bit_depth - bs * 8 + 1;
let partial = self.partial(n);
let params = AgParams::new(ag::MB0, PB_FACTOR * ag::PB0 / 4, ag::KB0);
// matrixing search over an eighth of the samples
let mut dilate = 8;
let mut min_bits1 = 1u64 << 31;
let mut best_res = self.last_mix_res[ch];
for mix_res in 0..=MAX_RES {
let nd = n / dilate;
let m = Mix { bits: DEFAULT_MIX_BITS, res: mix_res };
mix((&l[..nd], &r[..nd]), (&mut self.mix_u, &mut self.mix_v), m, &mut self.shift_uv, bs);
let cu = &mut self.coefs_u[ch][DEFAULT_NUM_UV - 1];
pc_block(&self.mix_u, &mut self.pred_u, nd, cu, DEFAULT_NUM_UV, chan_bits, DENSHIFT_DEFAULT);
let cv = &mut self.coefs_v[ch][DEFAULT_NUM_UV - 1];
pc_block(&self.mix_v, &mut self.pred_v, nd, cv, DEFAULT_NUM_UV, chan_bits, DENSHIFT_DEFAULT);
let bits = count(params, &self.pred_u[..nd], chan_bits)? + count(params, &self.pred_v[..nd], chan_bits)?;
if bits < min_bits1 {
min_bits1 = bits;
best_res = mix_res;
}
}
self.last_mix_res[ch] = best_res;
let m = Mix { bits: DEFAULT_MIX_BITS, res: best_res };
mix((l, r), (&mut self.mix_u, &mut self.mix_v), m, &mut self.shift_uv, bs);
// predictor search: 4 or 8 coefficients, converged on a 32nd of the samples
let (mut num_u, mut num_v) = (MIN_UV, MIN_UV);
let (mut min_bits1, mut min_bits2) = (1u64 << 31, 1u64 << 31);
for num_uv in (MIN_UV..=MAX_UV).step_by(4) {
dilate = 32;
for _ in 0..8 {
let cu = &mut self.coefs_u[ch][num_uv - 1];
pc_block(&self.mix_u, &mut self.pred_u, n / dilate, cu, num_uv, chan_bits, DENSHIFT_DEFAULT);
let cv = &mut self.coefs_v[ch][num_uv - 1];
pc_block(&self.mix_v, &mut self.pred_v, n / dilate, cv, num_uv, chan_bits, DENSHIFT_DEFAULT);
}
dilate = 8;
let header = 16 * num_uv as u64;
let bits1 = count(params, &self.pred_u[..n / dilate], chan_bits)? * dilate as u64 + header;
if bits1 < min_bits1 {
min_bits1 = bits1;
num_u = num_uv;
}
let bits2 = count(params, &self.pred_v[..n / dilate], chan_bits)? * dilate as u64 + header;
if bits2 < min_bits2 {
min_bits2 = bits2;
num_v = num_uv;
}
}
// escape when the estimate is no smaller than the samples themselves
let partial_bits = if partial { 32 } else { 0 };
let mut min_bits = min_bits1 + min_bits2 + 64 + partial_bits;
if bs != 0 {
min_bits += n as u64 * u64::from(bs * 8) * 2;
}
let escape_bits = n as u64 * u64::from(self.bit_depth) * 2 + partial_bits + 16;
let mut escape = min_bits >= escape_bits;
if !escape {
self.header(w, n, bs << 1);
w.put(DEFAULT_MIX_BITS, 8);
w.put(best_res as u32, 8);
for (coefs, num) in [(&self.coefs_u[ch][num_u - 1], num_u), (&self.coefs_v[ch][num_v - 1], num_v)] {
w.put(DENSHIFT_DEFAULT, 8); // mode 0
w.put((PB_FACTOR << 5) | num as u32, 8);
for &c in &coefs[..num] {
w.put(u32::from(c as u16), 16);
}
}
if bs != 0 {
let shift = bs * 8;
for pair in self.shift_uv[..2 * n].as_chunks::<2>().0 {
w.put((u32::from(pair[0]) << shift) | u32::from(pair[1]), shift * 2);
}
}
let cu = &mut self.coefs_u[ch][num_u - 1];
pc_block(&self.mix_u, &mut self.pred_u, n, cu, num_u, chan_bits, DENSHIFT_DEFAULT);
ag::compress(params, &self.pred_u[..n], w, chan_bits)?;
let cv = &mut self.coefs_v[ch][num_v - 1];
pc_block(&self.mix_v, &mut self.pred_v, n, cv, num_v, chan_bits, DENSHIFT_DEFAULT);
ag::compress(params, &self.pred_v[..n], w, chan_bits)?;
if (w.bit_len() - start) as u64 >= escape_bits {
w.truncate(start);
escape = true;
}
}
if escape {
self.encode_stereo_escape(w, l, r);
}
Ok(())
}
/// `EncodeStereoFast`: a channel pair without matrixing or search.
fn encode_stereo_fast(&mut self, w: &mut BitWriter, ch: usize, l: &[i32], r: &[i32]) -> Result<()> {
let start = w.bit_len();
let n = l.len();
let bs = bytes_shifted(self.bit_depth);
let chan_bits = self.bit_depth - bs * 8 + 1;
let partial = self.partial(n);
let params = AgParams::new(ag::MB0, PB_FACTOR * ag::PB0 / 4, ag::KB0);
let num = DEFAULT_NUM_UV;
let m = Mix { bits: DEFAULT_MIX_BITS, res: 0 };
mix((l, r), (&mut self.mix_u, &mut self.mix_v), m, &mut self.shift_uv, bs);
// written speculatively, rewound for an escape
self.header(w, n, bs << 1);
w.put(DEFAULT_MIX_BITS, 8);
w.put(0, 8);
for coefs in [&self.coefs_u[ch][num - 1], &self.coefs_v[ch][num - 1]] {
w.put(DENSHIFT_DEFAULT, 8);
w.put((PB_FACTOR << 5) | num as u32, 8);
for &c in &coefs[..num] {
w.put(u32::from(c as u16), 16);
}
}
if bs != 0 {
let shift = bs * 8;
for pair in self.shift_uv[..2 * n].as_chunks::<2>().0 {
w.put((u32::from(pair[0]) << shift) | u32::from(pair[1]), shift * 2);
}
}
let cu = &mut self.coefs_u[ch][num - 1];
pc_block(&self.mix_u, &mut self.pred_u, n, cu, num, chan_bits, DENSHIFT_DEFAULT);
let before = w.bit_len();
ag::compress(params, &self.pred_u[..n], w, chan_bits)?;
let bits1 = (w.bit_len() - before) as u64;
let cv = &mut self.coefs_v[ch][num - 1];
pc_block(&self.mix_v, &mut self.pred_v, n, cv, num, chan_bits, DENSHIFT_DEFAULT);
let before = w.bit_len();
ag::compress(params, &self.pred_v[..n], w, chan_bits)?;
let bits2 = (w.bit_len() - before) as u64;
let partial_bits = if partial { 32 } else { 0 };
let mut min_bits = bits1 + bits2 + 2 * 16 * num as u64 + 64 + partial_bits;
if bs != 0 {
min_bits += n as u64 * u64::from(bs * 8) * 2;
}
let escape_bits = n as u64 * u64::from(self.bit_depth) * 2 + partial_bits + 16;
if min_bits >= escape_bits || (w.bit_len() - start) as u64 >= escape_bits {
w.truncate(start);
self.encode_stereo_escape(w, l, r);
}
Ok(())
}
/// `EncodeStereoEscape`: a channel pair uncompressed, L R interleaved.
fn encode_stereo_escape(&mut self, w: &mut BitWriter, l: &[i32], r: &[i32]) {
let n = l.len();
self.header(w, n, 1);
for (&a, &b) in l.iter().zip(r) {
w.put(a as u32, self.bit_depth);
w.put(b as u32, self.bit_depth);
}
// the reference de-interleaves 20- and 24-bit input through the mix buffers
if matches!(self.bit_depth, 20 | 24) {
self.mix_u[..n].copy_from_slice(l);
self.mix_v[..n].copy_from_slice(r);
}
}
/// `EncodeMono`: one channel (SCE or LFE).
fn encode_mono(&mut self, w: &mut BitWriter, ch: usize, input: &[i32]) -> Result<()> {
let start = w.bit_len();
let n = input.len();
let bs = bytes_shifted(self.bit_depth);
let shift = bs * 8;
let chan_bits = self.bit_depth - shift;
let partial = self.partial(n);
let params = AgParams::new(ag::MB0, PB_FACTOR * ag::PB0 / 4, ag::KB0);
shift_mono(input, &mut self.mix_u, &mut self.shift_uv, n, bs);
let mut min_bits = 1u64 << 31;
let mut best_u = MIN_UV;
for num_u in (MIN_UV..=MAX_UV).step_by(4) {
let coefs = &mut self.coefs_u[ch][num_u - 1];
for _ in 0..7 {
pc_block(&self.mix_u, &mut self.pred_u, n / 32, coefs, num_u, chan_bits, DENSHIFT_DEFAULT);
}
pc_block(&self.mix_u, &mut self.pred_u, n / 8, coefs, num_u, chan_bits, DENSHIFT_DEFAULT);
let bits = 8 * count(params, &self.pred_u[..n / 8], chan_bits)? + 16 * num_u as u64;
if bits < min_bits {
best_u = num_u;
min_bits = bits;
}
}
let partial_bits = if partial { 32 } else { 0 };
min_bits += 32 + partial_bits;
if bs != 0 {
min_bits += n as u64 * u64::from(shift);
}
let escape_bits = n as u64 * u64::from(self.bit_depth) + partial_bits + 16;
let mut escape = min_bits >= escape_bits;
if !escape {
self.header(w, n, bs << 1);
w.put(0, 16); // mixBits = mixRes = 0
w.put(DENSHIFT_DEFAULT, 8); // mode 0
w.put((PB_FACTOR << 5) | best_u as u32, 8);
for &c in &self.coefs_u[ch][best_u - 1][..best_u] {
w.put(u32::from(c as u16), 16);
}
if bs != 0 {
for &s in &self.shift_uv[..n] {
w.put(u32::from(s), shift);
}
}
let coefs = &mut self.coefs_u[ch][best_u - 1];
pc_block(&self.mix_u, &mut self.pred_u, n, coefs, best_u, chan_bits, DENSHIFT_DEFAULT);
ag::compress(AgParams::standard(), &self.pred_u[..n], w, chan_bits)?;
if (w.bit_len() - start) as u64 >= escape_bits {
w.truncate(start);
escape = true;
}
}
if escape {
self.header(w, n, 1);
for &s in input {
w.put(s as u32, self.bit_depth);
}
// the reference converts 20- and 24-bit input through the mix buffer
if matches!(self.bit_depth, 20 | 24) {
self.mix_u[..n].copy_from_slice(input);
}
}
Ok(())
}
}
/// Bits the residuals `pc` code to.
fn count(params: AgParams, pc: &[i32], bits: u32) -> Result<u64> {
let mut c = BitCounter::default();
ag::compress(params, pc, &mut c, bits)?;
Ok(c.bit_len() as u64)
}
// The round trips need the decoder.
#[cfg(test)]
#[cfg(feature = "alac-decode")]
mod tests {
use super::super::Decoder;
use super::*;
fn signal(n: usize, ch: usize, bits: u32) -> Vec<Vec<i32>> {
let amp = ((1i64 << (bits - 1)) - 1) as f64 * 0.8;
(0..ch)
.map(|c| {
(0..n)
.map(|i| {
let t = i as f64 / 44_100.0;
let x =
(t * (220.0 + 110.0 * c as f64) * std::f64::consts::TAU).sin() * 0.7 + ((i * 7919 + c * 31) % 101) as f64 / 400.0 - 0.125;
(x * amp) as i32
})
.collect()
})
.collect()
}
fn roundtrip(cfg: EncoderConfig, frames: usize) -> usize {
let input = signal(frames, usize::from(cfg.channels), u32::from(cfg.bit_depth));
let mut enc = Encoder::new(cfg).unwrap();
let mut packets = Vec::new();
let fl = cfg.frame_length as usize;
for start in (0..frames).step_by(fl) {
let end = (start + fl).min(frames);
let chunk: Vec<&[i32]> = input.iter().map(|c| &c[start..end]).collect();
packets.push(enc.encode(&chunk).unwrap());
}
let mut dec = Decoder::new(&enc.cookie()).unwrap();
let mut out = vec![Vec::new(); input.len()];
for p in &packets {
for (o, c) in out.iter_mut().zip(dec.decode(p).unwrap()) {
o.extend(c);
}
}
assert_eq!(out, input, "{cfg:?}");
assert_eq!(enc.config().max_frame_bytes as usize, packets.iter().map(Vec::len).max().unwrap());
packets.iter().map(Vec::len).sum()
}
#[test]
fn every_depth_and_channel_count_roundtrips() {
for bits in [16, 20, 24, 32] {
for ch in 1..=8 {
for fast in [false, true] {
let cfg = EncoderConfig::new(44_100, ch, bits).with_fast(fast).with_frame_length(1024);
roundtrip(cfg, 3000);
}
}
}
}
#[test]
fn short_and_tiny_packets_roundtrip() {
for n in [1, 2, 5, 9, 31, 33, 100] {
roundtrip(EncoderConfig::new(48_000, 2, 16).with_frame_length(4096), n);
roundtrip(EncoderConfig::new(48_000, 1, 24).with_frame_length(4096), n);
}
roundtrip(EncoderConfig::new(48_000, 2, 24).with_frame_length(3), 50);
}
#[test]
fn compresses_tonal_audio_and_escapes_noise() {
// two tones, one per channel, at half scale: well under half the PCM size
let tones: Vec<Vec<i32>> = [440.0, 660.0]
.iter()
.map(|f| (0..44_100).map(|i| ((i as f64 * f / 44_100.0 * std::f64::consts::TAU).sin() * 16_000.0) as i32).collect())
.collect();
let mut enc = Encoder::new(EncoderConfig::new(44_100, 2, 16)).unwrap();
let mut bytes = 0;
for start in (0..44_100).step_by(4096) {
let end = (start + 4096).min(44_100);
bytes += enc.encode(&[&tones[0][start..end], &tones[1][start..end]]).unwrap().len();
}
assert!(bytes < 44_100 * 4 / 2, "{bytes} bytes");
// white noise at full scale does not compress: escaped frames, a few bytes over raw
let mut seed = 1u32;
let noise: Vec<i32> = (0..8192)
.map(|_| {
seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
(seed >> 16) as i16 as i32
})
.collect();
let mut enc = Encoder::new(EncoderConfig::new(44_100, 1, 16)).unwrap();
let p = enc.encode(&[&noise[..4096]]).unwrap();
assert!(p.len() <= 4096 * 2 + 4, "{}", p.len());
let mut dec = Decoder::new(&enc.cookie()).unwrap();
assert_eq!(dec.decode(&p).unwrap(), vec![noise[..4096].to_vec()]);
}
#[test]
fn bad_input_is_refused() {
assert!(Encoder::new(EncoderConfig::new(44_100, 2, 8)).is_err());
assert!(Encoder::new(EncoderConfig::new(44_100, 9, 16)).is_err());
assert!(Encoder::new(EncoderConfig::new(44_100, 2, 16).with_frame_length(0)).is_err());
let mut enc = Encoder::new(EncoderConfig::new(44_100, 2, 16).with_frame_length(16)).unwrap();
assert!(enc.encode(&[&[0; 4]]).is_err());
assert!(enc.encode(&[&[0; 4], &[0; 5]]).is_err());
assert!(enc.encode(&[&[0; 17], &[0; 17]]).is_err());
assert!(enc.encode(&[&[], &[]]).is_err());
assert!(enc.encode(&[&[40_000], &[0]]).is_err());
assert!(enc.encode(&[&[-32_768], &[32_767]]).is_ok());
}
}
+120
View File
@@ -0,0 +1,120 @@
// Ported from Apple's ALAC reference implementation (matrixlib.h, matrix_dec.c and matrix_enc.c, 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
//! Stereo matrixing and the shifted-off low bytes, from Apple's `matrixlib.h`, `matrix_dec.c` and
//! `matrix_enc.c`.
//!
//! The reference has one routine per sample width (16, 20, 24, 32 bits) over interleaved buffers;
//! on right-aligned planar `i32` samples they reduce to the same arithmetic, ported once here.
//!
//! A generalised mid/side transform, lossless for any `mixres` and `mixbits` (m = 2^mixbits):
//! u = floor((r·L + (m − r)·R) / m), v = L − R; inverse L = u + v − floor(r·v / m), R = L − v.
/// `x >> n` for any `n` (31 and above give the sign).
fn shr(x: i32, n: u32) -> i32 {
x >> n.min(31)
}
/// Split off the low `shift` bits of `x` into the shift buffer value and return the rest.
fn split(x: i32, shift: u32) -> (i32, u16) {
if shift == 0 { (x, 0) } else { (x >> shift, (x as u32 & ((1u32 << shift) - 1)) as u16) }
}
/// A channel pair's matrixing: `mixbits` (m = 2^mixbits) and `mixres` (r; 0 for none).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Mix {
pub(crate) bits: u32,
pub(crate) res: i32,
}
/// `mix16/20/24/32`: `u`, `v` from the samples of `l` and `r` (as many as the shortest of the
/// four holds), the low `bytes_shifted` bytes of each going to `shift_uv` (interleaved L, R).
pub(crate) fn mix((l, r): (&[i32], &[i32]), (u, v): (&mut [i32], &mut [i32]), m: Mix, shift_uv: &mut [u16], bytes_shifted: u32) {
let (mixbits, mixres) = (m.bits, m.res);
let shift = bytes_shifted * 8;
let m2 = (1i32 << mixbits.min(30)).wrapping_sub(mixres);
for j in 0..l.len().min(r.len()).min(u.len()).min(v.len()) {
let (lj, ls) = split(l[j], shift);
let (rj, rs) = split(r[j], shift);
if shift != 0
&& let Some(s) = shift_uv.get_mut(2 * j..2 * j + 2)
{
s.copy_from_slice(&[ls, rs]);
}
if mixres != 0 {
u[j] = shr(mixres.wrapping_mul(lj).wrapping_add(m2.wrapping_mul(rj)), mixbits);
v[j] = lj.wrapping_sub(rj);
} else {
u[j] = lj;
v[j] = rj;
}
}
}
/// `unmix16/20/24/32`: the samples of `l` and `r` (as many as the shortest of the four holds) from
/// `u`, `v` and the shifted-off bytes, wrapped to `bit_depth` bits as the reference's stores to
/// 16/24/32-bit words wrap them.
pub(crate) fn unmix((u, v): (&[i32], &[i32]), (l, r): (&mut [i32], &mut [i32]), m: Mix, shift_uv: &[u16], bytes_shifted: u32, bit_depth: u32) {
let (mixbits, mixres) = (m.bits, m.res);
let shift = bytes_shifted * 8;
for j in 0..u.len().min(v.len()).min(l.len()).min(r.len()) {
let (mut lj, mut rj) = if mixres != 0 {
let lj = u[j].wrapping_add(v[j]).wrapping_sub(shr(mixres.wrapping_mul(v[j]), mixbits));
(lj, lj.wrapping_sub(v[j]))
} else {
(u[j], v[j])
};
if shift != 0 {
let ls = shift_uv.get(2 * j).copied().unwrap_or(0);
let rs = shift_uv.get(2 * j + 1).copied().unwrap_or(0);
lj = (lj << shift) | i32::from(ls);
rj = (rj << shift) | i32::from(rs);
}
l[j] = super::bits::sign_extend(lj as u32, bit_depth);
r[j] = super::bits::sign_extend(rj as u32, bit_depth);
}
}
/// `copyPredictorTo24Shift` and friends for one channel: the samples from the predictor output
/// and the shifted-off bytes, wrapped to `bit_depth`.
pub(crate) fn unshift(pred: &[i32], shift_buf: &[u16], out: &mut [i32], num: usize, bytes_shifted: u32, bit_depth: u32) {
let shift = bytes_shifted * 8;
for j in 0..num.min(pred.len()).min(out.len()) {
let x = if shift != 0 { (pred[j] << shift) | i32::from(shift_buf.get(j).copied().unwrap_or(0)) } else { pred[j] };
out[j] = super::bits::sign_extend(x as u32, bit_depth);
}
}
/// The encoder's mono split (`EncodeMono`): predictor input and shifted-off bytes of `input`.
pub(crate) fn shift_mono(input: &[i32], pred_in: &mut [i32], shift_buf: &mut [u16], num: usize, bytes_shifted: u32) {
let shift = bytes_shifted * 8;
for j in 0..num.min(input.len()).min(pred_in.len()) {
let (x, s) = split(input[j], shift);
pred_in[j] = x;
if let Some(b) = shift_buf.get_mut(j) {
*b = s;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matrixing_inverts() {
let l: Vec<i32> = (0..100).map(|i| (i * 977 % 16_000_000) - 8_000_000).collect();
let r: Vec<i32> = (0..100).map(|i| (i * 7919 % 16_000_000) - 8_000_000).collect();
for (mixres, bytes) in [(0, 0), (1, 0), (2, 1), (4, 1), (3, 0)] {
let (mut u, mut v, mut s) = (vec![0; 100], vec![0; 100], vec![0u16; 200]);
let m = Mix { bits: 2, res: mixres };
mix((&l, &r), (&mut u, &mut v), m, &mut s, bytes);
let (mut l2, mut r2) = (vec![0; 100], vec![0; 100]);
unmix((&u, &v), (&mut l2, &mut r2), m, &s, bytes, 24);
assert_eq!((l2, r2), (l.clone(), r.clone()), "mixres {mixres}");
}
}
}
+348
View File
@@ -0,0 +1,348 @@
// Ported from Apple's ALAC reference implementation (ALACAudioTypes.h, ALACDecoder.cpp, ALACEncoder.cpp and ALACMagicCookieDescription.txt, 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
//! Apple Lossless (ALAC): [`Decoder`] (feature `alac-decode`) and [`Encoder`] (feature
//! `alac-encode`), packet by packet, with the stream configuration ([`SpecificConfig`]).
//!
//! Where the configuration goes: MP4's `alac` box (after its FullBox header) and Matroska's
//! `A_ALAC` CodecPrivate hold the 24-byte `ALACSpecificConfig` ([`SpecificConfig::to_bytes`]);
//! an MP4 of more than two channels carries the layout in a separate `chan` box, as Apple's
//! `afconvert` writes it. CAF's `kuki` chunk holds the magic cookie as Apple's encoder vends it,
//! the config followed by a `chan` atom for more than two channels ([`SpecificConfig::to_cookie`]).
//! The decoder takes either form.
//!
//! Samples are planar `i32`, right-aligned at the stream's bit depth (a 24-bit sample lies in
//! −2^23 ..= 2^23 − 1), channels in ALAC order (see [`channel_mask`]; [`wav_order`] maps them
//! to WAV order).
//!
//! Ported from Apple's reference implementation (<https://github.com/macosforge/alac>, commit
//! c38887c, `codec/`), Copyright © 2011 Apple Inc., Apache License 2.0. Changes from the
//! reference: hostile input returns errors instead of reading or writing out of bounds, integer
//! arithmetic wraps where C would overflow, the 4- and 8-tap predictor loops are folded into the
//! general one (same values), the sample-width variants of the matrix routines into one routine
//! over `i32`, and the encoder fills in the cookie's average bit rate.
// The bit I/O, coder, predictor and matrix modules serve both halves; with only one of
// `alac-decode` and `alac-encode` built, the other half's routines are unused.
#![cfg_attr(not(all(feature = "alac-decode", feature = "alac-encode")), allow(dead_code))]
mod ag;
mod bits;
#[cfg(feature = "alac-decode")]
mod decoder;
mod dp;
#[cfg(feature = "alac-encode")]
mod encoder;
mod matrix;
#[cfg(feature = "alac-decode")]
pub use decoder::Decoder;
#[cfg(feature = "decode-native")]
pub(crate) use decoder::header_frames;
#[cfg(feature = "alac-encode")]
pub use encoder::{Encoder, EncoderConfig};
/// The frames per packet Apple's encoder writes by default (`kALACDefaultFramesPerPacket`).
pub const DEFAULT_FRAME_LENGTH: u32 = 4096;
/// The most frames a packet may hold here: the decoder allocates a packet's samples up front, and
/// the demuxers allow packets of at most this many frames.
pub const MAX_FRAME_LENGTH: u32 = 1 << 20;
/// The most channels a stream has (`kALACMaxChannels`).
pub const MAX_CHANNELS: u8 = 8;
/// Size of the `ALACSpecificConfig` at the start of a cookie.
const CONFIG_LEN: usize = 24;
/// Size of the channel layout atom a cookie may carry after the config (`kChannelAtomSize` plus
/// `ALACAudioChannelLayout`).
const CHANNEL_LAYOUT_LEN: usize = 24;
/// The CoreAudio channel layout tag of each channel count, the only layouts ALAC uses
/// (`ALACChannelLayoutTags`): mono C; stereo L R; then C L R, C L R Cs, C L R Ls Rs,
/// C L R Ls Rs LFE, C L R Ls Rs Cs LFE and C Lc Rc L R Ls Rs LFE.
pub const CHANNEL_LAYOUT_TAGS: [u32; 8] =
[(100 << 16) | 1, (101 << 16) | 2, (113 << 16) | 3, (116 << 16) | 4, (120 << 16) | 5, (124 << 16) | 6, (142 << 16) | 7, (127 << 16) | 8];
/// Element tags (`ID_SCE` … `ID_END`).
const ID_SCE: u32 = 0;
const ID_CPE: u32 = 1;
const ID_CCE: u32 = 2;
const ID_LFE: u32 = 3;
const ID_DSE: u32 = 4;
const ID_PCE: u32 = 5;
const ID_FIL: u32 = 6;
const ID_END: u32 = 7;
/// The elements a frame of `channels` channels holds, in order (`sChannelMaps`): single channels
/// for C, the LFE and the 6.1 back centre, pairs for the rest.
pub(crate) fn element_map(channels: usize) -> &'static [u32] {
match channels {
1 => &[ID_SCE],
2 => &[ID_CPE],
3 => &[ID_SCE, ID_CPE],
4 => &[ID_SCE, ID_CPE, ID_SCE],
5 => &[ID_SCE, ID_CPE, ID_CPE],
6 => &[ID_SCE, ID_CPE, ID_CPE, ID_SCE],
7 => &[ID_SCE, ID_CPE, ID_CPE, ID_SCE, ID_SCE],
8 => &[ID_SCE, ID_CPE, ID_CPE, ID_CPE, ID_SCE],
_ => &[],
}
}
/// The WAVEFORMATEXTENSIBLE speaker mask of the layout for `channels` (the tags in
/// [`CHANNEL_LAYOUT_TAGS`], surrounds as back speakers): 0 for a count ALAC does not have.
pub fn channel_mask(channels: u8) -> u32 {
match channels {
1 => 0x4,
2 => 0x3,
3 => 0x7,
4 => 0x107,
5 => 0x37,
6 => 0x3F,
7 => 0x13F,
8 => 0xFF,
_ => 0,
}
}
/// Channel order for WAV/SMPTE output: WAV channel → decoded (ALAC-order) channel, for 3 to 8
/// channels; `None` for mono and stereo, which need none, and other counts. The inverse of
/// [`alac_order`].
pub fn wav_order(channels: u8) -> Option<&'static [usize]> {
Some(match channels {
3 => &[1, 2, 0],
4 => &[1, 2, 0, 3],
5 => &[1, 2, 0, 3, 4],
6 => &[1, 2, 0, 5, 3, 4],
7 => &[1, 2, 0, 6, 3, 4, 5],
8 => &[3, 4, 0, 7, 5, 6, 1, 2],
_ => return None,
})
}
/// Channel order for encoding WAV/SMPTE-order audio: ALAC channel → WAV channel (the
/// encoder's channel `j` is WAV channel `alac_order(n)[j]`), for 3 to 8 channels; `None` for
/// mono, stereo and other counts. The inverse of [`wav_order`].
pub fn alac_order(channels: u8) -> Option<&'static [usize]> {
Some(match channels {
3 => &[2, 0, 1],
4 => &[2, 0, 1, 3],
5 => &[2, 0, 1, 3, 4],
6 => &[2, 0, 1, 4, 5, 3],
7 => &[2, 0, 1, 4, 5, 6, 3],
8 => &[2, 6, 7, 0, 1, 4, 5, 3],
_ => return None,
})
}
/// Errors from cookie parsing, decoding and encoding.
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum Error {
/// The magic cookie is malformed or outside the supported range.
#[error("invalid configuration: {0}")]
InvalidConfig(&'static str),
/// A valid stream uses something this module does not implement.
#[error("unsupported feature: {0}")]
Unsupported(&'static str),
/// Malformed packet.
#[error("invalid bitstream: {0}")]
Bitstream(&'static str),
/// The packet ended early.
#[error("unexpected end of packet")]
Eof,
/// Samples or settings the encoder cannot take.
#[error("invalid encoder input: {0}")]
InvalidInput(&'static str),
}
/// Result with this module's [`Error`].
pub type Result<T> = std::result::Result<T, Error>;
/// The `ALACSpecificConfig` of a magic cookie, with the channel layout tag when the cookie has one.
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpecificConfig {
/// Frames per packet (the last packet may hold fewer).
pub frame_length: u32,
/// 0 for every stream the reference writes or reads.
pub compatible_version: u8,
/// Bits per source sample (16, 20, 24 or 32 from Apple's encoder).
pub bit_depth: u8,
/// Rice coder tuning: history multiplier (40).
pub pb: u8,
/// Rice coder tuning: initial history (10).
pub mb: u8,
/// Rice coder tuning: largest Rice parameter (14).
pub kb: u8,
/// Channel count.
pub channels: u8,
/// Unused by the coder (255).
pub max_run: u16,
/// Largest packet in bytes, 0 when unknown.
pub max_frame_bytes: u32,
/// Average bit rate, 0 when unknown.
pub avg_bit_rate: u32,
/// Sample rate in Hz.
pub sample_rate: u32,
/// The CoreAudio channel layout tag of the cookie's `chan` atom, when it has one.
pub channel_layout_tag: Option<u32>,
}
impl SpecificConfig {
/// A configuration with the reference encoder's tuning and no statistics.
pub fn new(sample_rate: u32, channels: u8, bit_depth: u8, frame_length: u32) -> Self {
SpecificConfig {
frame_length,
compatible_version: 0,
bit_depth,
pb: ag::PB0 as u8,
mb: ag::MB0 as u8,
kb: ag::KB0 as u8,
channels,
max_run: ag::MAX_RUN_DEFAULT as u16,
max_frame_bytes: 0,
avg_bit_rate: 0,
sample_rate,
channel_layout_tag: None,
}
}
/// Parse a magic cookie. As in the reference decoder, `frma` and `alac` atom headers in front
/// of the config (cookies from older QuickTime files) are skipped. The fields are not checked
/// here; [`Decoder::new`] checks what it needs.
pub fn parse(cookie: &[u8]) -> Result<Self> {
let mut c = cookie;
for atom in [b"frma", b"alac"] {
if c.get(4..8) == Some(atom.as_slice()) {
c = c.get(12..).ok_or(Error::InvalidConfig("magic cookie shorter than its atom header"))?;
}
}
let b = c.get(..CONFIG_LEN).ok_or(Error::InvalidConfig("magic cookie shorter than 24 bytes"))?;
let u32_at = |i: usize| b.get(i..i + 4).and_then(|s| s.try_into().ok()).map_or(0, u32::from_be_bytes);
let byte = |i: usize| b.get(i).copied().unwrap_or(0);
// `chan` atom: size, 'chan', version and flags, layout tag, bitmap, description count.
let rest = c.get(CONFIG_LEN..).unwrap_or_default();
// Apple's reference encoder copies the tag in host byte order (little-endian on x86 and
// ARM) although the cookie is big-endian: a byte-swapped standard tag is read as that tag.
let channel_layout_tag = (rest.get(4..8) == Some(b"chan".as_slice()))
.then(|| rest.get(12..16).and_then(|s| s.try_into().ok()).map(u32::from_be_bytes))
.flatten()
.map(|t| if !CHANNEL_LAYOUT_TAGS.contains(&t) && CHANNEL_LAYOUT_TAGS.contains(&t.swap_bytes()) { t.swap_bytes() } else { t });
Ok(SpecificConfig {
frame_length: u32_at(0),
compatible_version: byte(4),
bit_depth: byte(5),
pb: byte(6),
mb: byte(7),
kb: byte(8),
channels: byte(9),
max_run: u16::from_be_bytes([byte(10), byte(11)]),
max_frame_bytes: u32_at(12),
avg_bit_rate: u32_at(16),
sample_rate: u32_at(20),
channel_layout_tag,
})
}
/// The 24-byte `ALACSpecificConfig`, as MP4's `alac` box and Matroska's CodecPrivate hold it.
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(CONFIG_LEN + CHANNEL_LAYOUT_LEN);
out.extend_from_slice(&self.frame_length.to_be_bytes());
out.extend_from_slice(&[self.compatible_version, self.bit_depth, self.pb, self.mb, self.kb, self.channels]);
out.extend_from_slice(&self.max_run.to_be_bytes());
out.extend_from_slice(&self.max_frame_bytes.to_be_bytes());
out.extend_from_slice(&self.avg_bit_rate.to_be_bytes());
out.extend_from_slice(&self.sample_rate.to_be_bytes());
out
}
/// The magic cookie as the reference encoder vends it (`GetMagicCookie`) and CAF stores it:
/// [`Self::to_bytes`], then for more than two channels a `chan` atom with
/// [`Self::channel_layout_tag`] (the standard tag for the count when `None`). The tag is
/// written big-endian, as Apple's cookie description specifies (the reference encoder writes
/// it in host byte order).
pub fn to_cookie(&self) -> Vec<u8> {
let mut out = self.to_bytes();
let standard = CHANNEL_LAYOUT_TAGS.get(usize::from(self.channels).wrapping_sub(1)).copied();
if let Some(tag) = self.channel_layout_tag.or(standard).filter(|_| self.channels > 2) {
out.extend_from_slice(&(CHANNEL_LAYOUT_LEN as u32).to_be_bytes());
out.extend_from_slice(b"chan");
out.extend_from_slice(&[0; 4]);
out.extend_from_slice(&tag.to_be_bytes());
out.extend_from_slice(&[0; 8]);
}
out
}
/// Whether the channels are the standard layout for their count: the cookie names that
/// layout, or none (the reference decoder ignores the tag and every ALAC encoder writes the
/// standard order).
pub fn has_standard_layout(&self) -> bool {
let standard = CHANNEL_LAYOUT_TAGS.get(usize::from(self.channels).wrapping_sub(1)).copied();
standard.is_some() && self.channel_layout_tag.is_none_or(|t| Some(t) == standard)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cookies_roundtrip() {
let mut c = SpecificConfig::new(48_000, 6, 24, 4096);
c.max_frame_bytes = 12_345;
c.avg_bit_rate = 2_000_000;
let bytes = c.to_cookie();
assert_eq!(bytes.len(), 48);
let mut parsed = SpecificConfig::parse(&bytes).unwrap();
assert_eq!(parsed.channel_layout_tag, Some((124 << 16) | 6));
assert!(parsed.has_standard_layout());
parsed.channel_layout_tag = None;
assert_eq!(parsed, c);
assert_eq!(c.to_bytes(), bytes[..24]);
let stereo = SpecificConfig::new(44_100, 2, 16, 4096).to_cookie();
assert_eq!(stereo.len(), 24);
assert_eq!(&stereo[..12], &[0, 0, 0x10, 0, 0, 16, 40, 10, 14, 2, 0, 255]);
}
#[test]
fn a_little_endian_layout_tag_is_read() {
// as Apple's reference encoder writes the tag of a 7.1 stream
let mut cookie = SpecificConfig::new(44_100, 8, 16, 4096).to_cookie();
cookie[36..40].copy_from_slice(&((127u32 << 16) | 8).to_le_bytes());
let c = SpecificConfig::parse(&cookie).unwrap();
assert_eq!(c.channel_layout_tag, Some((127 << 16) | 8));
assert!(c.has_standard_layout());
}
#[test]
fn old_quicktime_atoms_are_skipped() {
let config = SpecificConfig::new(44_100, 2, 16, 4096).to_cookie();
let mut wrapped = vec![0, 0, 0, 12, b'f', b'r', b'm', b'a', b'a', b'l', b'a', b'c', 0, 0, 0, 36, b'a', b'l', b'a', b'c', 0, 0, 0, 0];
wrapped.extend_from_slice(&config);
assert_eq!(SpecificConfig::parse(&wrapped).unwrap(), SpecificConfig::parse(&config).unwrap());
assert!(SpecificConfig::parse(&wrapped[..30]).is_err());
assert!(SpecificConfig::parse(&[0; 5]).is_err());
}
#[test]
fn the_orders_are_permutations() {
for n in 3..=8u8 {
let order = wav_order(n).unwrap();
let inverse = alac_order(n).unwrap();
assert!((0..usize::from(n)).all(|i| inverse[order[i]] == i && order[inverse[i]] == i), "{n} channels");
let mut sorted = order.to_vec();
sorted.sort_unstable();
assert_eq!(sorted, (0..usize::from(n)).collect::<Vec<_>>());
assert_eq!(channel_mask(n).count_ones(), u32::from(n));
let elements: usize = element_map(usize::from(n)).iter().map(|&e| if e == ID_CPE { 2 } else { 1 }).sum();
assert_eq!(elements, usize::from(n));
}
}
}
+8 -1
View File
@@ -145,7 +145,14 @@ impl TryFrom<RawLayout> for ChannelLayout {
}
/// A channel count as the API holds it, saturating at `u16::MAX`.
#[cfg(any(feature = "aac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode", feature = "pcm-encode"))]
#[cfg(any(
feature = "aac-decode",
feature = "alac-decode",
feature = "opus-decode",
feature = "ac3-decode",
feature = "mpa-decode",
feature = "pcm-encode"
))]
pub(crate) fn count(n: usize) -> u16 {
u16::try_from(n).unwrap_or(u16::MAX)
}
+23 -9
View File
@@ -8,11 +8,11 @@
//! `wasm32-unknown-unknown`.
//!
//! - [`decode`], [`probe`], [`Decoder`] (streaming, seekable), [`tracks`], [`detect_container`]
//! (feature `decode`): WAV and AIFF are read natively; AAC, Opus, AC-3/E-AC-3, MP1/MP2 and PCM
//! in MP4/MOV, Matroska/WebM, Ogg or raw streams go through craft-media's demuxers and
//! [`TrackDecoder`]; everything else (MP3, FLAC, ALAC, Vorbis, CAF, ADPCM, A-law, µ-law)
//! through `symphonia`, which is to be replaced by native decoders. Feature `decode-native` is
//! the same without symphonia.
//! (feature `decode`): WAV and AIFF are read natively; AAC, ALAC, Opus, AC-3/E-AC-3, MP1/MP2 and
//! PCM in MP4/MOV, Matroska/WebM, Ogg or raw streams go through craft-media's demuxers and
//! [`TrackDecoder`]; everything else (MP3, FLAC, Vorbis, CAF, ADPCM, A-law, µ-law) through
//! `symphonia`, which is to be replaced by native decoders. Feature `decode-native` is the
//! same without symphonia.
//! - [`TrackDecoder`] (any decoder feature): one track's packets from any demuxer to planar
//! `f32` in WAV channel order.
//! - [`encode`], [`FileEncoder`]: WAV/BWF/RF64 and AIFF/AIFF-C (feature `pcm-encode`, which also
@@ -21,7 +21,8 @@
//! `lossless-encode` and `aac-encode`.
//! - [`peaks`] (feature `peaks`): min/max/RMS pyramid for waveform drawing.
//! - Codec modules for packet-level use, each with its own features: [`aac`] (`aac-decode`,
//! `aac-encode`), [`opus`] (`opus-decode`), [`ac3`] (`ac3-decode`), [`mpa`] (`mpa-decode`).
//! `aac-encode`), [`alac`] (`alac-decode`, `alac-encode`), [`opus`] (`opus-decode`), [`ac3`]
//! (`ac3-decode`), [`mpa`] (`mpa-decode`).
//!
//! The shared types below build with no features.
@@ -29,19 +30,21 @@
pub mod aac;
#[cfg(feature = "ac3-decode")]
pub mod ac3;
#[cfg(any(feature = "alac-decode", feature = "alac-encode"))]
pub mod alac;
mod buffer;
mod layout;
#[cfg(feature = "mpa-decode")]
pub mod mpa;
#[cfg(feature = "opus-decode")]
pub mod opus;
#[cfg(any(feature = "aac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode"))]
#[cfg(any(feature = "aac-decode", feature = "alac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode"))]
mod pcm;
#[cfg(feature = "peaks")]
pub mod peaks;
#[cfg(feature = "decode-native")]
mod read;
#[cfg(any(feature = "aac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode"))]
#[cfg(any(feature = "aac-decode", feature = "alac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode"))]
mod track;
#[cfg(any(feature = "decode-native", feature = "pcm-encode"))]
mod wav;
@@ -58,7 +61,7 @@ pub use layout::{ChannelLayout, Speaker};
pub use read::SharedSource;
#[cfg(feature = "decode-native")]
pub use read::{DEFAULT_MAX_SAMPLES, DecodeOptions, Decoder, TrackInfo, decode, decode_with, detect_container, probe, probe_with, tracks, tracks_of};
#[cfg(any(feature = "aac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode"))]
#[cfg(any(feature = "aac-decode", feature = "alac-decode", feature = "opus-decode", feature = "ac3-decode", feature = "mpa-decode"))]
pub use track::{LOOKAHEAD_PACKETS, TrackDecoder};
#[cfg(feature = "pcm-encode")]
pub use write::wav_float_header;
@@ -135,6 +138,17 @@ impl From<mpa::Error> for AudioError {
}
}
#[cfg(any(feature = "alac-decode", feature = "alac-encode"))]
impl From<alac::Error> for AudioError {
fn from(e: alac::Error) -> Self {
match e {
alac::Error::Unsupported(_) => AudioError::Unsupported(e.to_string()),
alac::Error::InvalidInput(_) => AudioError::Encode(e.to_string()),
_ => AudioError::Malformed(e.to_string()),
}
}
}
/// The file formats [`encode`] writes. Each needs its feature: WAV and AIFF `pcm-encode`, FLAC
/// `flac-encode`; asking for one that is not built returns [`AudioError::Unsupported`].
#[non_exhaustive]
+131 -13
View File
@@ -177,10 +177,9 @@ impl Packets<'_> {
*pending = Some(p);
// Timestamps are rounded to the file's timestamp scale (often 1 ms), so the
// packet's index places it exactly where packets have a known length.
let by_index = point.sample.and_then(|k| frames.start(src, k));
Ok(by_index.unwrap_or_else(|| {
rescale_round(u64::try_from(pts_ns.saturating_sub(*first_ns)).unwrap_or(0), 1_000_000_000, u64::from(*rate))
}))
let timed = rescale_round(u64::try_from(pts_ns.saturating_sub(*first_ns)).unwrap_or(0), 1_000_000_000, u64::from(*rate));
let start = point.sample.and_then(|k| frames.start(src, k, timed, |i| demux.read_sample(*track, i).ok()));
Ok(start.unwrap_or(timed))
}
Some(Err(e)) => Err(e),
None => Ok(frame),
@@ -192,8 +191,12 @@ impl Packets<'_> {
/// How the decoded-timeline start of a Matroska packet is found from its index.
enum MkvFrames {
/// Every packet decodes to this many frames.
Constant(u64),
/// Every packet decodes to `frames` frames, except where a packet's timestamp is more than
/// `slack` frames (the timestamps' rounding) from that place: after a short packet. Then the
/// packets before it are counted with `count` (reading each packet's frames from its header,
/// into `starts`, the starts of the packets counted so far), or without one the timestamp
/// places it.
Constant { frames: u64, slack: u64, count: Option<crate::track::PacketFrames>, starts: Vec<u64> },
/// PCM: the stored sizes of the track's packets, and the bytes per frame.
Pcm { sizes: Vec<u32>, frame_bytes: u64 },
/// E-AC-3, whose packets can differ in length: each packet's length from the headers of its
@@ -208,10 +211,23 @@ enum MkvFrames {
}
impl MkvFrames {
/// The decoded-timeline start of packet `k`, when the packets say.
fn start(&mut self, src: &Src<'_>, k: usize) -> Option<u64> {
/// The decoded-timeline start of packet `k`, when the packets say (`src`: the file, for
/// packet headers; `timed`: the start its timestamp gives; `read`: packet `i`'s data).
fn start(&mut self, src: &Src<'_>, k: usize, timed: u64, mut read: impl FnMut(usize) -> Option<Vec<u8>>) -> Option<u64> {
match self {
MkvFrames::Constant(n) => Some(k as u64 * *n),
MkvFrames::Constant { frames, slack, count, starts } => {
let even = (k as u64).saturating_mul(*frames);
if even.abs_diff(timed) <= *slack {
return Some(even);
}
let count = (*count)?;
while starts.len() <= k {
let last = starts.last().copied().unwrap_or(0);
let n = read(starts.len() - 1).and_then(|p| count(&p, *frames)).unwrap_or(*frames);
starts.push(last.saturating_add(n));
}
starts.get(k).copied()
}
MkvFrames::Pcm { sizes, frame_bytes } => Some(sizes.iter().take(k).map(|&s| u64::from(s)).sum::<u64>() / *frame_bytes),
MkvFrames::Eac3 { samples, prefix, starts } => {
// Headers are read only as far as packet `k`; a later seek extends the list. A
@@ -858,11 +874,17 @@ fn open_mkv<'a>(src: &Src<'a>, container: Container, track: Option<usize>) -> Op
(0..t.samples.len().min(crate::track::LOOKAHEAD_PACKETS)).filter_map(|i| demux.read_sample(track, i).ok()).collect();
let probe = TrackDecoder::new(&params, &lookahead.iter().map(Vec::as_slice).collect::<Vec<_>>()).ok()?;
let rate = probe.sample_rate();
let frames_of = match (probe.frames_per_packet(), params.pcm) {
// Packet starts come from the packets where they can: block timestamps are rounded (to
// milliseconds by default). ALAC packets are all its frame length except the last, so every
// start is a multiple of it, unless a short packet came earlier: then the timestamp is further
// from that multiple than its rounding allows, and the earlier packets' headers are counted.
let (num, den) = t.timebase;
let tick = rescale_round(num, den.max(1), u64::from(rate)).saturating_add(1);
let frames_of = match (probe.full_packet_frames(), params.pcm) {
_ if probe.codec() == CodecId::Eac3 => {
MkvFrames::Eac3 { samples: t.samples.iter().map(|s| (s.offset, s.size)).collect(), prefix: t.frame_prefix(0), starts: Vec::new() }
}
(Some(n), _) => MkvFrames::Constant(n as u64),
(Some(n), _) => MkvFrames::Constant { frames: n as u64, slack: tick, count: probe.packet_frames_reader(), starts: vec![0] },
(None, Some(pcm)) => {
let frame_bytes = u64::from(pcm.bits.div_ceil(8)) * u64::from(params.channels.unwrap_or(1).max(1));
MkvFrames::Pcm { sizes: t.samples.iter().map(|s| s.size).collect(), frame_bytes: frame_bytes.max(1) }
@@ -878,7 +900,6 @@ fn open_mkv<'a>(src: &Src<'a>, container: Container, track: Option<usize>) -> Op
// and the presentation starts `CodecDelay` later. Both are counted in timestamp ticks, the
// delay rounded to one, so a file whose first frame is stored at 0 presents from exactly 0
// although its timestamps are rounded (AAC's 1024-frame priming is 21.33 ms, stored as -21).
let (num, den) = t.timebase;
let last = t.samples.iter().max_by_key(|s| s.pts);
let delay_ticks = i64::try_from(rescale_round(t.codec_delay_ns.saturating_mul(den), num.saturating_mul(1_000_000_000), 1)).unwrap_or(i64::MAX);
let start = t.samples.iter().map(|s| s.pts).min().unwrap_or(0).saturating_add(delay_ticks);
@@ -888,7 +909,7 @@ fn open_mkv<'a>(src: &Src<'a>, container: Container, track: Option<usize>) -> Op
} else {
// ffmpeg 6 writes AAC and E-AC-3 without block durations, and records the end in a tag.
let n = match &frames_of {
MkvFrames::Constant(n) => *n,
MkvFrames::Constant { frames: n, .. } => *n,
MkvFrames::Eac3 { prefix, .. } => eac3_packet_frames(src, prefix, s.offset, s.size)? as u64,
_ => return None,
};
@@ -1311,6 +1332,103 @@ mod adts_tests {
let opened = Decoder::open(Failing { bytes, good: 300_000 }, Some("aac"), &DecodeOptions::default());
assert!(matches!(opened, Err(AudioError::Io(_))), "{:?}", opened.err());
}
/// An escaped (uncompressed) stereo 16-bit ALAC packet of `chunk`, partial when shorter than
/// `frame_length`.
fn alac_escaped_stereo(chunk: &[[i16; 2]], frame_length: usize) -> Vec<u8> {
let mut w = craft_media::bitstream::BitWriter::new();
let partial = chunk.len() != frame_length;
for (value, bits) in [(1, 3), (0, 4), (0, 12), (u32::from(partial) << 3 | 1, 4)] {
w.write_bits(value, bits);
}
if partial {
w.write_bits(chunk.len() as u32, 32);
}
for f in chunk {
w.write_bits(u32::from(f[0] as u16), 16);
w.write_bits(u32::from(f[1] as u16), 16);
}
w.write_bits(7, 3);
w.finish()
}
/// ALAC in MP4 decodes natively (without symphonia this is the only path): every sample,
/// the short last packet included, and a seek lands on the exact frame.
#[test]
fn alac_in_mp4_decodes_and_seeks_natively() {
use craft_media::isobmff::{Brand, CodecConfig, FourCc, Mp4Writer, SampleEntry, TrackConfig, WriteSample, WriterOptions};
// Escaped (uncompressed) stereo 16-bit packets of 4 frames, the last one of 2.
let frames: Vec<[i16; 2]> = (0..10).map(|i| [i * 100 - 300, -(i * 77)]).collect();
let packet = |chunk: &[[i16; 2]]| alac_escaped_stereo(chunk, 4);
let cookie = crate::alac::SpecificConfig::new(48_000, 2, 16, 4).to_bytes();
let entry = SampleEntry::audio(FourCc(*b"alac"), CodecConfig::Alac { cookie }, 2, 48_000.0, 16);
let mut w = Mp4Writer::new(std::io::Cursor::new(Vec::new()), WriterOptions::new(Brand::Mp4)).unwrap();
let track = w.add_track(TrackConfig::new(entry, 48_000)).unwrap();
for chunk in frames.chunks(4) {
w.write_sample(track, WriteSample { data: &packet(chunk), duration: chunk.len() as u32, composition_offset: 0, is_sync: true }).unwrap();
}
let file = w.finish().unwrap().into_inner();
let (info, buf) = decode(&file, None).unwrap();
assert_eq!((info.codec, info.sample_format, info.sample_rate, info.frames), (crate::CodecId::Alac, crate::SampleFormat::Int16, 48_000, 10));
let ints: Vec<[i16; 2]> = (0..buf.frames()).map(|i| [0, 1].map(|c| (buf.channels[c][i] * 32768.0) as i16)).collect();
assert_eq!(ints, frames);
let mut d = Decoder::from_bytes(&file, None, &DecodeOptions::default()).unwrap();
d.seek(5).unwrap();
let block = d.next_block().unwrap().unwrap();
assert_eq!((block.channels[0][0] * 32768.0) as i16, frames[5][0]);
}
/// ALAC in Matroska seeks to the exact frame: packet starts come from the frame length, not
/// from the millisecond timestamps (packet 1 starts at frame 4096, stored as 85 ms = 4080).
#[test]
fn alac_in_matroska_seeks_to_the_exact_frame() {
use craft_media::matroska::{MkvWriter, MuxOptions, TrackKind, TrackSpec};
let frames: Vec<[i16; 2]> = (0..10_000).map(|i| [i as i16, -(i as i16)]).collect();
let mut spec = TrackSpec::new(TrackKind::Audio, "A_ALAC");
spec.codec_private = crate::alac::SpecificConfig::new(48_000, 2, 16, 4096).to_bytes();
spec.audio = Some((48_000.0, 2, Some(16)));
let mut m = MkvWriter::new(std::io::Cursor::new(Vec::new()), vec![spec], MuxOptions::default()).unwrap();
for (k, chunk) in frames.chunks(4096).enumerate() {
let pts_ns = (k * 4096) as i64 * 1_000_000_000 / 48_000;
let duration_ns = chunk.len() as u64 * 1_000_000_000 / 48_000;
m.write_frame(0, pts_ns, true, &alac_escaped_stereo(chunk, 4096), Some(duration_ns)).unwrap();
}
let file = m.finish().unwrap().into_inner();
let (info, buf) = decode(&file, Some("mka")).unwrap();
assert_eq!((info.codec, buf.frames()), (crate::CodecId::Alac, 10_000));
let mut d = Decoder::from_bytes(&file, Some("mka"), &DecodeOptions::default()).unwrap();
for target in [5000u64, 4096, 8191, 9999] {
d.seek(target).unwrap();
let block = d.next_block().unwrap().unwrap();
assert_eq!((block.channels[0][0] * 32768.0).round() as i64, target as i64, "seek to {target}");
}
}
/// A short ALAC packet before the end moves every later packet: seeks after it still land on
/// the exact frame.
#[test]
fn alac_in_matroska_seeks_past_a_short_packet() {
use craft_media::matroska::{MkvWriter, MuxOptions, TrackKind, TrackSpec};
let frames: Vec<[i16; 2]> = (0..12_000).map(|i| [i as i16, -(i as i16)]).collect();
let mut spec = TrackSpec::new(TrackKind::Audio, "A_ALAC");
spec.codec_private = crate::alac::SpecificConfig::new(48_000, 2, 16, 4096).to_bytes();
spec.audio = Some((48_000.0, 2, Some(16)));
let mut m = MkvWriter::new(std::io::Cursor::new(Vec::new()), vec![spec], MuxOptions::default()).unwrap();
let mut start = 0;
for len in [4096, 1000, 4096, 2808] {
let chunk = &frames[start..start + len];
let pts_ns = start as i64 * 1_000_000_000 / 48_000;
m.write_frame(0, pts_ns, true, &alac_escaped_stereo(chunk, 4096), Some(len as u64 * 1_000_000_000 / 48_000)).unwrap();
start += len;
}
let file = m.finish().unwrap().into_inner();
let mut d = Decoder::from_bytes(&file, Some("mka"), &DecodeOptions::default()).unwrap();
for target in [6000u64, 5096, 9191, 11_999, 100] {
d.seek(target).unwrap();
let block = d.next_block().unwrap().unwrap();
assert_eq!((block.channels[0][0] * 32768.0).round() as i64, target as i64, "seek to {target}");
}
}
}
#[cfg(all(test, feature = "ac3-decode"))]
+141 -12
View File
@@ -6,6 +6,8 @@
use crate::aac;
#[cfg(feature = "ac3-decode")]
use crate::ac3;
#[cfg(feature = "alac-decode")]
use crate::alac;
#[cfg(feature = "mpa-decode")]
use crate::mpa;
#[cfg(feature = "opus-decode")]
@@ -26,6 +28,13 @@ enum Inner {
/// The format ADTS packets must keep, when their headers gave the configuration.
adts_format: Option<AdtsFormat>,
},
#[cfg(feature = "alac-decode")]
Alac {
dec: alac::Decoder,
order: Option<&'static [usize]>,
/// 2^-(bit depth - 1): full scale to ±1.
scale: f64,
},
/// The decoder, and what the packets read so far say of the track's packets.
#[cfg(feature = "ac3-decode")]
Ac3(Box<ac3::Decoder>, Ac3Packets),
@@ -42,14 +51,18 @@ enum Inner {
},
}
/// Reads the frames a packet holds from its data, given the frames of a full packet.
#[cfg(feature = "decode-native")]
pub(crate) type PacketFrames = fn(&[u8], u64) -> Option<u64>;
/// Decodes the packets of one audio track to planar `f32`, in WAV channel order when
/// [`TrackDecoder::layout`] is ordered.
///
/// It takes the track's [`CodecParams`] (from `isobmff::Track::codec_params`,
/// `matroska::Track::codec_params`, `ogg::Stream::codec_params`, or built by hand for another
/// container) and decodes AAC (LC; HE-AAC as its upsampled LC core), Opus, AC-3/E-AC-3, MP1/MP2
/// and PCM. Container timing (AAC priming, Opus pre-skip, end trimming) is the caller's: every
/// decoded sample is returned.
/// container) and decodes AAC (LC; HE-AAC as its upsampled LC core), ALAC, Opus, AC-3/E-AC-3,
/// MP1/MP2 and PCM. Container timing (AAC priming, Opus pre-skip, end trimming) is the caller's:
/// every decoded sample is returned.
///
/// ```no_run
/// # fn demo(params: &craft_audio::CodecParams, packets: &[Vec<u8>]) -> craft_audio::Result<()> {
@@ -101,10 +114,14 @@ impl TrackDecoder {
/// Opus with empty `extradata` and 1 or 2 `channels` (RTP, MPEG-TS) needs no `OpusHead`. Other
/// missing configuration is [`AudioError::Malformed`].
///
/// Codecs without a native packet decoder yet (MP3, FLAC, ALAC, Vorbis) are
/// ALAC needs its magic cookie in `extradata`.
///
/// Codecs without a native packet decoder yet (MP3, FLAC, Vorbis) are
/// [`AudioError::Unsupported`]; `craft_audio::decode` decodes them through symphonia when the
/// `symphonia` feature is on.
pub fn new(params: &CodecParams, lookahead: &[&[u8]]) -> Result<TrackDecoder> {
#[cfg(not(feature = "aac-decode"))]
let _ = lookahead;
let rate = params.sample_rate.unwrap_or(0);
let channels = usize::from(params.channels.unwrap_or(0));
// AC-3 and MPEG audio read their parameters from the first frame; AAC reads the lookahead;
@@ -144,6 +161,32 @@ impl TrackDecoder {
let order = layout.is_ordered().then(|| aac::wav_order(cfg)).flatten();
(Inner::Aac { dec: Box::new(dec), up, order, adts_format }, codec, out, layout, SampleFormat::Compressed)
}
#[cfg(feature = "alac-decode")]
CodecId::Alac => {
if params.extradata.is_empty() {
return Err(AudioError::Malformed("ALAC track without a magic cookie (extradata)".into()));
}
let dec = alac::Decoder::new(&params.extradata)?;
let cfg = *dec.config();
// The cookie's rate is the codec's; the container's stands in when it is 0.
let rate = if cfg.sample_rate > 0 { cfg.sample_rate } else { rate };
if rate == 0 {
return Err(AudioError::Malformed("ALAC track without a sample rate in its cookie or container".into()));
}
let (layout, order) = if cfg.has_standard_layout() {
(ChannelLayout::from_mask(alac::channel_mask(cfg.channels)), alac::wav_order(cfg.channels))
} else {
(ChannelLayout::unordered(u16::from(cfg.channels)), None)
};
let sample_format = match cfg.bit_depth {
1..=8 => SampleFormat::Int8,
9..=16 => SampleFormat::Int16,
17..=24 => SampleFormat::Int24,
_ => SampleFormat::Int32,
};
let scale = 2f64.powi(1 - i32::from(cfg.bit_depth));
(Inner::Alac { dec, order, scale }, CodecId::Alac, rate, layout, sample_format)
}
#[cfg(feature = "ac3-decode")]
CodecId::Ac3 | CodecId::Eac3 => match ac3::parse_header(first) {
Ok(h) => {
@@ -265,6 +308,12 @@ impl TrackDecoder {
}
reorder(out, *order)
}
#[cfg(feature = "alac-decode")]
Inner::Alac { dec, order, scale } => {
let decoded = dec.decode(packet)?;
let floats = decoded.into_iter().map(|c| c.into_iter().map(|v| (f64::from(v) * *scale) as f32).collect()).collect();
reorder(floats, *order)
}
#[cfg(feature = "mpa-decode")]
Inner::Mpa(d) => {
let mut out: Vec<Vec<f32>> = Vec::new();
@@ -359,6 +408,12 @@ impl TrackDecoder {
None => Some(per_unit),
}
}
// A damaged packet whose header survives says its length; otherwise a packet is as
// long as the last one (a full one at the start).
#[cfg(feature = "alac-decode")]
Inner::Alac { dec, .. } => {
Some(dec.packet_frames(data).unwrap_or(if self.last_frames > 0 { self.last_frames } else { dec.config().frame_length as usize }))
}
#[cfg(feature = "ac3-decode")]
Inner::Ac3(..) => Some(ac3_packet_frames(data)),
#[cfg(feature = "mpa-decode")]
@@ -372,12 +427,15 @@ impl TrackDecoder {
/// The frames every packet decodes to, for codecs whose packets all have one length: AAC
/// (1024, 2048 for HE-AAC; a packet of several ADTS frames decodes to that many times this) and
/// AC-3 / E-AC-3 (the frames of the last decoded packet, else of the first packet's primary
/// syncframes, else 1536) and MP1/MP2 (one frame per packet); `None` for Opus and PCM, and for
/// AC-3 / E-AC-3 whose packets read so far differ in length.
/// syncframes, else 1536) and MP1/MP2 (one frame per packet); `None` for Opus, PCM and ALAC
/// (whose last packet is shorter; the container's timing says), and for AC-3 / E-AC-3 whose
/// packets read so far differ in length.
pub fn frames_per_packet(&self) -> Option<usize> {
match &self.inner {
#[cfg(feature = "aac-decode")]
Inner::Aac { up, .. } => Some(aac::FRAME_LEN * if up.is_some() { 2 } else { 1 }),
#[cfg(feature = "alac-decode")]
Inner::Alac { .. } => None,
#[cfg(feature = "ac3-decode")]
Inner::Ac3(_, shape) if shape.varies => None,
#[cfg(feature = "ac3-decode")]
@@ -393,15 +451,40 @@ impl TrackDecoder {
}
}
/// The frames of a full packet, for codecs whose packets all have that length except ones
/// at the end of the stream: ALAC's frame length (only the last packet is shorter). Where
/// [`Self::frames_per_packet`] is `Some`, the same value.
#[cfg(feature = "decode-native")]
pub(crate) fn full_packet_frames(&self) -> Option<usize> {
match &self.inner {
#[cfg(feature = "alac-decode")]
Inner::Alac { dec, .. } => Some(dec.config().frame_length as usize),
_ => self.frames_per_packet(),
}
}
/// How many frames a packet holds, read from the packet alone, for codecs whose packets
/// say: ALAC's headers (given the frame length).
#[cfg(feature = "decode-native")]
pub(crate) fn packet_frames_reader(&self) -> Option<PacketFrames> {
match &self.inner {
#[cfg(feature = "alac-decode")]
Inner::Alac { .. } => Some(crate::alac::header_frames),
_ => None,
}
}
/// Frames to decode before a seek target and drop, so the decoder has settled when the target
/// is reached: two AAC frames, 80 ms of Opus (RFC 7845 §4.6), one AC-3 or MPEG audio frame
/// (their filter banks overlap one frame), none for PCM. E-AC-3 syncframes of fewer than six
/// blocks may reuse exponents from any syncframe before them, so once one has been read the
/// stream decodes from its start (`usize::MAX`).
/// (their filter banks overlap one frame), none for ALAC (its packets decode independently)
/// and PCM. E-AC-3 syncframes of fewer than six blocks may reuse exponents from any syncframe
/// before them, so once one has been read the stream decodes from its start (`usize::MAX`).
pub fn preroll(&self) -> usize {
match &self.inner {
#[cfg(feature = "aac-decode")]
Inner::Aac { up, .. } => 2 * aac::FRAME_LEN * if up.is_some() { 2 } else { 1 },
#[cfg(feature = "alac-decode")]
Inner::Alac { .. } => 0,
#[cfg(feature = "ac3-decode")]
Inner::Ac3(_, shape) if shape.short => usize::MAX,
#[cfg(feature = "ac3-decode")]
@@ -428,6 +511,8 @@ impl TrackDecoder {
up.reset();
}
}
#[cfg(feature = "alac-decode")]
Inner::Alac { .. } => {}
#[cfg(feature = "ac3-decode")]
Inner::Ac3(d, _) => d.reset(),
#[cfg(feature = "opus-decode")]
@@ -442,6 +527,7 @@ impl TrackDecoder {
fn unsupported(codec: CodecId) -> AudioError {
let feature = match codec {
CodecId::Aac | CodecId::HeAac => Some("aac-decode"),
CodecId::Alac => Some("alac-decode"),
CodecId::Ac3 | CodecId::Eac3 => Some("ac3-decode"),
CodecId::Opus => Some("opus-decode"),
CodecId::Mp1 | CodecId::Mp2 => Some("mpa-decode"),
@@ -450,7 +536,7 @@ fn unsupported(codec: CodecId) -> AudioError {
match feature {
// A codec this crate decodes, in a build without its feature.
Some(f) => AudioError::Unsupported(format!("decoding {} needs the `{f}` feature", codec.name())),
None if cfg!(feature = "symphonia") && matches!(codec, CodecId::Mp3 | CodecId::Flac | CodecId::Alac | CodecId::Vorbis) => {
None if cfg!(feature = "symphonia") && matches!(codec, CodecId::Mp3 | CodecId::Flac | CodecId::Vorbis) => {
AudioError::Unsupported(format!("no native {} packet decoder yet (craft_audio::decode reads it through symphonia)", codec.name()))
}
None => AudioError::Unsupported(format!("no native {} packet decoder yet", codec.name())),
@@ -459,7 +545,7 @@ fn unsupported(codec: CodecId) -> AudioError {
/// `channels` reordered by `order` (output channel → decoded channel); unchanged when the counts
/// differ.
#[cfg(any(feature = "aac-decode", feature = "opus-decode"))]
#[cfg(any(feature = "aac-decode", feature = "alac-decode", feature = "opus-decode"))]
fn reorder(mut channels: Vec<Vec<f32>>, order: Option<&[usize]>) -> Vec<Vec<f32>> {
match order {
Some(order) if order.len() == channels.len() => order.iter().map(|&c| channels.get_mut(c).map(std::mem::take).unwrap_or_default()).collect(),
@@ -1056,13 +1142,56 @@ mod tests {
#[test]
fn codecs_without_a_packet_decoder_are_unsupported() {
for codec in [CodecId::Flac, CodecId::Vorbis, CodecId::Alac, CodecId::Mp3, CodecId::H264] {
for codec in [CodecId::Flac, CodecId::Vorbis, CodecId::Mp3, CodecId::H264] {
assert!(matches!(TrackDecoder::new(&CodecParams::new(codec), &[]), Err(AudioError::Unsupported(_))), "{codec:?}");
}
// A codec whose feature is off names the feature.
#[cfg(not(feature = "aac-decode"))]
assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Aac), &[]), Err(AudioError::Unsupported(m)) if m.contains("aac-decode")));
#[cfg(not(feature = "alac-decode"))]
assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Alac), &[]), Err(AudioError::Unsupported(m)) if m.contains("alac-decode")));
#[cfg(not(feature = "opus-decode"))]
assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Opus), &[]), Err(AudioError::Unsupported(m)) if m.contains("opus-decode")));
// PCM needs its sample format.
assert!(TrackDecoder::new(&CodecParams::new(CodecId::Pcm).with_channels(2).with_sample_rate(48_000), &[]).is_err());
}
#[cfg(feature = "alac-decode")]
#[test]
fn alac_needs_a_sample_rate() {
let cookie = alac::SpecificConfig::new(0, 2, 16, 4096).to_bytes();
assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Alac).with_extradata(cookie.clone()), &[]), Err(AudioError::Malformed(_))));
let dec = TrackDecoder::new(&CodecParams::new(CodecId::Alac).with_extradata(cookie).with_sample_rate(32_000), &[]).unwrap();
assert_eq!(dec.sample_rate(), 32_000);
}
#[cfg(feature = "alac-decode")]
#[test]
fn alac_needs_its_cookie() {
assert!(matches!(TrackDecoder::new(&CodecParams::new(CodecId::Alac), &[]), Err(AudioError::Malformed(_))));
let bad = CodecParams::new(CodecId::Alac).with_extradata(vec![0; 10]);
assert!(matches!(TrackDecoder::new(&bad, &[]), Err(AudioError::Malformed(_))));
}
/// ALAC 5.1 comes out in WAV order at full scale, and a lost packet is as long as the last.
#[cfg(all(feature = "alac-decode", feature = "alac-encode"))]
#[test]
fn alac_decodes_in_wav_order() {
// ALAC order C L R Ls Rs LFE: channel i holds the constant i * 1000.
let input: Vec<Vec<i32>> = (0..6).map(|i| vec![i * 1000; 300]).collect();
let mut enc = alac::Encoder::new(alac::EncoderConfig::new(48_000, 6, 16).with_frame_length(1024)).unwrap();
let packet = enc.encode(&input.iter().map(Vec::as_slice).collect::<Vec<_>>()).unwrap();
let params = CodecParams::new(CodecId::Alac).with_extradata(enc.cookie()).with_sample_rate(44_100);
let mut dec = TrackDecoder::new(&params, &[]).unwrap();
// a damaged packet whose header survives is concealed with its own length
assert_eq!(dec.conceal(Some(&packet[..8])).unwrap().frames(), 300);
assert_eq!((dec.sample_rate(), dec.layout(), dec.sample_format()), (48_000, ChannelLayout::SURROUND_5_1, SampleFormat::Int16));
assert_eq!(dec.conceal(None).unwrap().frames(), 1024);
let out = dec.decode(&packet).unwrap();
// WAV order L R C LFE Ls Rs
let firsts: Vec<f32> = out.channels.iter().map(|c| c[0] * 32768.0).collect();
assert_eq!(firsts, vec![1000.0, 2000.0, 0.0, 5000.0, 3000.0, 4000.0]);
assert_eq!(dec.conceal(None).unwrap().frames(), 300);
assert_eq!((dec.frames_per_packet(), dec.preroll()), (None, 0));
}
}
+2 -1
View File
@@ -305,7 +305,8 @@ fn packets_longer_than_any_codec_uses_are_refused() {
// read, and nothing is allocated from it. symphonia read it, so SoundCraft refused the file.
let (_, buf) = decode(&mp4(480, &lpcm(1 << 21), |_, a| a), None).unwrap();
assert_eq!(buf.frames(), 480);
// ALAC still goes to symphonia, behind the same guard.
// ALAC: a cookie declaring such packets is refused by the native decoder and then by the
// symphonia path's guard.
assert!(matches!(decode(&mp4(480, &alac(1 << 21), |_, a| a), None), Err(AudioError::Malformed(m)) if m.contains("declares packets")));
}
+155
View File
@@ -0,0 +1,155 @@
//! ALAC against ffmpeg (oracle; skipped when ffmpeg is absent): ffmpeg's ALAC files decode to the
//! exact input samples through `craft_audio::decode`, and ffmpeg decodes our encoder's packets,
//! muxed into M4A, to the exact input samples. Both in WAV channel order.
use craft_audio::alac::{Encoder, EncoderConfig, alac_order};
use craft_audio::craft_media::isobmff::{Brand, CodecConfig, FourCc, Mp4Writer, SampleEntry, TrackConfig, WriteSample, WriterOptions};
use craft_audio::{CodecId, decode};
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::process::Command;
fn dir() -> PathBuf {
crate::testkit::fixtures_dir("alac")
}
/// The ffmpeg channel layout ALAC uses for `channels`.
fn layout(channels: usize) -> &'static str {
["mono", "stereo", "3.0", "4.0", "5.0", "5.1", "6.1(back)", "7.1(wide)"][channels - 1]
}
/// `frames` frames of `channels` channels at `bits` bits, in WAV order: tones, noise bursts,
/// silence and full-scale peaks.
fn input(channels: usize, bits: u32, frames: usize) -> Vec<Vec<i32>> {
let max = ((1i64 << (bits - 1)) - 1) as f64;
let mut seed = 0x2545_F491u32;
(0..channels)
.map(|c| {
(0..frames)
.map(|i| {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
let noise = seed as f64 / f64::from(u32::MAX) * 2.0 - 1.0;
let tone = (i as f64 * (200.0 + 50.0 * c as f64) / 48_000.0 * std::f64::consts::TAU).sin();
let x = match (i / 2500) % 4 {
0 => tone * 0.7 + noise * 0.001,
1 => noise * 0.5,
2 => 0.0,
_ => tone.signum(),
};
(x * max).round().clamp(-max - 1.0, max) as i32
})
.collect()
})
.collect()
}
/// Interleaved s32le with each sample at the top of the word.
fn s32le(planar: &[Vec<i32>], bits: u32) -> Vec<u8> {
let mut out = Vec::new();
for i in 0..planar[0].len() {
for c in planar {
out.extend_from_slice(&(c[i] << (32 - bits)).to_le_bytes());
}
}
out
}
/// ffmpeg's decode of `file` as planar samples at `bits` bits.
fn ffmpeg_decode(ff: &Path, file: &Path, channels: usize, bits: u32) -> Vec<Vec<i32>> {
let out = Command::new(ff).args(["-v", "error", "-i"]).arg(file).args(["-f", "s32le", "-"]).output().unwrap();
assert!(out.status.success() && out.stderr.is_empty(), "{}", String::from_utf8_lossy(&out.stderr));
let mut planar = vec![Vec::new(); channels];
for (i, w) in out.stdout.as_chunks::<4>().0.iter().enumerate() {
planar[i % channels].push(i32::from_le_bytes([w[0], w[1], w[2], w[3]]) >> (32 - bits));
}
planar
}
/// Our encoder's packets for `wav` (WAV order) muxed into M4A.
fn our_m4a(wav: &[Vec<i32>], bits: u32, frame_length: u32) -> Vec<u8> {
let channels = wav.len();
let alac: Vec<&[i32]> = match alac_order(channels as u8) {
Some(order) => order.iter().map(|&w| wav[w].as_slice()).collect(),
None => wav.iter().map(Vec::as_slice).collect(),
};
let mut enc = Encoder::new(EncoderConfig::new(48_000, channels as u8, bits as u8).with_frame_length(frame_length)).unwrap();
let frames = wav[0].len();
let packets: Vec<(Vec<u8>, u32)> = (0..frames)
.step_by(frame_length as usize)
.map(|s| {
let e = (s + frame_length as usize).min(frames);
(enc.encode(&alac.iter().map(|c| &c[s..e]).collect::<Vec<_>>()).unwrap(), (e - s) as u32)
})
.collect();
let entry = SampleEntry::audio(FourCc(*b"alac"), CodecConfig::Alac { cookie: enc.cookie() }, channels as u32, 48_000.0, bits as u16);
let mut w = Mp4Writer::new(Cursor::new(Vec::new()), WriterOptions::new(Brand::Mp4)).unwrap();
let t = w.add_track(TrackConfig::new(entry, 48_000)).unwrap();
for (data, duration) in &packets {
w.write_sample(t, WriteSample { data, duration: *duration, composition_offset: 0, is_sync: true }).unwrap();
}
w.finish().unwrap().into_inner()
}
/// `craft_audio::decode` of an ALAC file, back to integers (exact up to 24 bits, which `f32`
/// holds).
fn our_decode(bytes: &[u8], bits: u32) -> Vec<Vec<i32>> {
let (info, buf) = decode(bytes, Some("m4a")).unwrap();
assert_eq!((info.codec, info.damaged), (CodecId::Alac, 0));
let scale = 2f64.powi(bits as i32 - 1);
buf.channels.iter().map(|c| c.iter().map(|&s| (f64::from(s) * scale) as i32).collect()).collect()
}
#[test]
fn ffmpeg_alac_decodes_exactly() {
let Some(ff) = crate::testkit::ffmpeg_or_skip("alac oracle") else { return };
for (channels, bits) in [(1, 16), (2, 16), (2, 24), (3, 16), (4, 16), (5, 24), (6, 24), (7, 16), (8, 16)] {
let wav = input(channels, bits, 12_345);
let name = format!("ffmpeg_{channels}ch_{bits}");
let raw = dir().join(format!("{name}.s32"));
let m4a = dir().join(format!("{name}.m4a"));
std::fs::write(&raw, s32le(&wav, bits)).unwrap();
let fmt = if bits == 16 { "s16p" } else { "s32p" };
let st = Command::new(&ff)
.args(["-v", "error", "-y", "-f", "s32le", "-ar", "48000", "-ch_layout", layout(channels), "-i"])
.arg(&raw)
.args(["-c:a", "alac", "-sample_fmt", fmt])
.arg(&m4a)
.status()
.unwrap();
assert!(st.success(), "{name}: ffmpeg encode");
let ours = our_decode(&std::fs::read(&m4a).unwrap(), bits);
assert!(ours == wav, "{name}: decoded samples differ from the input");
}
}
/// 32-bit streams are left out: ffmpeg does not decode escaped (uncompressed) 32-bit stereo
/// frames ("not yet implemented"), which the noise in the input produces; Apple's decoder does.
#[test]
fn ffmpeg_decodes_our_alac_exactly() {
let Some(ff) = crate::testkit::ffmpeg_or_skip("alac oracle") else { return };
for (channels, bits, frame) in [
(1, 16, 4096),
(2, 16, 4096),
(2, 20, 4096),
(2, 24, 1000),
(3, 16, 4096),
(4, 24, 4096),
(5, 16, 4096),
(6, 24, 4096),
(7, 16, 4096),
(8, 24, 4096),
] {
let wav = input(channels, bits, 10_007);
let m4a = our_m4a(&wav, bits, frame);
let path = dir().join(format!("ours_{channels}ch_{bits}.m4a"));
std::fs::write(&path, &m4a).unwrap();
let theirs = ffmpeg_decode(&ff, &path, channels, bits);
assert!(theirs == wav, "{channels} ch, {bits} bits: ffmpeg's decode differs from the input");
// and our own reader on the same file
if bits <= 24 {
assert!(our_decode(&m4a, bits) == wav, "{channels} ch, {bits} bits: our decode differs");
}
}
}
+1
View File
@@ -9,6 +9,7 @@ mod testkit;
mod aac;
mod ac3_oracle;
mod alac;
mod concealment;
mod mpa_oracle;
mod opus;