mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-25 10:37:34 -06:00
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>
129 lines
3.9 KiB
Python
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'))
|