LC3 Frame Structure and External Rate Adaptation

 

LC3 Frame Structure and External Rate Adaptation
Chapter 3, Sections 3.5 and 3.6 — Payload Byte Layout and Seamless Bitrate Changes
Chapter
3 — Sections 3.5 + 3.6
Level
Intermediate

Section 3.5 describes the exact byte-level layout of the LC3 payload — what is where inside those nbytes bytes. Section 3.6 explains how the codec supports seamless bitrate changes mid-stream without any teardown or glitch. These two sections are essential for anyone implementing an LC3 parser, a bitstream analyser, or an adaptive bitrate audio system.

Keywords

Frame Layout Side Information Arithmetic Coded Region Residual Region Bidirectional Packing Rate Adaptation nbytes Change

3.5 Frame Structure

Four-Part Payload Layout

Every LC3 payload of exactly nbytes bytes contains four parts. The key insight is that two independent write/read pointers are used — one starting from the front (byte 0) and one from the back (byte nbytes−1) — and they grow toward each other until the payload is fully packed.

Part Location Read Direction Content
1. Side information Ends at byte nbytes−1, grows backward Backward (LSB-first within each byte) Pbw, lastnz, lsbMode, ggind, TNS on/off per filter, pitch_present, SNS Stage 1 (ind_LF, ind_HF), SNS Stage 2 joint index, LTPF activation + pitch_index, FNF
2. Arithmetic coded data Starts at byte 0, grows forward Forward (MSB-first within each byte) TNS reflection coefficient indices, spectral coefficient MSBs (2-tuple symbols from the AC coder)
3. Signs and LSBs Immediately before the side information (backward-adjacent) Backward Sign bits for non-zero coefficients and LSB bitplanes. In lsbMode=1 this region is larger. Interleaved with the side info write pointer (same bp_side/mask_side).
4. Residual data Between end of AC data and start of signs/LSBs Backward Residual refinement bits (lsbMode=0) or stripped LSBs (lsbMode=1), packed into the space not used by parts 2 or 3.

Visual Representation of the Payload

The diagram below shows how the four regions fit within the nbytes payload. The two dynamic regions grow toward each other and must not overlap — if they would, the codec must truncate the spectrum or reduce residual data to fit.

← Arithmetic Coded
Grows forward
from byte 0
Residual
Fills remaining
space
Signs + LSBs
Backward-adjacent
to side info
Side Information →
Grows backward
from byte nbytes−1
Byte 0 → ← Dynamic meeting point → ← Byte nbytes−1

The side information and signs/LSBs all use the same backward write pointer (bp_side, mask_side). The AC data uses a separate forward write pointer (bp). The residual occupies whatever gap remains between them after all other data is written — the encoder computes exactly how many residual bits will fit and writes that many.

Why This Layout?

The bidirectional layout is a clever solution to two competing requirements:

  • Side information must be read first (the decoder needs Pbw, ggind, TNS flags, SNS data, etc. before it can decode the arithmetic stream), so it sits at the back where it can be read immediately without knowing the AC stream size.
  • Arithmetic-coded spectral data is variable-length and grows from the front — the range coder naturally emits a forward-growing byte stream.
  • Residual data fills whatever gap is left in the middle — the encoder doesn’t need to pre-compute its exact size before writing the other parts.

This layout is very efficient: every bit in the payload has a clear purpose, there is no padding, and the decoder can always parse the side information starting from byte nbytes−1 regardless of what is in the forward AC region.

3.6 External Rate Adaptation

Seamless Bitrate Changes

LC3 supports changing the compressed frame size (nbytes) at any time — even frame by frame. This is called external rate adaptation because the rate change comes from outside the codec (from a Bluetooth audio profile or a link-layer rate controller), not from the codec’s own bit allocation algorithm.

The change is seamless — no teardown, no re-initialization, no gap in audio. The encoder simply starts using the new nbytes value from the current frame onward, and the decoder automatically determines the new bitrate from the received packet size:

Side How Rate Change is Applied
Encoder Receives new byte_count from the profile for the current frame. Immediately uses this new nbytes — no state reset needed. The bit budget (nbits_spec) and all bitrate-dependent parameters (TNS weighting, LTPF gain, attack detector thresholds) are recalculated for the new nbytes.
Decoder Receives the new payload size (from the transport layer or profile). It uses the received packet size as byte_count for that frame — it does not need an explicit signal that the rate changed. All bitrate-dependent decoder parameters update automatically.

Which Parameters Change When nbytes Changes

Whenever nbytes changes, these variables are recomputed from nbits = 8 × nbytes:

Module What Changes
TNS (3.3.8 / 3.4.6) tns_lpc_weighting flag (Section 3.3.8.2, Eq. 71): active only when nbits < 48×Nms. Enables or disables LPC coefficient bandwidth expansion at low bitrates.
LTPF (3.3.9 / 3.4.9) gain_ltpf and gain_ind (Section 3.4.9.4): the LTPF filter gain decreases as bitrate increases and reaches 0 at high bitrates where the filter is not useful.
Attack Detector (3.3.6) Activation conditions (Section 3.3.6.1): the attack detector uses nbytes thresholds to decide whether to activate. A rate change may enable or disable it.
Bit budget (3.3.10.1) nbits_spec is recomputed with the new nbits. The rateFlag and modeFlag thresholds also update, changing the arithmetic coder tables used.
Global gain (3.3.10.2) ggoff depends on nbits. The gain bisection search starts fresh for each frame, so it naturally adapts to the new bit budget.

The session parameters {fs, Nms, Nf, Nc} do NOT change during rate adaptation — those require a full session teardown and re-initialization. Only nbytes changes.

Rate Adaptation in BlueZ LE Audio

In Bluetooth LE Audio, the BAP profile supports Content Control and Codec_Specific_Configuration updates mid-stream via ASE (Audio Stream Endpoint) reconfiguration. The LC3 codec simply responds to the new SDU size without any internal state reset:

/* Rate adaptation: encoder side — just change the byte count argument */
/* Previous frame: nbytes = 100 (80 kbps at 48 kHz 10ms)              */
/* New frame: nbytes = 60 (48 kbps)                                    */
/* No re-init needed — just pass new nbytes to encode call             */

int old_nbytes = 100;
int new_nbytes = 60;   /* received from profile/BAP controller */

/* Encode with new rate starting this frame */
lc3_encode(encoder,
           LC3_PCM_FORMAT_S16,
           pcm_input,
           /* stride */ 1,
           new_nbytes,       /* ← new byte count */
           payload_out);

/* Decoder side: byte count is inferred from received packet size      */
/* The ISO socket tells you how many bytes arrived — that is nbytes    */
ssize_t received = recvmsg(iso_fd, &msg, 0);   /* = 60 bytes */
lc3_decode(decoder, payload_rx, (int)received, LC3_PCM_FORMAT_S16, pcm_out, 1);
/* lc3_decode uses received length as nbytes — rate change automatic  */

Next in this Series

Section 3.7 — Tables and Constants: band index tables, LD-MDCT windows, SNS codebooks, TNS tables, LTPF interpolation filters, and arithmetic coder tables

Final Tutorial → All Tutorials

Leave a Reply

Your email address will not be published. Required fields are marked *