Skip to content

FLAC Multiplex (`mXfC`) Format

Container for multichannel signal data partitioned into standard FLAC streams of at most 8 channels, matching FLAC’s native per-stream channel limit. A multiplex is an 8-byte container header followed by a sequence of channel slices, each consisting of an 8-byte slice header and an independent standard FLAC bitstream.

The multiplex has two structural layers:

  • Partitioning — channels are split channel-major into slices of at most 8 channels each, matching FLAC’s per-stream channel limit.
  • FLAC encoding — each slice is encoded as an independent standard FLAC (fLaC) stream.

All multi-byte fields are little-endian. The container header is packed (no padding) and must be exactly 8 bytes:

OffSizeFieldC typeConstraint
04magicuint320x4366586D (ASCII m X f C)
42total_channelsuint16≥ 1
62slice_countuint16≥ 1

slice_count slice blocks follow the container header back-to-back. Each slice block is an 8-byte slice header followed by a variable-length FLAC bitstream, defined in Slice layout.

From the no-overlap and full-coverage rules in Validation, ⌈total_channels / 8⌉ ≤ slice_count ≤ total_channels.

Each slice is preceded by an 8-byte slice header:

OffSizeFieldC typeConstraint
02first_channeluint16< total_channels
22channel_countuint161 ≤ channel_count ≤ 8; first_channel + channel_count ≤ total_channels
44payload_sizeuint32byte size of the FLAC bitstream that follows

Immediately after the slice header, payload_size bytes of a standard FLAC (fLaC) bitstream follow. The next slice header begins at the byte right after the bitstream.

The multiplex’s total_channels channels are partitioned into contiguous non-overlapping channel ranges; each slice carries the FLAC encoding of one such range. For 22 channels:

total_channels = 22, slice_count = 3
Slice 0: first_channel = 0, channel_count = 8 -> channels 0.. 7
Slice 1: first_channel = 8, channel_count = 8 -> channels 8..15
Slice 2: first_channel = 16, channel_count = 6 -> channels 16..21

Slices may appear in any order. Readers must place channels using first_channel rather than the slice’s position in the file.

The multiplex headers do not carry sample format or bit depth — these are properties of each slice’s FLAC bitstream and are read from the FLAC STREAMINFO metadata block during slice decoding. FLAC restricts samples to 8, 16, or 24 bits per sample in signed two’s-complement; the multiplex inherits this constraint.

All slices in a single multiplex must agree on sample format, sample rate, and sample count after FLAC decoding (see Validation). The multiplex itself does not duplicate these values in any header.

The only compression knob is the FLAC compression level applied independently to each slice when it is FLAC-encoded:

ValueMeaning
0fastest, least compression
8slowest, best compression

The level is a property of the encoder, not of the wire format — different slices in the same multiplex may have been produced with different levels and still decode to identical samples. FLAC is lossless; the level affects size and CPU cost only.

A conforming reader must reject inputs that violate any of the following:

  • Container header reads less than 8 bytes.
  • magic != 0x4366586D.
  • A slice header reads less than 8 bytes.
  • first_channel ≥ total_channels.
  • first_channel + channel_count > total_channels.
  • channel_count is 0 or greater than 8.
  • Any channel index appears in more than one slice’s range.
  • The union of slice ranges does not cover every index in [0, total_channels) exactly once.
  • A slice payload reads fewer than payload_size bytes from the stream.
  • A slice payload is not a valid standard FLAC (fLaC) bitstream.
  • A decoded slice’s actual channel count does not match its slice-header channel_count.
  • Any slice’s decoded sample format, sample rate, or sample count differs from slice 0’s.

The validation order is at the reader’s discretion, but cheap structural checks (magic, header sizes, per-slice index arithmetic) should run before payload-decode checks (FLAC bitstream validity, sample-format consistency).

Single-call encoder that builds a multiplex from one or more channel-major sample buffers. Allocates the result on the heap; caller frees with free. Returns NULL on invalid arguments, libFLAC failure, or allocation failure.

Compile: cc -std=c17 -O3 fmux_encode.c -lFLAC -o fmux_encode

#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <FLAC/stream_encoder.h>
#define FMUX_MAGIC 0x4366586Du
#define FMUX_MAX_CHANNELS_PER_SLICE 8
#pragma pack(push, 1)
typedef struct {
uint32_t magic;
uint16_t total_channels;
uint16_t slice_count;
} FmuxContainerHeader;
typedef struct {
uint16_t first_channel;
uint16_t channel_count;
uint32_t payload_size;
} FmuxSliceHeader;
#pragma pack(pop)
// Growable byte buffer with an explicit write cursor; libFLAC patches STREAMINFO
// via seek/tell on finish, so a simple append-only buffer is not sufficient
typedef struct {
uint8_t* data;
size_t length;
size_t capacity;
size_t position;
} ByteBuffer;
static bool buffer_write(ByteBuffer* buffer, const uint8_t* bytes, size_t count) {
const size_t required = buffer->position + count;
if (required > buffer->capacity) {
size_t new_capacity = buffer->capacity ? buffer->capacity : 4096;
while (new_capacity < required) {
new_capacity *= 2;
}
uint8_t* new_data = (uint8_t*)realloc(buffer->data, new_capacity);
if (!new_data) {
return false;
}
buffer->data = new_data;
buffer->capacity = new_capacity;
}
memcpy(buffer->data + buffer->position, bytes, count);
buffer->position += count;
if (buffer->position > buffer->length) {
buffer->length = buffer->position;
}
return true;
}
static FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder* encoder,
const FLAC__byte bytes[],
size_t count,
uint32_t samples,
uint32_t current_frame,
void* client_data) {
(void)encoder;
(void)samples;
(void)current_frame;
return buffer_write((ByteBuffer*)client_data, bytes, count)
? FLAC__STREAM_ENCODER_WRITE_STATUS_OK
: FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
}
static FLAC__StreamEncoderSeekStatus seek_callback(const FLAC__StreamEncoder* encoder,
FLAC__uint64 absolute_offset,
void* client_data) {
(void)encoder;
ByteBuffer* slice_buffer = (ByteBuffer*)client_data;
if (absolute_offset > slice_buffer->length) {
return FLAC__STREAM_ENCODER_SEEK_STATUS_ERROR;
}
slice_buffer->position = (size_t)absolute_offset;
return FLAC__STREAM_ENCODER_SEEK_STATUS_OK;
}
static FLAC__StreamEncoderTellStatus tell_callback(const FLAC__StreamEncoder* encoder,
FLAC__uint64* absolute_offset,
void* client_data) {
(void)encoder;
*absolute_offset = (FLAC__uint64)((const ByteBuffer*)client_data)->position;
return FLAC__STREAM_ENCODER_TELL_STATUS_OK;
}
static bool encode_one_slice(const int32_t* const* slice_channels,
uint16_t channels_in_slice,
uint32_t samples_count,
uint32_t sample_rate,
uint8_t bits_per_sample,
uint8_t compression_level,
ByteBuffer* slice_buffer) {
FLAC__StreamEncoder* encoder = FLAC__stream_encoder_new();
if (!encoder) {
return false;
}
FLAC__stream_encoder_set_channels(encoder, channels_in_slice);
FLAC__stream_encoder_set_bits_per_sample(encoder, bits_per_sample);
FLAC__stream_encoder_set_sample_rate(encoder, sample_rate);
FLAC__stream_encoder_set_total_samples_estimate(encoder, samples_count);
FLAC__stream_encoder_set_compression_level(encoder, compression_level);
FLAC__stream_encoder_set_verify(encoder, false);
const FLAC__StreamEncoderInitStatus init_status = FLAC__stream_encoder_init_stream(
encoder,
write_callback,
seek_callback,
tell_callback,
NULL,
slice_buffer);
if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
FLAC__stream_encoder_delete(encoder);
return false;
}
const bool ok = FLAC__stream_encoder_process(encoder, slice_channels, samples_count)
&& FLAC__stream_encoder_finish(encoder);
FLAC__stream_encoder_delete(encoder);
return ok;
}
uint8_t* fmux_encode(const int32_t* const* input_channels,
uint16_t channels_count,
uint32_t samples_count,
uint32_t sample_rate,
uint8_t bits_per_sample,
uint8_t compression_level,
size_t* output_size) {
if (!input_channels || !output_size) {
return NULL;
}
if (!channels_count) {
return NULL;
}
if (bits_per_sample != 8 && bits_per_sample != 16 && bits_per_sample != 24) {
return NULL;
}
if (compression_level > 8) {
return NULL;
}
if (!samples_count || !sample_rate) {
return NULL;
}
const uint16_t slice_count = (channels_count + FMUX_MAX_CHANNELS_PER_SLICE - 1) / FMUX_MAX_CHANNELS_PER_SLICE;
// Emit container header into the growing output buffer
ByteBuffer output = {0};
const FmuxContainerHeader container_header = {
.magic = FMUX_MAGIC,
.total_channels = channels_count,
.slice_count = slice_count,
};
if (!buffer_write(&output, (const uint8_t*)&container_header, sizeof(container_header))) {
free(output.data);
return NULL;
}
// Encode each slice and append (slice header + FLAC bitstream) to the multiplex
const int32_t* slice_ptrs[FMUX_MAX_CHANNELS_PER_SLICE];
for (uint16_t slice_index = 0; slice_index < slice_count; ++slice_index) {
const uint16_t channels_start = (uint16_t)(slice_index * FMUX_MAX_CHANNELS_PER_SLICE);
const uint16_t channels_left = (uint16_t)(channels_count - channels_start);
const uint16_t channels_in_slice = channels_left < FMUX_MAX_CHANNELS_PER_SLICE
? channels_left
: FMUX_MAX_CHANNELS_PER_SLICE;
for (uint16_t channel = 0; channel < channels_in_slice; ++channel) {
slice_ptrs[channel] = input_channels[channels_start + channel];
}
ByteBuffer slice_buffer = {0};
if (!encode_one_slice(slice_ptrs, channels_in_slice, samples_count, sample_rate, bits_per_sample, compression_level, &slice_buffer)) {
free(slice_buffer.data);
free(output.data);
return NULL;
}
const FmuxSliceHeader slice_header = {
.first_channel = channels_start,
.channel_count = channels_in_slice,
.payload_size = (uint32_t)slice_buffer.length,
};
if (!buffer_write(&output, (const uint8_t*)&slice_header, sizeof(slice_header)) ||
!buffer_write(&output, slice_buffer.data, slice_buffer.length)) {
free(slice_buffer.data);
free(output.data);
return NULL;
}
free(slice_buffer.data);
}
*output_size = output.length;
return output.data;
}
  • The encoder produces a host-byte-order container and slice headers. On big-endian platforms the multi-byte fields must be byte-swapped to little-endian before writing the bytes out. The reader assumes little-endian.
  • The encoder emits slices in ascending order of first_channel for predictability. The format does not require this ordering; conforming readers must use first_channel to place channels regardless of slice position in the file.
  • Each slice payload is a standard FLAC bitstream per https://xiph.org/flac/format.html. Readers delegate slice payload parsing to a FLAC decoder rather than re-implementing FLAC parsing.
  • FLAC__stream_encoder_finish rewrites the slice’s STREAMINFO metadata block in-place by seeking back to the start of the bitstream; the in-memory seek/tell callbacks above support this. A file-backed encoder using FLAC__stream_encoder_init_file does not need explicit seek/tell callbacks.