FLAC: Free Lossless Audio Codec — Technical Deep Dive
FLAC (Free Lossless Audio Codec) was developed by Josh Coalson in 2001 and is maintained by the Xiph.Org Foundation. Unlike lossy codecs that discard audio data, FLAC reproduces bitwise-identical audio on decode — every sample you put in comes back out exactly unchanged. It typically achieves 50–60 % compression on CD audio, roughly halving WAV file sizes while preserving every original waveform sample.
Stream Structure
A FLAC stream begins with the 4-byte magic fLaC, followed by one or more metadata blocks, then a sequence of audio frames.
Metadata Blocks
Each metadata block has a 4-byte header: 1 bit is-last flag, 7-bit block type, 24-bit length. Types:
| Type | Decimal | Description |
|---|---|---|
| STREAMINFO | 0 | Mandatory. Sample rate, channels, bits per sample, total samples, MD5 |
| PADDING | 1 | Reserved space for tag updates without rewriting |
| APPLICATION | 2 | Application-specific data (third-party use) |
| SEEKTABLE | 3 | Seek point table for fast random access |
| VORBIS_COMMENT | 4 | UTF-8 key=value tags (artist, title, album, etc.) |
| CUESHEET | 5 | CD layout with track offsets and ISRCs |
| PICTURE | 6 | Embedded cover art (same format as ID3v2 APIC) |
The STREAMINFO block (34 bytes) encodes, packed in bits: minimum/maximum block sizes (16 bits each), minimum/maximum frame sizes (24 bits each), sample rate (20 bits, up to 655,050 Hz), number of channels minus one (3 bits, up to 8 channels), bits per sample minus one (5 bits, 4–32 bps), total samples (36 bits), and a 128-bit MD5 signature of the unencoded audio.
Audio Frame Structure
Each FLAC frame contains:
- Frame header — sync code
0xFF 0xF8, blocking strategy, block size, sample rate, channel assignment, sample size, frame/sample number, header CRC-8 - Subframes — one per channel, each with its own subframe type and encoding
- Frame footer — CRC-16 of the entire frame
Subframe Encoding Types
FLAC's compression core lies in the subframe predictor, which exploits temporal correlation:
| Type | Code | Description |
|---|---|---|
| SUBFRAME_CONSTANT | 0 | All samples identical — stores only one value |
| SUBFRAME_VERBATIM | 1 | Uncompressed raw samples — fallback when prediction fails |
| SUBFRAME_FIXED | 8–12 | Polynomial predictor, order 0–4, fixed coefficients |
| SUBFRAME_LPC | 32–63 | FIR linear predictor, order 1–32, quantised coefficients |
For FIXED subframes, the predictor order selects pre-defined integer coefficients. For LPC subframes, the encoder calculates optimal real-valued FIR coefficients via autocorrelation (Levinson-Durbin algorithm), then quantises them to integer precision. Higher orders model more complex patterns but add coefficient overhead.
After prediction, the residuals (prediction errors) are encoded with Golomb-Rice coding — a near-optimal entropy coder for Laplacian-distributed residuals. The encoder partitions residuals into segments, selecting the Rice parameter that minimises bits.
Channel Coupling
Stereo files can use decorrelated stereo modes to improve compression when L and R channels are correlated (as in most music):
- Independent: L and R encoded separately
- Mid-side: encodes (L+R)/2 as mid and (L−R)/2 as side — usually compresses better when channels are similar
- Left-side: stores L and the difference L−R
- Right-side: stores R and the difference L−R
The encoder compares all applicable modes and selects the one producing the smallest frame.
Compression Levels
FLAC compression levels (-0 to -8) trade encode speed for file size by adjusting predictor order, block size, and search effort:
| Level | Block size | Max LPC order | Rice search | Typical ratio | Speed |
|---|---|---|---|---|---|
| -0 | 1152 | FIXED only | 2 | ~63% of WAV | Fastest |
| -1 | 1152 | 0 | 2 | ~61% | Very fast |
| -2 | 4096 | 0 | 0 | ~59% | Fast |
| -4 | 4096 | 8 | 0 | ~57% | Medium |
| -5 | 4096 | 8 | 0 | ~55% | Default |
| -6 | 4096 | 12 | 0 | ~54% | Slow |
| -8 | 4096 | 32 | 0 | ~53% | Slowest |
The difference between -0 and -8 is typically only 5–10% smaller files at 10× the encode time — for archival purposes, -8 is worth it; for real-time encoding, -0 or -2 suffices.
FFmpeg FLAC Encoding
# WAV to FLAC, maximum compression, keep all tags
ffmpeg -i input.wav -c:a flac -compression_level 8 output.flac
# CD rip (16-bit 44100 Hz) to high-res FLAC (24-bit 96 kHz upscale — same data, different container)
# Note: upsampling does not add quality; use only to match a target format
ffmpeg -i input.wav -c:a flac -ar 96000 -sample_fmt s32 output_24bit.flac
# Batch convert MP3 to FLAC (preserves dynamic range but quality capped at source MP3 lossy encoding)
for f in *.mp3; do ffmpeg -i "$f" -c:a flac "${f%.mp3}.flac"; done
# Embed cover art into existing FLAC
ffmpeg -i audio.flac -i cover.jpg \
-map 0:a -map 1:v \
-c:a copy -c:v copy \
-metadata:s:v comment="Cover (front)" \
with_cover.flac
# Extract embedded cover art
ffmpeg -i audio.flac -an -vcodec copy cover_extracted.jpg
Python: Reading and Writing FLAC
import soundfile as sf
import numpy as np
# Read FLAC — returns float64 array by default
samples, rate = sf.read('track.flac')
print(f"Rate: {rate} Hz | Shape: {samples.shape} | dtype: {samples.dtype}")
# Write lossless 24-bit FLAC
sf.write('output_24bit.flac', samples, rate, subtype='PCM_24')
# Available FLAC subtypes: PCM_16, PCM_24, PCM_32
# ---- Tag manipulation with mutagen ----
from mutagen.flac import FLAC
audio = FLAC('track.flac')
# Read stream info
print(f"Sample rate : {audio.info.sample_rate} Hz")
print(f"Channels : {audio.info.channels}")
print(f"Bits/sample : {audio.info.bits_per_sample}")
print(f"Duration : {audio.info.length:.2f} s")
# Read / write VorbisComment tags
print("Title:", audio.get('title', ['(none)'])[0])
audio['artist'] = ['New Artist Name']
audio['album'] = ['Remastered Edition 2025']
audio.save()
Parsing the FLAC File Structure in Python
import struct, hashlib
def read_flac_metadata(path: str) -> dict:
info = {}
with open(path, 'rb') as f:
magic = f.read(4)
if magic != b'fLaC':
raise ValueError(f"Not a FLAC file: {magic!r}")
while True:
hdr = f.read(4)
if len(hdr) < 4:
break
is_last = bool(hdr[0] & 0x80)
block_type = hdr[0] & 0x7F
length = struct.unpack('>I', b'\x00' + hdr[1:])[0]
data = f.read(length)
if block_type == 0: # STREAMINFO
# Unpack the 34-byte STREAMINFO block
min_bs, max_bs = struct.unpack('>HH', data[0:4])
# min/max frame size stored as 24-bit big-endian
min_fs = int.from_bytes(data[4:7], 'big')
max_fs = int.from_bytes(data[7:10], 'big')
# sample_rate(20) | channels(3) | bps(5) packed in 4 bytes starting at offset 10
packed = int.from_bytes(data[10:14], 'big')
info['sample_rate'] = (packed >> 12) & 0xFFFFF
info['channels'] = ((packed >> 9) & 0x7) + 1
info['bps'] = ((packed >> 4) & 0x1F) + 1
# total_samples: bottom 4 bits of data[13] + data[14:18] = 36 bits
total = ((data[13] & 0x0F) << 32) | int.from_bytes(data[14:18], 'big')
info['total_samples'] = total
info['md5'] = data[18:34].hex()
elif block_type == 4: # VORBIS_COMMENT
# Length-prefixed UTF-8 strings
vendor_len = struct.unpack('<I', data[0:4])[0]
vendor = data[4:4+vendor_len].decode('utf-8', errors='replace')
info['vendor'] = vendor
if is_last:
break
return info
meta = read_flac_metadata('track.flac')
print(meta)
# {'sample_rate': 44100, 'channels': 2, 'bps': 16, 'total_samples': 14495700, 'md5': '...', 'vendor': 'reference libFLAC 1.4.3 20230624'}
FLAC vs Other Lossless Formats
| Format | Lossless | Typical ratio | Container | Streaming | OS support |
|---|---|---|---|---|---|
| FLAC | Yes | ~55% of WAV | Native / OGG | HTTP range | Linux/Win/Mac (native) |
| ALAC | Yes | ~60% of WAV | MP4/M4A | HLS | Apple ecosystem native |
| WAV | Yes (PCM) | 100% | RIFF | Limited | Universal |
| AIFF | Yes (PCM) | 100% | IFF | Limited | Apple native |
| WavPack | Yes (+lossy hybrid) | ~55% | .wv | No | Limited |
| APE (Monkey's) | Yes | ~50% | .ape | No | Limited hardware support |
FLAC is the best default lossless format for non-Apple ecosystems due to open patents, wide hardware support (DAPs, NAS devices, receivers), and rich metadata via Vorbis Comment.
Common Pitfalls
- Converting lossy → FLAC does not restore quality. FLAC is lossless — it preserves exactly what it receives. If the source MP3 has artefacts, the FLAC will contain those same artefacts. The only benefit is a lossless container for future re-processing without additional generation loss.
- 24-bit FLAC from 16-bit source adds no information. Up-sampling bit depth zero-pads the lower bits. The sonic result is identical; file size increases.
- FLAC is not natively streamable. Unlike HLS/DASH with ALAC-in-MP4, FLAC requires HTTP range requests or a custom player. For lossless Apple Music streaming, Apple uses ALAC in fragmented MP4.
- Tag the ALBUM ARTIST field when building music libraries. Many players and DAPs use
albumartistfor compilation grouping, notartist.
Related conversions
Common video conversions that pair well with this guide: