A Zig implementation of the MPEG I Layer III (mp3) decoder.
- Parses and decodes MPEG-1 Layer III (MP3) frames.
- Produces interleaved PCM samples.
- Can:
- print decode stats
- write decoded WAV output
- play audio directly in terminal
- stream decode+play in realtime mode
- fetch YouTube audio (
yt-dlp+ffmpeg) and decode it
cli.zig- CLI entry point and runtime orchestration.mp3decoder.zig- frame parsing + decode pipeline + full/realtime decode APIs.wav.zig- PCM16 and WAV encoding helpers.algorithm/- decoding stages:bits.zigsideinfo.zighuffman.zigrequantize.zigstereo.zigreorder.zigantialias.zigimdct.zigpqmf.zigtables.zig
The decoder follows the standard MPEG-1 Layer III flow in mp3decoder.zig.
-
Frame sync + header parse
- Scan bytes for sync words and parse MPEG header fields (
parseFrameHeader). - Validate version/layer/rate/length and skip inconsistent candidates.
- Skip ID3v2 and optional Xing/Info metadata frame in full-file decode.
- Scan bytes for sync words and parse MPEG header fields (
-
Side info + bit reservoir setup
- Read side info (
sideinfo.parseSideInfo) and derive frame band indices. - Append current frame main data to reservoir tail from prior frames.
- Use
mainDataBeginto pick the true Huffman bitstream start.
- Read side info (
-
Scale factors + Huffman spectral decode
- Parse scale factors per granule/channel (
huffman.parseScaleFactors). - Decode Huffman-coded spectral lines (
huffman.parseHuffmanData). - Preserve granule-0 long-block scale factors for
scfsireuse.
- Parse scale factors per granule/channel (
-
Requantize + stereo processing
- Requantize integer Huffman output to frequency-domain amplitudes (
requantize.requantizeGranule). - For joint stereo, apply MS/intensity stereo transforms (
stereo.processStereo).
- Requantize integer Huffman output to frequency-domain amplitudes (
-
Reorder + anti-alias + IMDCT
- Reorder short/mixed blocks to frequency order (
reorder.reorderSpectrum). - Apply alias reduction for eligible bands (
antialias.applyAntiAlias). - Run IMDCT with overlap-add state per channel (
imdct.applyIMDCT).
- Reorder short/mixed blocks to frequency order (
-
Polyphase synthesis (PQMF) + output
- Process each 32-subband time slice through synthesis filterbank (
pqmf.synthFilterStep). - Clamp to
[-1, 1]float PCM and interleave channels. - Emit either:
- one contiguous PCM buffer (
decodeAllFrames), or - streaming chunks via callback (
decodeAllFramesRealtime).
- one contiguous PCM buffer (
- Process each 32-subband time slice through synthesis filterbank (
zig buildRun directly:
zig build run -- <args>Or compile the executable and run it:
zig build
./zig-out/bin/cli <args>zig build run -- [--play|-p] [--interactive] [--seek <sec|mm:ss|hh:mm:ss>] <input.mp3|-> [output.wav]
zig build run -- [--play|-p] [--interactive] [--seek <sec|mm:ss|hh:mm:ss>] --yt <youtube-url> [output.wav]-
--play,-p
Decode and play in terminal (aplay,paplay, orffplay). -
--interactive
Enable keyboard controls (<-/->seek,qquit). Requires a TTY. -
--seek <t>
Seek offset in seconds ormm:ss/hh:mm:ss. -
--yt <url>
Pull audio from YouTube viayt-dlp, transcode to MP3 viaffmpeg, then decode. -
-(input path)
Read MP3 bytes from stdin.
Decode and print stats:
zig build run -- song.mp3Decode and write WAV:
zig build run -- song.mp3 output.wavPlay audio:
zig build run -- --play song.mp3Play audio with interactive controls:
zig build run -- --play --interactive song.mp3Play with seek:
zig build run -- --play --seek 1:30 song.mp3Pipe input:
cat song.mp3 | zig build run -- --play -YouTube source:
zig build run -- --play --yt "https://www.youtube.com/watch?v=VIDEO_ID"