Files
filmcraft/crates/aac/tools/derive.py
Brandon Thomas c2872fbd8d M6.2: filmcraft-aac — clean-room AAC-LC encoder and decoder
Encoder: transient-driven window switching (long/start/short/stop, grouping),
sine/KBD MDCT, MDCT-domain psychoacoustic model (Bark spreading, SFM tonality,
ATH, PE), TNS, per-band M/S, lambda rate-distortion loop with measured-error
scalefactor search and DP sectioning over all codebooks, bit reservoir with
fill elements (exact CBR), VBR quality scale, 1-8 channels (configs 1-7 + PCE
for 7ch), ASC + optional ADTS, 1024-sample priming reported.

Decoder: full AAC-LC syntax incl. PNS (correlated M/S noise), intensity stereo,
TNS, M/S, pulse data, both window shapes, PCE/DSE/FIL, ADTS helpers; never
panics on malformed input.

Tests use ffmpeg only as an oracle: our streams decode cleanly in ffmpeg
(CBR within 0.1%), our decoder matches ffmpeg to ~1e-7 on ffmpeg-encoded
streams (8-96 kHz, mono..7.1, IS/MS/TNS), plus fuzzing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:30:13 -04:00

129 lines
3.9 KiB
Python

"""Black-box derivation of the AAC Huffman codebooks by probing the ffmpeg decoder (oracle only)."""
import sys, json, math
from concurrent.futures import ThreadPoolExecutor
from probe import frame, silent, run, fit
def unit(sf):
return 2.0 ** (0.25 * (sf - 100)) / (32768 * 1024)
PARAMS = { # cb: (dim, signed, lav)
1: (4, True, 1), 2: (4, True, 1), 3: (4, False, 2), 4: (4, False, 2),
5: (2, True, 4), 6: (2, True, 4), 7: (2, False, 7), 8: (2, False, 7),
9: (2, False, 12), 10: (2, False, 12), 11: (2, False, 16),
}
def analyse(y, err, sfs):
if err or len(y) < 2048:
return None
x, res, en = fit(y[:2048])
if en == 0:
return [0] * 12 if res == 0 else None
if res > 1e-6 * en:
return None
out = []
for i, v in enumerate(x):
u = unit(sfs[i // 4])
m = abs(v) / u
q = m ** 0.75
r = round(q)
if abs(q - r) > 0.02 * max(1, r):
return None
out.append(int(r) * (1 if v >= 0 else -1))
return out
G = 140
def query_spec(cb, S, k, e):
dim, signed, lav = PARAMS[cb]
ncopies = 12 // dim
bits = ''
for c in range(ncopies):
bits += S + ('0' if c % 2 == 0 else '1') * k + '00000' * e
y, err = run([frame(cb, '000', bits, G=G), silent(), silent()])
vals = analyse(y, err, [G, G, G])
if vals is None:
return None
vecs = [vals[c * dim:(c + 1) * dim] for c in range(ncopies)]
v = vecs[0]
for c in range(ncopies):
exp = v if (signed or c % 2 == 0) else [-a for a in v]
if vecs[c] != exp:
return None
if signed:
if any(abs(a) > lav for a in v):
return None
mod = 2 * lav + 1
idx = 0
for a in v:
idx = idx * mod + (a + lav)
return idx
if any(a < 0 for a in v):
return None
if sum(1 for a in v if a != 0) != k:
return None
if cb == 11:
if sum(1 for a in v if a == 16) != e:
return None
elif e:
return None
if any(a > lav for a in v):
return None
mod = lav + 1
idx = 0
for a in v:
idx = idx * mod + a
return idx
def query_sf(S):
Gs = 128
# spectral: cb1 (-1,0,0,0) = '10001' in each band
y, err = run([frame(1, S + '0' + S, '10001' * 3, G=Gs), silent(), silent()])
for d in range(-60, 61):
sfs = [Gs + d, Gs + d, Gs + 2 * d]
if not all(0 <= s <= 255 for s in sfs):
continue
vals = analyse(y, err, sfs)
if vals == [-1, 0, 0, 0] * 3:
return d + 60
return None
def hyps(cb):
dim, signed, lav = PARAMS[cb]
if signed:
return [(0, 0)]
hs = [(k, 0) for k in range(dim + 1)]
if cb == 11:
hs += [(1, 1), (2, 1), (2, 2)]
return hs
def derive(cb, maxlen=20):
leaves = {}
frontier = ['0', '1']
with ThreadPoolExecutor(14) as ex:
while frontier:
def q(S):
if cb == 0:
return S, query_sf(S)
for (k, e) in hyps(cb):
r = query_spec(cb, S, k, e)
if r is not None:
return S, r
return S, None
nxt = []
for S, r in ex.map(q, frontier):
if r is not None:
leaves[S] = r
elif len(S) < maxlen:
nxt += [S + '0', S + '1']
print(f'cb{cb}: depth {len(frontier[0])} frontier {len(frontier)} leaves {len(leaves)}', file=sys.stderr)
frontier = nxt
return leaves
if __name__ == '__main__':
cb = int(sys.argv[1])
leaves = derive(cb)
kraft = sum(2.0 ** -len(s) for s in leaves)
idxs = sorted(leaves.values())
print(f'cb{cb}: {len(leaves)} leaves, kraft {kraft}, unique idx {len(set(idxs))}, range {idxs[0]}..{idxs[-1]}', file=sys.stderr)
json.dump({s: i for s, i in leaves.items()}, open(f'cb{cb}.json', 'w'))