Standard library · Encoding and data

audio/flac

Lossless compression: reading and writing FLAC.

import audio.flac

FLAC is lossless: what comes out is exactly what went in, and the file is usually a little over half the size of the same sound as 16 bit PCM. This package reads and writes it without any outside library.

The decoder handles the whole format: constant, verbatim, fixed-predictor and LPC subframes, Rice-coded and escaped residuals, and all four ways FLAC can share a stereo pair between two channels. The encoder writes constant, verbatim and fixed-predictor subframes, choosing per block whichever is smallest, which is where most of the compression comes from.

Things to know

  • Lossless means lossless: a clip at 8, 16 or 24 bits comes back bit for bit. VerifyFLAC proves it on a given file by decoding it again.
  • An encode is only as good as the clip it was given. A clip has no bit depth of its own until something sets bits, and the encoder then assumes 16.
  • Tags are not here. FLAC keeps them in a metadata block, which audio/tag reads and writes.
  • Blocks hands back a vector you must free, and a stream header holds an MD5 you must release with FreeStreamInfo.
  • Compression depends entirely on the material. A tone goes to a fifth of its PCM size, noise barely moves, and real music lands somewhere near half.

Types

FrameInfo

struct FrameInfo
What a frame header says, which may differ from the stream header.
NameTypeDescription
block_sizeint = 0How many frames this block holds.
rateint = 0Frames per second.
channelsint = 0How many channels this frame carries.
bitsint = 0Bits per sample.
assignmentint = 0How the channels are stored: independent, or left/side, right/side or mid/side.
variablebool = falseTrue when the frame is numbered by sample rather than by frame.
numberi64 = 0The frame or sample number this frame starts at.
flac-Read.salam
import audio
import audio.flac
import audio.synth
import compress

func main:
    src := synth.Tone(440.0, 0.1, 44100, synth.WaveSine)
    mut q := audio.ToBits(src, 16)
    q.bits = 16
    enc := flac.EncodeFLAC(q)
    println flac.IsFLAC(enc.data, enc.len), flac.IsFLAC(enc.data, 4)
    println audio.FormatName(audio.DetectFormat(enc.data, enc.len))

    blocks := flac.Blocks(enc.data, enc.len)
    println blocks.len(), blocks.get(0).kind == flac.BlockStreamInfo, blocks.get(0).last, blocks.get(0).size

    mut si := flac.StreamInfo {}
    println flac.ReadStreamInfo(enc.data, enc.len, si)
    println si.rate, si.channels, si.bits, si.total as int
    println si.md5.len, si.audio_offset
    println si.min_block, si.max_block

    mut fi := flac.FrameInfo {}
    head := flac.ReadFrameHeader(enc.data, enc.len, si.audio_offset, si, fi)
    println head > 0, fi.channels, fi.bits, fi.rate, fi.block_size

    mut ok := false
    back := flac.DecodeFLAC(enc.data, enc.len, ok)
    println ok, back.frames, back.channels, back.bits
    println audio.Sample(back, 100, 0) == audio.Sample(q, 100, 0)

    mut pok := false
    info := flac.ProbeFLAC(enc.data, enc.len, pok)
    println pok, info.rate, info.channels, info.bits, info.frames, info.lossless

    println flac.MaxChannels, flac.MaxBlockSize
    mut junk := compress.NewByteBuf()
    compress.BufPush(junk, 1)
    println flac.CRC8(junk.data, 0, 0), flac.CRC16Buf(junk, 0, 1) == flac.CRC16(junk.data, 0, 1)
    mut bad := false
    flac.DecodeFLAC(junk.data, junk.len, bad)
    println bad

    flac.FreeStreamInfo(si)
    blocks.free()
    compress.BufFree(enc)
    compress.BufFree(junk)
    audio.Free(src)
    audio.Free(q)
    audio.Free(back)
end
Outputtrue false flac 1 true true 34 true 44100 1 16 4410 16 42 4096 4096 true 1 16 44100 4096 true 4410 1 16 true true 44100 1 16 4410 true 8 65535 0 true false

StreamInfo

struct StreamInfo
What a FLAC stream says about itself, read from its first metadata block.
NameTypeDescription
min_blockint = 0The smallest block the encoder used.
max_blockint = 0The largest block it used.
min_frameint = 0The smallest frame in bytes, or 0 when unknown.
max_frameint = 0The largest frame in bytes.
rateint = 0Frames per second.
channelsint = 0How many channels.
bitsint = 0Bits per sample, from 4 to 32.
totali64 = 0How many frames the whole stream holds, or 0 when the encoder did not know.
md5compress.ByteBufThe MD5 of the decoded signal, 16 bytes. Release it with FreeStreamInfo.
audio_offsetint = 0Where the first audio frame starts, past every metadata block.

The example for this item is shown with the ones above: flac-Read.salam

MetaBlock

struct MetaBlock
One metadata block: what kind it is and where its body is.
NameTypeDescription
kindint = 0Which Block number it is.
offsetint = 0Where the body starts.
sizeint = 0How many bytes the body holds.
lastbool = falseTrue on the final block before the audio.

The example for this item is shown with the ones above: flac-Read.salam

Constants and variables

DefaultBlock

const DefaultBlock: i32 = 4096
The block size the encoder uses unless a level asks for another.
flac-Write.salam
import audio
import audio.flac
import audio.synth
import compress
import filepath
import mem
import os

func main:
    src := synth.Tone(440.0, 0.2, 44100, synth.WaveSine)
    mut tone := audio.ToBits(src, 16)
    tone.bits = 16
    raw := tone.frames * tone.channels * 2
    println tone.frames, raw

    enc := flac.EncodeFLAC(tone)
    println compress.BufLen(enc) < raw / 2
    println flac.VerifyFLAC(enc.data, enc.len)
    println flac.DefaultBlock

    mut ok := false
    back := flac.DecodeFLAC(enc.data, enc.len, ok)
    mut worst := 0.0
    mut i := 0
    until i < tone.frames:
        d := audio.Sample(back, i, 0) - audio.Sample(tone, i, 0)
        if d > worst:
            worst = d
        end
        if 0.0 - d > worst:
            worst = 0.0 - d
        end
        i += 1
    end
    println worst

    each lv in [0, 5, 8]:
        one := flac.EncodeFLACLevel(tone, lv)
        println lv, compress.BufLen(one) > 42, flac.VerifyFLAC(one.data, one.len)
        compress.BufFree(one)
    end
    println compress.BufLen(flac.EncodeFLAC(audio.Clip {}))

    path := filepath.Join(os.TempDir(), "salam_flac_example.flac")
    defer mem.Free(path as void*)
    println flac.SaveFLAC(tone, path)
    mut dok := false
    disk := flac.OpenFLAC(path, dok)
    println dok, disk.frames == tone.frames, disk.bits
    println flac.SaveFLACLevel(tone, path, 8)
    mut iok := false
    info := flac.ProbeFileFLAC(path, iok)
    println iok, info.frames
    mut gone := false
    missing := filepath.Join(os.TempDir(), "salam_flac_none.flac")
    defer mem.Free(missing as void*)
    flac.OpenFLAC(missing, gone)
    println gone

    compress.BufFree(enc)
    audio.Free(src)
    audio.Free(tone)
    audio.Free(back)
    audio.Free(disk)
end
Output8820 17640 true true 4096 0 0 true true 5 true true 8 true true 0 true true true 16 true true 8820 false

Metadata block numbers Block…

The kinds of metadata block a FLAC file can hold before its audio.
NameValue
BlockStreamInfo0
BlockPadding1
BlockApplication2
BlockSeekTable3
BlockVorbisComment4
BlockCueSheet5
BlockPicture6

The example for this item is shown with the ones above: flac-Read.salam

MaxBlockSize

const MaxBlockSize: i32 = 65535
The largest block FLAC allows.

The example for this item is shown with the ones above: flac-Read.salam

MaxChannels

const MaxChannels: i32 = 8
The most channels FLAC allows.

The example for this item is shown with the ones above: flac-Read.salam

Functions

EncodeFLACLevel

func EncodeFLACLevel(c: audio.Clip, level: int): compress.ByteBuf
Encodes a clip as FLAC at a level from 0 to 8. The level picks the block size; every level is lossless.

The example for this item is shown with the ones above: flac-Write.salam

EncodeFLAC

func EncodeFLAC(c: audio.Clip): compress.ByteBuf
Encodes a clip as FLAC at the middle level.

The example for this item is shown with the ones above: flac-Write.salam

VerifyFLAC

func VerifyFLAC(p: void*, n: int): bool
Decodes a stream again and checks it against its own header. Use it to prove an encode really was lossless.

The example for this item is shown with the ones above: flac-Write.salam

ReadFrameHeader

func ReadFrameHeader(p: void*, n: int, at: int, si: StreamInfo, out &: FrameInfo): int
Reads one frame header and checks its CRC-8. It returns how many bytes the header took, or -1 when this is not a frame.

The example for this item is shown with the ones above: flac-Read.salam

DecodeFLAC

func DecodeFLAC(p: void*, n: int, ok &: bool): audio.Clip
Decodes a whole FLAC stream from memory into a clip. Constant, verbatim, fixed and LPC subframes are all handled, as are Rice and escaped residuals and all four channel assignments.

The example for this item is shown with the ones above: flac-Read.salam

CRC8

func CRC8(p: void*, off: int, n: int): int
The CRC-8 FLAC puts at the end of every frame header, polynomial 0x07.

The example for this item is shown with the ones above: flac-Read.salam

CRC16

func CRC16(p: void*, off: int, n: int): int
The CRC-16 FLAC puts at the end of every frame, polynomial 0x8005.

The example for this item is shown with the ones above: flac-Read.salam

CRC16Buf

func CRC16Buf(b: compress.ByteBuf, off: int, n: int): int
The same CRC-16 over a byte buffer.

The example for this item is shown with the ones above: flac-Read.salam

IsFLAC

func IsFLAC(p: void*, n: int): bool
True when these bytes start with the fLaC magic number.

The example for this item is shown with the ones above: flac-Read.salam

Blocks

func Blocks(p: void*, n: int): Vector<MetaBlock>
Every metadata block, in order. Free the vector when you are done.

The example for this item is shown with the ones above: flac-Read.salam

ReadStreamInfo

func ReadStreamInfo(p: void*, n: int, out &: StreamInfo): bool
Reads the stream header and works out where the audio starts.

The example for this item is shown with the ones above: flac-Read.salam

FreeStreamInfo

func FreeStreamInfo(si &: StreamInfo)
Releases the MD5 a stream header holds.

The example for this item is shown with the ones above: flac-Read.salam

ProbeFLAC

func ProbeFLAC(p: void*, n: int, ok &: bool): audio.Info
Reads the stream header alone, so you learn the length and rate without decoding.

The example for this item is shown with the ones above: flac-Read.salam

OpenFLAC

func OpenFLAC(path: str, ok &: bool): audio.Clip
Reads a FLAC file from disk into a clip.

The example for this item is shown with the ones above: flac-Read.salam

ProbeFileFLAC

func ProbeFileFLAC(path: str, ok &: bool): audio.Info
Reads the stream header of a file on disk.

The example for this item is shown with the ones above: flac-Read.salam

SaveFLAC

func SaveFLAC(c: audio.Clip, path: str): bool
Writes a clip to a FLAC file.

The example for this item is shown with the ones above: flac-Write.salam

SaveFLACLevel

func SaveFLACLevel(c: audio.Clip, path: str, level: int): bool
Writes a clip to a FLAC file at a level you choose.

The example for this item is shown with the ones above: flac-Write.salam