Something went wrong. Try again.
An MPEG audio codec in Zig that decodes Layers I, II and III and encodes Layers II and III.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220#!/usr/bin/env python3# SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us># SPDX-License-Identifier: MIT
"""Encode a synthetic clip with this encoder, decode it with ffmpeg's decoder,and report how much of the signal survived.
Two things are being checked and they are worth keeping apart. The first isthat the bitstream is *valid*: ffmpeg has to recognise it as Layer II at thestated sampling rate, channel count and bit rate, and decode the number ofsamples that went in. That is a yes-or-no question and a failure there is abug. The second is how good it sounds, which is a matter of degree -- theencoder throws information away on purpose, so the interesting number is howthe ratio moves with bit rate rather than any single value of it.
Agreement is reported as signal-to-noise ratio in decibels against the source,after allowing for the 481 sample delay of the two filterbanks. It is not alistening test and does not pretend to be: a psychoacoustic model existsprecisely to put noise where it will not be heard, so an encoder that scoresworse here may well sound better.
What the ratio has to be read against is the budget. A uniform quantizergiven b bits a sample cannot do better than about 6.02b decibels, and aLayer II frame at 384 kbit/s in stereo has 4.35 bits for each of its samplesbefore its headers and scalefactors are paid for -- so 26 dB is the whole ofwhat is available there, and at 96 kbit/s it is 6.5. The figure to watch istherefore decibels per bit, which is what catches a scalefactor, quantizer orallocation that has gone wrong: it collapses well below six.
Six is a ceiling for Layer II and not for Layer III, which reaches ten andmore. That is not a paradox and not a mistake in the arithmetic: Layer IIsends a fixed number of bits per sample, while Layer III Huffman codes itsspectrum, so the bits it sends are not the bits its quantizer used. Entropycoding buys accuracy the uniform-quantizer bound knows nothing about. Thecomparison worth making is between the two layers at the same bit rate, whereLayer III should win, and it does.
usage: tools/check-encoder.py [--seconds N] [--keep]
Requires ffmpeg, and `zig build` to have been run."""
import argparseimport arrayimport mathimport osimport shutilimport subprocessimport sysimport tempfileimport wave
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))BINARY = os.path.join(ROOT, "zig-out", "bin", "zig-mp3")
# The delay through the analysis filterbank and back out of the synthesis one.# Layer III's transform adds a granule on top of that.DELAY = {2: 481, 3: 481 + 576}
LEFT = ("0.40*sin(2*PI*430*t)+0.22*sin(2*PI*2900*t)+0.20*sin(2*PI*t*t*2600)" "+0.10*(random(3)-0.5)+0.55*lt(mod(t+0.13,0.19),0.003)")RIGHT = ("0.32*sin(2*PI*610*t)+0.20*sin(2*PI*6300*t)+0.24*sin(2*PI*t*t*1500)" "+0.12*(random(4)-0.5)+0.60*lt(mod(t+0.07,0.23),0.003)")
# label, sampling rate, channels, kbit/s, layerMATRIX = [ ("Layer II 44100 stereo 384k", 44100, 2, 384, 2), ("Layer II 44100 stereo 256k", 44100, 2, 256, 2), ("Layer II 44100 stereo 192k", 44100, 2, 192, 2), ("Layer II 44100 stereo 128k", 44100, 2, 128, 2), ("Layer II 44100 stereo 96k", 44100, 2, 96, 2), ("Layer II 44100 mono 192k", 44100, 1, 192, 2), ("Layer II 44100 mono 64k", 44100, 1, 64, 2), ("Layer II 48000 stereo 256k", 48000, 2, 256, 2), ("Layer II 32000 stereo 192k", 32000, 2, 192, 2), ("Layer II 22050 stereo 96k", 22050, 2, 96, 2), ("Layer II 24000 stereo 128k", 24000, 2, 128, 2), ("Layer II 16000 mono 64k", 16000, 1, 64, 2), ("Layer III 44100 stereo 320k", 44100, 2, 320, 3), ("Layer III 44100 stereo 256k", 44100, 2, 256, 3), ("Layer III 44100 stereo 192k", 44100, 2, 192, 3), ("Layer III 44100 stereo 128k", 44100, 2, 128, 3), ("Layer III 44100 mono 192k", 44100, 1, 192, 3), ("Layer III 44100 mono 64k", 44100, 1, 64, 3), ("Layer III 48000 stereo 256k", 48000, 2, 256, 3), ("Layer III 32000 stereo 192k", 32000, 2, 192, 3),]
def run(*args): result = subprocess.run(args, capture_output=True) if result.returncode != 0: sys.exit(f"failed: {' '.join(args)}\n{result.stderr.decode()[:2000]}") return result
def make_source(path, seconds, rate, channels): run("ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", f"aevalsrc='{LEFT}':d={seconds}", "-f", "lavfi", "-i", f"aevalsrc='{RIGHT}':d={seconds}", "-filter_complex", "[0:a][1:a]amerge=inputs=2[a]", "-map", "[a]", "-ac", str(channels), "-ar", str(rate), "-c:a", "pcm_s16le", path)
def load(path): with wave.open(path) as w: samples = array.array("h") samples.frombytes(w.readframes(w.getnframes())) return w.getnchannels(), w.getframerate(), samples
def probe(path): result = run("ffprobe", "-hide_banner", "-loglevel", "error", "-show_entries", "stream=codec_name,sample_rate,channels,bit_rate", "-of", "default=noprint_wrappers=1", path) found = {} for line in result.stdout.decode().splitlines(): if "=" in line: key, value = line.split("=", 1) found[key] = value return found
def agreement(source, decoded, channels, delay): """SNR of the decoded signal against the source, allowing for the delay.""" count = min(len(source), len(decoded) - delay * channels) signal = noise = 0.0 for i in range(count): want = source[i] got = decoded[i + delay * channels] signal += want * want noise += (got - want) * (got - want) if noise == 0: return math.inf if signal == 0: return None return 10 * math.log10(signal / noise)
def main(): parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--seconds", type=float, default=4.0) parser.add_argument("--keep", action="store_true") parser.add_argument("--threshold", type=float, default=4.0, help="decibels per bit below which a case counts as " "failed; the ceiling is about six") args = parser.parse_args()
if not os.path.exists(BINARY): sys.exit(f"{BINARY} not found; run `zig build` first") for tool in ("ffmpeg", "ffprobe"): if shutil.which(tool) is None: sys.exit(f"{tool} not found")
work = tempfile.mkdtemp(prefix="zig-mp3-encoder-") failures = [] worst = math.inf
for label, rate, channels, kbps, layer in MATRIX: source = os.path.join(work, "source.wav") encoded = os.path.join(work, "case.mp%d" % layer) back = os.path.join(work, "back.wav") make_source(source, args.seconds, rate, channels) run(BINARY, "--encode", "--layer", str(layer), "--bitrate", str(kbps), source, encoded)
# Valid, before anything about quality: ffmpeg has to agree with the # header about what this is. found = probe(encoded) wanted = { "codec_name": "mp2" if layer == 2 else "mp3", "sample_rate": str(rate), "channels": str(channels), "bit_rate": str(kbps * 1000), } wrong = {k: (found.get(k), v) for k, v in wanted.items() if found.get(k) != v} if wrong: print(f" {label} INVALID {wrong}") failures.append(label) continue
run("ffmpeg", "-y", "-hide_banner", "-loglevel", "error", "-i", encoded, "-c:a", "pcm_s16le", back) _, _, original = load(source) _, _, decoded = load(back)
snr = agreement(original, decoded, channels, DELAY[layer]) size = os.path.getsize(encoded) if snr is None: print(f" {label} ERROR the source was silence") failures.append(label) continue # What the budget allows, before the frame's overheads are taken off. bits_per_sample = kbps * 1000 / (rate * channels) per_bit = snr / bits_per_sample worst = min(worst, per_bit) ok = per_bit >= args.threshold if not ok: failures.append(label) print(f" {label} {snr:6.2f} dB {bits_per_sample:4.2f} bit/sample" f" {per_bit:5.2f} dB/bit {size:>8} bytes" f"{'' if ok else ' <-- FAILED'}")
if not args.keep: shutil.rmtree(work, ignore_errors=True) else: print(f"\nfiles kept in {work}")
print(f"\nworst {worst:.2f} dB per bit over {len(MATRIX)} settings, " f"against a ceiling of about 6") if failures: print("failed: " + ", ".join(failures)) return 1 print("every setting produced a stream ffmpeg reads as what it claims to be") return 0
if __name__ == "__main__": sys.exit(main())