diff options
| author | Hunter Kvalevog <hunter@kvog.sh> | 2026-10-02 21:46:00 -0500 |
|---|---|---|
| committer | Hunter Kvalevog <hunter@kvog.sh> | 2026-10-03 16:47:57 -0500 |
| commit | aa3266cf3964b152fb3175f512bb7e882709bb20 (patch) | |
| tree | eeddac2eb06d9e3d45fbfab99806b51e2d12f567 /flacdec/flacdec.c | |
| parent | 43ec6d4963a2b40c2bcde33505c7cfc7855e507d (diff) | |
flacdec: WIPflacdec
Diffstat (limited to 'flacdec/flacdec.c')
| -rw-r--r-- | flacdec/flacdec.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/flacdec/flacdec.c b/flacdec/flacdec.c new file mode 100644 index 0000000..4b36115 --- /dev/null +++ b/flacdec/flacdec.c @@ -0,0 +1,564 @@ +// ================================================================================================ +// Toy FLAC decoder. +// +// ref: https://www.rfc-editor.org/rfc/rfc9639.pdf +// +// Changelog: +// ??/??/??: Initial release +// +// License: +// SPDX-License-Identifier: 0BSD +// Copyright (c) 2026 Hunter Kvalevog +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE. +// ================================================================================================ + +#include <assert.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define DIE(...) do { fprintf(stderr, __VA_ARGS__); abort(); } while (0) + +#if 1 +# define SPEW(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else +# define SPEW(...) +#endif + +typedef struct BitR BitR; +struct BitR +{ + const uint8_t *bbuf; + size_t blen; + size_t cbyte; + size_t cbit; +}; + +uint8_t peek_bit(BitR *br) +{ + assert(br->cbyte < br->blen); + uint8_t bit = (br->bbuf[br->cbyte] >> (7 - br->cbit)) & 1; + return bit; +} + +uint8_t read_bit(BitR *br) +{ + assert(br->cbyte < br->blen); + uint8_t bit = (br->bbuf[br->cbyte] >> (7 - br->cbit)) & 1; + br->cbit += 1; + br->cbyte += br->cbit / 8; + br->cbit %= 8; + return bit; +} + +void read_bytes(BitR *br, size_t len, uint8_t *out) +{ + assert(!br->cbit); + assert(br->cbyte + len <= br->blen); + for (size_t i = 0; i < len; ++i) { + out[i] = br->bbuf[br->cbyte]; + br->cbyte += 1; + } +} + +#if 0 +uint32_t bswap32(uint32_t i) +{ + // return __builtin_bswap32(i); + return + (i & 0xFF000000) >> 24 | + (i & 0x00FF0000) >> 16 | + (i & 0x0000FF00) >> 8 | + (i & 0x000000FF); +} +#endif + +int64_t make_signed(uint64_t u, size_t n) +{ + uint64_t sign = (uint64_t)1 << (n - 1); + return (int64_t)((u ^ sign) - sign); +} + +uint64_t read_bits(BitR *br, uint8_t n) +{ + uint64_t b = 0; + for (uint8_t i = 0; i < n; ++i) { + b <<= 1; + b |= read_bit(br); + } + return b; +} + +int64_t read_sbits(BitR *br, uint8_t n) +{ + if (n == 0) + return 0; + return make_signed(read_bits(br, n), n); +} + +void skip_bits(BitR *br, size_t n) +{ + br->cbit += n; + br->cbyte += br->cbit / 8; + br->cbit %= 8; +} + +void read_coded_residuals(BitR *br, uint32_t block_size, uint8_t order, int32_t *out) +{ + // (9.2.7) + uint8_t rice_bits = read_bits(br, 2) ? 5 : 4; + uint8_t rice_order = read_bits(br, 4); + size_t rice_partitions = 1 << rice_order; + assert(block_size % rice_partitions == 0); + + for (size_t i = 0; i < rice_partitions; ++i) { + size_t num = block_size >> rice_order; + if (i == 0) { + num -= order; + } + + size_t param = read_bits(br, rice_bits); + if ((rice_bits == 4 && param == 0xF) || param == 0x1F) { + // escaped partition (9.2.7.1) + uint8_t width = read_bits(br, 5); + for (size_t j = 0; j < num; ++j) { + *out = read_sbits(br, width); + ++out; + } + } else { + for (size_t j = 0; j < num; ++j) { + // (D.2.7) + uint32_t quot = 0; + while (!read_bit(br)) + ++quot; + uint32_t folded = (quot << param) | read_bits(br, param); + if (folded % 2 == 0) { + *out = folded >> 1; + } else { + *out = ~(folded >> 1); + } + ++out; + } + } + } +} + +const char *help = + "flacdec - toy flac decoder \n" + " \n" + "OPTIONS: \n" + " --dry Disable ffplay playback \n" + " --dump-samples=<path.raw> Dump raw audio frames \n" + " --help Display this menu \n"; + +int main(int argc, char **argv) +{ + // Parse arguments + bool a_dry = false; + const char *a_dump_samples = 0; + const char *a_file = 0; + for (int i = 1; i < argc; ++i) { + const char *a = argv[i]; + if (!strcmp(a, "--dry")) { + a_dry = true; + } else if (!strncmp(a, "--dump-samples=", 15)) { + a_dump_samples = a + 15; + } else if (!strcmp(a, "--help")) { + fprintf(stderr, "%s", help); + return 0; + } else if (*a == '-') { + fprintf(stderr, "Unknown argument: %s\n", a); + return 1; + } else if (!a_file) { + a_file = a; + } else { + fprintf(stderr, "Unexpected argument: %s\n", a); + return 1; + } + } + if (!a_file) + DIE("no file supplied\n"); + + // Read entire input file into memory + size_t flen = 0; + uint8_t *fbuf = 0; + { + FILE* f = fopen(a_file, "rb"); + if (!f) + DIE("failed to open input\n"); + + fseek(f, 0, SEEK_END); + flen = ftell(f); + fseek(f, 0, SEEK_SET); + + fbuf = malloc(flen); + fread(fbuf, 1, flen, f); + fclose(f); + } + + // Bit-level reader + BitR br = { fbuf, flen, 0, 0 }; + + // Parse header f L a C + if (read_bits(&br, 32) != 0x664C6143) + DIE("invalid input file\n"); + + // Metadata fields + uint16_t min_block_size = 0; + uint16_t max_block_size = 0; + uint32_t min_frame_size = 0; + uint32_t max_frame_size = 0; + uint32_t sample_rate = 0; + uint8_t num_channels = 0; + uint8_t bit_depth = 0; + uint64_t num_samples = 0; + + // Parse metadata blocks + for (;;) { + uint8_t end = read_bit(&br); + uint16_t type = read_bits(&br, 7); + uint32_t len = read_bits(&br, 24); + SPEW("chunk type=%d len=%d end=%d\n", type, len, end); + + switch (type) { + // streaminfo (8.2) + case 0: { + min_block_size = read_bits(&br, 16); + max_block_size = read_bits(&br, 16); + min_frame_size = read_bits(&br, 24); + max_frame_size = read_bits(&br, 24); + sample_rate = read_bits(&br, 20); + num_channels = read_bits(&br, 3) + 1; + bit_depth = read_bits(&br, 5) + 1; + num_samples = read_bits(&br, 36); + skip_bits(&br, 128); // md5 + } break; + default: { + skip_bits(&br, len * 8); + }; + }; + + if (end) + break; + } + + fprintf(stderr, "block size: [%d, %d]\n", min_block_size, max_block_size); + fprintf(stderr, "frame size: [%d, %d]\n", min_frame_size, max_frame_size); + fprintf(stderr, "sample rate: %d\n", sample_rate); + fprintf(stderr, "channels: %d\n", num_channels); + fprintf(stderr, "bit depth: %d\n", bit_depth); + fprintf(stderr, "num samples: %llu\n", num_samples); + + // Try to open ffplay for playback + FILE *ffplay = 0; + if (!a_dry) { + char ffplay_cmd[256]; + snprintf(ffplay_cmd, sizeof(ffplay_cmd), "ffplay -loglevel quiet -f s16le -ar %u " + "-ch_layout stereo -nodisp -autoexit -i - ", + sample_rate); + ffplay = popen(ffplay_cmd, "w"); + if (!ffplay) + fprintf(stderr, "ffplay not available, no playback\n"); + } + + // Open sample dump file + FILE *dump = 0; + if (a_dump_samples) { + dump = fopen(a_dump_samples, "wb"); + if (!dump) + DIE("Failed to open %s for writing\n", a_dump_samples); + } + + // only stero s16 is supported + assert(num_channels == 2 && bit_depth == 16); + + int32_t *subframe_buf = 0; // raw subframe samples. side channel can be 17-bit + int16_t *samples_buf = 0; // output samples + size_t samples_len = 0; + + // stats + int npred_c = 0; + int npred_v = 0; + int npred_f = 0; + int npred_l = 0; + + // Parse frames + //for (;;) { + while (br.cbyte < br.blen) { + // Frames must start on a byte boundary (9.1) + assert(!br.cbit); + // Frames must start with a sync code + uint16_t sync_code = read_bits(&br, 15); + assert(sync_code == 0x7FFC); + + // Assume encoder was good and all frames have the same blocking strategy bit + bool variable_block_size = read_bit(&br); + assert(!variable_block_size && "unsupported encoding"); + + uint8_t bs_code = read_bits(&br, 4); + uint8_t sr_code = read_bits(&br, 4); + uint8_t ch_code = read_bits(&br, 4); + uint8_t bd_code = read_bits(&br, 3); + skip_bits(&br, 1); // padding bit + + // (9.1.5) + uint64_t frame_num = 0; + { + // Extended UTF-8 coded number: up to 36 bits unencoded or 7 bytes encoded + // ref: https://www.rfc-editor.org/info/rfc3629/#section-3 + uint8_t b1 = read_bits(&br, 8); + uint8_t extra = 0; + if (b1 >> 7 == 0x00) { frame_num = b1; } + else if (b1 >> 5 == 0x06) { frame_num = b1 & 0x1F; extra = 1; } + else if (b1 >> 4 == 0x0E) { frame_num = b1 & 0x0F; extra = 2; } + else if (b1 >> 3 == 0x1E) { frame_num = b1 & 0x07; extra = 3; } + else if (b1 >> 2 == 0x3E) { frame_num = b1 & 0x03; extra = 4; } + else if (b1 >> 1 == 0x7E) { frame_num = b1 & 0x01; extra = 5; } + else if (b1 == 0xFE) { frame_num = 0; extra = 6; } + else { DIE("bad coded frame number\n"); } + + for (uint8_t i = 0; i < extra; ++i) { + uint8_t b = read_bits(&br, 8); + assert(b >> 6 == 0x02 && "invalid UTF-8 code"); + frame_num <<= 6; + frame_num |= (b & 0x3F); + } + } + SPEW("+frame %llu\n", frame_num); + + // Parse block size (9.1.1) + uint32_t block_size = 0; + if (bs_code == 0) + DIE("invalid block size code\n"); + else if (bs_code == 1) + block_size = 192; + else if (bs_code <= 5) + block_size = 144 * (1 << bs_code); + else if (bs_code == 6) + block_size = read_bits(&br, 8) + 1; + else if (bs_code == 7) + block_size = read_bits(&br, 16) + 1; + else + block_size = 1 << bs_code; + assert(block_size); + SPEW(" %d samples\n", block_size); + + // Parse sample rate (9.1.2) + // Don't care, just skip uncommon + if (sr_code == 12) + skip_bits(&br, 8); + if (sr_code == 13 || sr_code == 14) + skip_bits(&br, 16); + + // Parse bit depth (9.1.4) + const uint8_t bdtab[] = { bit_depth, 8, 12, 0, 16, 20, 24, 32 }; + assert(bd_code < 8 && bd_code != 3); + uint8_t frame_bd = bdtab[bd_code]; + // frame and header bit depth must be the same + assert(frame_bd == bit_depth); + + // Skip CRC + skip_bits(&br, 8); + + // Output buffer for samples + // s16 only for now + if (block_size > samples_len) { + subframe_buf = realloc(subframe_buf, block_size * sizeof(int32_t) * 2); // 2 channels + samples_buf = realloc(samples_buf, block_size * sizeof(int16_t) * 2); + samples_len = block_size; + assert(subframe_buf && samples_buf); + } + + // Subframes + for (size_t sf_num = 0; sf_num < 2; ++sf_num) { + SPEW(" +subframe %zu\n", sf_num); + // First subframe should start on a byte boundary + assert(sf_num != 0 || !br.cbit); + + int32_t *samples = subframe_buf + block_size * sf_num; + + // (9.2.1) + skip_bits(&br, 1); + uint8_t sf_type_bits = read_bits(&br, 6); + + // Decode predictor type and order + enum { + PRED_CONSTANT, + PRED_VERBATIM, + PRED_FIXED, + PRED_LINEAR, + }; + int pred_type = 0; + uint8_t pred_order = 0; + if (sf_type_bits == 0) { + pred_type = PRED_CONSTANT; + } else if (sf_type_bits == 1) { + pred_type = PRED_VERBATIM; + } else if (sf_type_bits >= 8 && sf_type_bits <= 12) { + pred_type = PRED_FIXED; + pred_order = sf_type_bits - 8; + } else if (sf_type_bits >= 32 && sf_type_bits <= 63) { + pred_type = PRED_LINEAR; + pred_order = sf_type_bits - 32 + 1; + } else { + assert(!"invalid predictor type"); + } + SPEW(" pred=%d order=%d\n", pred_type, pred_order); + + // Wasted bits per sample (9.2.2) + // If every sample in this subframe has k trailing zero bits (common when audio was + // converted from a lower bit depth, e.g. 8 bit -> 16 bit) the encoder shifts them all + // right by k. All samples will need to be shifted left by k on decode. + uint8_t wbits_k = 0; + if (read_bit(&br)) { + do { + wbits_k += 1; + } while (!read_bit(&br)); + } + SPEW(" wbits:k=%d\n", wbits_k); + + // Calculate bit depth for this subframe. + // Side channel gets an extra bit. + bool is_side = + (ch_code == 8 && sf_num == 1) || + (ch_code == 9 && sf_num == 0) || + (ch_code == 10 && sf_num == 1); + uint8_t sf_bd = frame_bd + is_side - wbits_k; + + // Decode samples + size_t cur = 0; + switch (pred_type) { + case PRED_CONSTANT: { + // (9.2.3) + int32_t dc = read_sbits(&br, sf_bd); + for (uint32_t i = 0; i < block_size; ++i) + samples[cur++] = dc; + ++npred_c; + } break; + case PRED_VERBATIM: { + // (9.2.4) + for (uint32_t i = 0; i < block_size; ++i) + samples[cur++] = read_sbits(&br, sf_bd); + ++npred_v; + } break; + case PRED_FIXED: { + // (9.2.5) + assert(pred_order < 5); + for (uint8_t i = 0; i < pred_order; ++i) + samples[cur++] = read_sbits(&br, sf_bd); + + read_coded_residuals(&br, block_size, pred_order, &samples[pred_order]); + + for (uint32_t i = pred_order; i < block_size; ++i) { + int32_t *a = &samples[i - pred_order]; // oldest first + int32_t s = 0; + switch (pred_order) { + case 0: { s = 0; } break; + case 1: { s = a[0]; } break; + case 2: { s = 2 * a[1] - a[0]; } break; + case 3: { s = 3 * a[2] - 3 * a[1] + a[0]; } break; + case 4: { s = 4 * a[3] - 6 * a[2] + 4 * a[1] - a[0]; } break; + } + samples[cur] += s; + ++cur; + } + + ++npred_f; + } break; + case PRED_LINEAR: { + // (9.2.6) + for (uint8_t i = 0; i < pred_order; ++i) + samples[cur++] = read_sbits(&br, sf_bd); + + int16_t lpc_coeffs[32]; + uint8_t lpc_coeff_width = read_bits(&br, 4) + 1; + int8_t lpc_shift = read_sbits(&br, 5); assert(lpc_shift >= 0); + for (uint8_t i = 0; i < pred_order; ++i) { + lpc_coeffs[i] = read_sbits(&br, lpc_coeff_width); + } + + read_coded_residuals(&br, block_size, pred_order, &samples[pred_order]); + + for (uint32_t i = pred_order; i < block_size; ++i) { + int32_t *a = &samples[i - pred_order]; // oldest first + int64_t s = 0; + for (uint8_t j = 0; j < pred_order; ++j) { + s += a[pred_order - j - 1] * lpc_coeffs[j]; + } + samples[cur] += (s >> lpc_shift); + ++cur; + } + + ++npred_l; + } break; + }; + + // un-waste bits + for (size_t i = 0; i < block_size; ++i) + samples[i] *= (1 << wbits_k); + } + + // padding after last subframe + if (br.cbit) + skip_bits(&br, 8 - br.cbit); + + // crc16, don't care + skip_bits(&br, 16); + + // undo decorrelation (4.2, 9.1.3) + int32_t *s1 = subframe_buf; + int32_t *s2 = subframe_buf + block_size; + for (size_t i = 0; i < block_size; ++i) { + int16_t l = 0; + int16_t r = 0; + switch (ch_code) { + case 1: { + // 2 channels: left, right + l = s1[i]; + r = s2[i]; + } break; + case 8: { + // 2 channels: left, right; stored as left-side stereo + l = s1[i]; + r = s1[i] - s2[i]; + } break; + case 9: { + // 2 channels: left, right; stored as side-right stereo + l = s2[i] + s1[i]; + r = s2[i]; + } break; + case 10: { + // 2 channels: left, right; stored as mid-side stereo + int32_t m = (s1[i] * 2) | (s2[i] & 1); + l = (m + s2[i]) >> 1; + r = (m - s2[i]) >> 1; + } break; + default: assert(!"unsupported channel layout"); + } + // interleaved write + samples_buf[2 * i + 0] = l; + samples_buf[2 * i + 1] = r; + } + + // print some cool stats + SPEW(" ->%zu\n", br.cbyte); + SPEW(" predictors: c=%d v=%d f=%d l=%d\n", npred_c, npred_v, npred_f, npred_l); + + // update playback + if (ffplay) + fwrite(samples_buf, sizeof(int16_t), block_size * 2, ffplay); + if (dump) + fwrite(samples_buf, sizeof(int16_t), block_size * 2, dump); + } + if (ffplay) + pclose(ffplay); + if (dump) + fclose(dump); +} + |