Something went wrong. Try again.
A FLAC encoder and decoder for Zig, written against RFC 9639, for native FLAC streams and FLAC in Ogg
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The header every frame starts with, and the header every subframe starts//! with (RFC 9639, Sections 9.1 and 9.2.1).//!//! A frame header repeats the sample rate, channel count and bit depth rather//! than relying on the streaminfo block, so that a decoder joining a stream//! part-way through knows what it is looking at. Two of those three fields can//! still defer to streaminfo, which is what puts such a stream outside the//! streamable subset.
const std = @import("std");
const bits = @import("bits.zig");const format = @import("format.zig");const ChannelAssignment = format.ChannelAssignment;const SubframeType = format.SubframeType;
/// The 15 bits every frame starts with (RFC 9639, Section 9.1). The blocking/// strategy bit follows immediately, so the first two bytes of a frame are/// 0xfff8 for a fixed block size stream and 0xfff9 for a variable one.pub const sync_code: u15 = 0b111111111111100;
/// Whether a frame header's coded number counts frames or samples. It says the/// same thing in every frame of a stream (RFC 9639, Section 9.1).pub const Blocking = enum(u1) { /// Every frame but the last holds the same number of samples, and the /// coded number is the count of frames before this one. fixed = 0, /// Frames may differ in size, and the coded number is the count of samples /// before this one. variable = 1,};
pub const ReadError = bits.BitReader.Error || format.coded_number.Error || error{ MissingSyncCode, ReservedFrameHeaderBits, ReservedChannelAssignment, ReservedBlockSize, ForbiddenSampleRate, HeaderCrcMismatch,};
pub const WriteError = bits.BitWriter.Error || error{ CodedNumberTooLarge, UnrepresentableFrameHeader,};
pub const FrameHeader = struct { blocking: Blocking, /// Interchannel samples in this frame. block_size: u32, /// Null where the frame defers to the streaminfo block, which is allowed /// but leaves the streamable subset (RFC 9639, Section 7). sample_rate: ?u32, channels: ChannelAssignment, /// Null where the frame defers to the streaminfo block, as above. bits_per_sample: ?u8, /// The frame number under `.fixed` blocking, the number of samples before /// this frame under `.variable`. number: u64,
/// The bit depth a subframe of this frame codes at, before wasted bits are /// taken off: one more than the frame's own for a side channel, which has /// to hold a difference (RFC 9639, Section 4.2). pub fn subframeBitsPerSample(h: FrameHeader, streaminfo_bps: u8, channel: usize) u8 { const base = h.bits_per_sample orelse streaminfo_bps; return base + @intFromBool(h.channels.isSideChannel(channel)); }
pub fn read(r: *bits.BitReader) ReadError!FrameHeader { if (try r.take(15) != sync_code) return error.MissingSyncCode; const blocking: Blocking = @fromBackingInt(@intCast(try r.take(1))); const block_size_bits: u4 = @intCast(try r.take(4)); const sample_rate_bits: u4 = @intCast(try r.take(4)); const channels = try ChannelAssignment.fromBits(@intCast(try r.take(4))); const bits_per_sample = try decodeBitDepth(@intCast(try r.take(3))); if (try r.take(1) != 0) return error.ReservedFrameHeaderBits;
const number = try format.coded_number.read(r); // A fixed block size stream counts frames, and the format caps that // count at what six octets hold (RFC 9639, Section 9.1.5). if (blocking == .fixed and number > std.math.maxInt(u31)) return error.ReservedFrameHeaderBits;
const block_size: u32 = switch (block_size_bits) { 0b0000 => return error.ReservedBlockSize, 0b0001 => 192, 0b0010...0b0101 => @as(u32, 144) << block_size_bits, 0b0110 => @as(u32, @intCast(try r.take(8))) + 1, 0b0111 => @as(u32, @intCast(try r.take(16))) + 1, 0b1000...0b1111 => @as(u32, 1) << block_size_bits, };
const sample_rate: ?u32 = switch (sample_rate_bits) { 0b0000 => null, 0b1100 => @as(u32, @intCast(try r.take(8))) * 1000, 0b1101 => @intCast(try r.take(16)), 0b1110 => @as(u32, @intCast(try r.take(16))) * 10, 0b1111 => return error.ForbiddenSampleRate, else => common_sample_rates[sample_rate_bits], };
const expected = r.crc8.final(); if (try r.take(8) != expected) return error.HeaderCrcMismatch;
return .{ .blocking = blocking, .block_size = block_size, .sample_rate = sample_rate, .channels = channels, .bits_per_sample = bits_per_sample, .number = number, }; }
pub fn write(h: FrameHeader, w: *bits.BitWriter) WriteError!void { const block_size = try encodeBlockSize(h.block_size); const sample_rate = try encodeSampleRate(h.sample_rate); const depth = try encodeBitDepth(h.bits_per_sample);
try w.put(15, sync_code); try w.put(1, @backingInt(h.blocking)); try w.put(4, block_size.code); try w.put(4, sample_rate.code); try w.put(4, h.channels.toBits()); try w.put(3, depth); try w.put(1, 0); try format.coded_number.write(w, h.number); if (block_size.extra_bits != 0) try w.put(block_size.extra_bits, h.block_size - 1); if (sample_rate.extra_bits != 0) try w.put(sample_rate.extra_bits, sample_rate.extra_value); try w.put(8, w.crc8.final()); }};
/// Sample rates that need no field of their own (RFC 9639, Section 9.1.2,/// Table 15). Index 0 defers to streaminfo and indices 12 through 15 are not/// rates, so those entries are never read.const common_sample_rates = [16]u32{ 0, 88200, 176400, 192000, 8000, 16000, 22050, 24000, 32000, 44100, 48000, 96000, 0, 0, 0, 0,};
fn decodeBitDepth(v: u3) error{ReservedFrameHeaderBits}!?u8 { return switch (v) { 0b000 => null, 0b001 => 8, 0b010 => 12, 0b011 => error.ReservedFrameHeaderBits, 0b100 => 16, 0b101 => 20, 0b110 => 24, 0b111 => 32, };}
fn encodeBitDepth(bps: ?u8) error{UnrepresentableFrameHeader}!u3 { const depth = bps orelse return 0b000; return switch (depth) { 8 => 0b001, 12 => 0b010, 16 => 0b100, 20 => 0b101, 24 => 0b110, 32 => 0b111, else => error.UnrepresentableFrameHeader, };}
const BlockSizeBits = struct { code: u4, extra_bits: u7 };
/// Choose how to state a block size. The eight- and sixteen-bit forms cost a/// byte or two but reach any size; everything else is a table entry.fn encodeBlockSize(n: u32) error{UnrepresentableFrameHeader}!BlockSizeBits { if (n == 0 or n > 65536) return error.UnrepresentableFrameHeader; if (n == 192) return .{ .code = 0b0001, .extra_bits = 0 }; inline for (2..6) |v| { if (n == 144 << v) return .{ .code = v, .extra_bits = 0 }; } inline for (8..16) |v| { if (n == 1 << v) return .{ .code = v, .extra_bits = 0 }; } // 65536 is the one size the sixteen-bit form cannot state, since it stores // one less than the size and 65535 is forbidden there (Section 9.1.6). if (n > 65535) return error.UnrepresentableFrameHeader; if (n <= 256) return .{ .code = 0b0110, .extra_bits = 8 }; return .{ .code = 0b0111, .extra_bits = 16 };}
const SampleRateBits = struct { code: u4, extra_bits: u7 = 0, extra_value: u32 = 0 };
fn encodeSampleRate(rate: ?u32) error{UnrepresentableFrameHeader}!SampleRateBits { const hz = rate orelse return .{ .code = 0b0000 }; for (common_sample_rates[1..12], 1..) |common, code| { if (hz == common) return .{ .code = @intCast(code) }; } if (hz % 1000 == 0 and hz / 1000 <= std.math.maxInt(u8)) return .{ .code = 0b1100, .extra_bits = 8, .extra_value = hz / 1000 }; if (hz <= std.math.maxInt(u16)) return .{ .code = 0b1101, .extra_bits = 16, .extra_value = hz }; if (hz % 10 == 0 and hz / 10 <= std.math.maxInt(u16)) return .{ .code = 0b1110, .extra_bits = 16, .extra_value = hz / 10 }; return error.UnrepresentableFrameHeader;}
/// A subframe's header: what kind of subframe it is, and how many/// least-significant bits of every one of its samples were left out because/// they are zero (RFC 9639, Sections 9.2.1 and 9.2.2).pub const SubframeHeader = struct { type: SubframeType, wasted_bits: u6 = 0,
pub const ReadErrorSet = bits.BitReader.Error || error{ ReservedSubframeType, MalformedSubframeHeader, };
pub fn read(r: *bits.BitReader) ReadErrorSet!SubframeHeader { if (try r.take(1) != 0) return error.MalformedSubframeHeader; const t = try SubframeType.fromBits(@intCast(try r.take(6))); const wasted: u6 = if (try r.take(1) == 0) 0 else blk: { // The count appears in unary as one less than itself, so a flag of // one always means at least one wasted bit. const unary = try r.takeUnary(); if (unary >= format.limits.max_bits_per_sample) return error.MalformedSubframeHeader; break :blk @intCast(unary + 1); }; return .{ .type = t, .wasted_bits = wasted }; }
pub fn write(h: SubframeHeader, w: *bits.BitWriter) bits.BitWriter.Error!void { try w.put(1, 0); try w.put(6, h.type.toBits()); if (h.wasted_bits == 0) { try w.put(1, 0); } else { try w.put(1, 1); try w.putUnary(@as(u32, h.wasted_bits) - 1); } }};
const testing = std.testing;
test "the frame header of RFC 9639 example 1" { // Appendix D.1.4, Table 29, field by field. var input: std.Io.Reader = .fixed(&.{ 0xff, 0xf8, 0x69, 0x18, 0x00, 0x00, 0xbf }); var r: bits.BitReader = .init(&input); const h = try FrameHeader.read(&r);
try testing.expectEqual(Blocking.fixed, h.blocking); try testing.expectEqual(@as(u32, 1), h.block_size); try testing.expectEqual(@as(?u32, 44100), h.sample_rate); try testing.expectEqual(ChannelAssignment{ .independent = 2 }, h.channels); try testing.expectEqual(@as(?u8, 16), h.bits_per_sample); try testing.expectEqual(@as(u64, 0), h.number);
var buffer: [16]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try h.write(&w); try testing.expectEqualSlices(u8, &.{ 0xff, 0xf8, 0x69, 0x18, 0x00, 0x00, 0xbf }, buffer[0..7]);}
test "the frame header of RFC 9639 example 3" { // Appendix D.3.4: mono, 8-bit, 32 kHz, a block of 24 samples. var input: std.Io.Reader = .fixed(&.{ 0xff, 0xf8, 0x68, 0x02, 0x00, 0x17, 0xe9 }); var r: bits.BitReader = .init(&input); const h = try FrameHeader.read(&r);
try testing.expectEqual(@as(u32, 24), h.block_size); try testing.expectEqual(@as(?u32, 32000), h.sample_rate); try testing.expectEqual(ChannelAssignment{ .independent = 1 }, h.channels); try testing.expectEqual(@as(?u8, 8), h.bits_per_sample);
var buffer: [16]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try h.write(&w); try testing.expectEqualSlices(u8, &.{ 0xff, 0xf8, 0x68, 0x02, 0x00, 0x17, 0xe9 }, buffer[0..7]);}
test "the frame header of RFC 9639 example 2" { // Appendix D.2.7, Table 36: a block size of 16 stated in a field of its // own, and side-right stereo, which puts the side channel first. var input: std.Io.Reader = .fixed(&.{ 0xff, 0xf8, 0x69, 0x98, 0x00, 0x0f, 0x99 }); var r: bits.BitReader = .init(&input); const h = try FrameHeader.read(&r);
try testing.expectEqual(@as(u32, 16), h.block_size); try testing.expectEqual(ChannelAssignment.right_side, h.channels); try testing.expect(h.channels.isSideChannel(0)); try testing.expect(!h.channels.isSideChannel(1)); // "As this subframe codes for a side channel, the bit depth is increased // by 1 bit from 16 bits to 17 bits" -- Appendix D.2.7. try testing.expectEqual(@as(u8, 17), h.subframeBitsPerSample(16, 0)); try testing.expectEqual(@as(u8, 16), h.subframeBitsPerSample(16, 1));}
test "frame headers round-trip across the tables" { const block_sizes = [_]u32{ 1, 15, 16, 192, 255, 256, 257, 576, 1152, 2304, 4608, 1024, 4096, 32768, 65535 }; const sample_rates = [_]?u32{ null, 8000, 44100, 48000, 96000, 192000, 11025, 3000, 12345, 655350 }; const numbers = [_]u64{ 0, 1, 127, 128, 65535, 1 << 30, 51_000_000_000 };
// A variable block size stream counts samples rather than frames, and its // count reaches 36 bits where a frame number stops at 31 (Section 9.1.5). const blockings = [_]Blocking{ .fixed, .variable };
for (blockings) |blocking| for (block_sizes) |block_size| for (sample_rates) |sample_rate| for (numbers) |number| { if (blocking == .fixed and number > std.math.maxInt(u31)) continue; const h: FrameHeader = .{ .blocking = blocking, .block_size = block_size, .sample_rate = sample_rate, .channels = .mid_side, .bits_per_sample = 24, .number = number, };
var buffer: [32]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try h.write(&w); try testing.expect(w.isByteAligned());
var input: std.Io.Reader = .fixed(buffer[0..@intCast(w.pos)]); var r: bits.BitReader = .init(&input); try testing.expectEqual(h, try FrameHeader.read(&r)); };}
test "a frame number larger than the format allows is refused" { // A fixed block size stream's coded number counts frames, and the format // caps that at what six octets hold (RFC 9639, Section 9.1.5). The same // seven-octet number is fine under variable blocking, where it counts // samples. for ([_]Blocking{ .variable, .fixed }) |blocking| { const h: FrameHeader = .{ .blocking = blocking, .block_size = 4096, .sample_rate = 44100, .channels = .{ .independent = 1 }, .bits_per_sample = 16, .number = 51_000_000_000, }; var buffer: [32]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try h.write(&w);
var input: std.Io.Reader = .fixed(buffer[0..@intCast(w.pos)]); var r: bits.BitReader = .init(&input); if (blocking == .variable) { try testing.expectEqual(h, try FrameHeader.read(&r)); } else { try testing.expectError(error.ReservedFrameHeaderBits, FrameHeader.read(&r)); } }}
test "frame headers a stream must not contain" { // A sync code that is not one. var not_sync: std.Io.Reader = .fixed(&.{ 0xff, 0xf0, 0x69, 0x18, 0x00, 0x00, 0xbf }); var r1: bits.BitReader = .init(¬_sync); try testing.expectError(error.MissingSyncCode, FrameHeader.read(&r1));
// Block size bits of 0b0000 are reserved. var reserved_size: std.Io.Reader = .fixed(&.{ 0xff, 0xf8, 0x09, 0x18, 0x00, 0x00, 0x00 }); var r2: bits.BitReader = .init(&reserved_size); try testing.expectError(error.ReservedBlockSize, FrameHeader.read(&r2));
// Sample rate bits of 0b1111 are forbidden. var forbidden_rate: std.Io.Reader = .fixed(&.{ 0xff, 0xf8, 0x6f, 0x18, 0x00, 0x00, 0x00 }); var r3: bits.BitReader = .init(&forbidden_rate); try testing.expectError(error.ForbiddenSampleRate, FrameHeader.read(&r3));
// The header of example 1 with one bit of its coded number flipped, so // that the check no longer matches. var bad_crc: std.Io.Reader = .fixed(&.{ 0xff, 0xf8, 0x69, 0x18, 0x01, 0x00, 0xbf }); var r4: bits.BitReader = .init(&bad_crc); try testing.expectError(error.HeaderCrcMismatch, FrameHeader.read(&r4));
// A block size of 65536 is stated by no form of the field. const too_big: FrameHeader = .{ .blocking = .fixed, .block_size = 65536, .sample_rate = 44100, .channels = .{ .independent = 1 }, .bits_per_sample = 16, .number = 0, }; var buffer: [32]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try testing.expectError(error.UnrepresentableFrameHeader, too_big.write(&w));}
test "subframe headers round-trip" { const types = [_]SubframeType{ .constant, .verbatim, .{ .fixed = 0 }, .{ .fixed = 4 }, .{ .lpc = 1 }, .{ .lpc = 12 }, .{ .lpc = 32 }, }; for (types) |t| for ([_]u6{ 0, 1, 2, 3, 15, 31 }) |wasted| { const h: SubframeHeader = .{ .type = t, .wasted_bits = wasted }; var buffer: [16]u8 = undefined; var output: std.Io.Writer = .fixed(&buffer); var w: bits.BitWriter = .init(&output); try h.write(&w); try w.padToByte();
var input: std.Io.Reader = .fixed(buffer[0..@intCast(w.pos)]); var r: bits.BitReader = .init(&input); try testing.expectEqual(h, try SubframeHeader.read(&r)); };}
test "the subframe headers of RFC 9639 example 1" { // Appendix D.1.4, Tables 30 and 31: verbatim subframes using 2 and then 4 // wasted bits, so that 16-bit samples are stored in 14 and then 12 bits. var input: std.Io.Reader = .fixed(&.{ 0b00000011, 0b01000000 }); var r: bits.BitReader = .init(&input); const first = try SubframeHeader.read(&r); try testing.expectEqual(SubframeType.verbatim, first.type); try testing.expectEqual(@as(u6, 2), first.wasted_bits);}