LC3 Codec: High-Level Operation and Decoder Interfaces

 

LC3 Codec: High-Level Operation and Decoder Interfaces
Chapter 2, Sections 2.3 and 2.4 — LC3 Specification v1.0 (Bluetooth SIG)
Series
LC3 Deep Dive
Chapter
2 — Sections 2.3 & 2.4
Level
Beginner–Intermediate

In this tutorial, you will learn how an LC3 encode-transmit-decode session actually works end-to-end — including both fixed bitrate and variable bitrate operation. You will also learn the full set of decoder inputs and outputs and how the Bad Frame Indication (BFI) flag controls the decoder behavior.

Keywords

BFI Flag payloadRX OutputPCM Fixed Bitrate Variable Bitrate Packet Loss Concealment Multi-channel Decoder Interface

2.3 LC3 High-Level Operation Description

How rate parameters flow from profile to link layer

LC3 sits between the Bluetooth audio profile (like BAP — Basic Audio Profile) and the Bluetooth LE link layer. The profile defines byte_count — the compressed frame size in bytes that the LC3 encoder shall produce. As long as byte_count is within the link layer’s maximum frame size, the payload is transmitted over the air.

On the receiver side, the LC3 decoder gets the received payload (payloadRX) plus the same byte_count — it needs to know how many bytes to expect for correct decoding. It also receives a BFI (Bad Frame Indication) flag. If BFI = 1, the payload has bit errors and the decoder should not attempt to decode it.

Case 1: Fixed Bitrate, Single Channel

This is the simplest case. The same byte_count is used for every single frame throughout the session. Both encoder and decoder use the same bits_per_audio_sample setting (though they are allowed to differ).

Encoder side (Transmitter):

  • Receives InputPCM buffer of size Nf × bits_per_audio_sample/8 bytes.
  • Compresses to exactly byte_count bytes → payloadTX.
  • Transmits over the Bluetooth LE ISO channel.

Decoder side (Receiver):

  • Receives payloadRX of size byte_count bytes.
  • If bit errors are detected → set BFI = 1, otherwise BFI = 0.
  • If BFI = 0: decode normally → produce OutputPCM.
  • If BFI ≠ 0: skip decoding, use Packet Loss Concealment (PLC) to generate substitute audio.

Case 2: Variable Bitrate, Multiple Channels

LC3 also supports external rate control — where the profile can change byte_count per frame and per channel. This is used for things like dynamic bitrate adjustment without tearing down the stream.

Key rules in multi-channel variable bitrate mode:

Item Rule
byte_count[k] Can be different per channel k and per frame. Range: 20–400 bytes.
Number of channels (Nc) Fixed for the entire session. Does not change.
bits_per_audio_sample Can differ between encoder (enc) and decoder (dec). Same bit depth for all channels.
BFI[k] One flag per channel. Implementations should handle all channels jointly (mute all or none).
Total frame size Sum of all channel byte_count values. Defined by the profile (e.g. BAP).

Allowing different bits_per_audio_sample_enc and bits_per_audio_sample_dec means a decoder with only 16-bit capability can still decode a 24-bit or 32-bit encoded stream — it just outputs 16-bit samples.

The BFI Flag — How it Works

The Bad Frame Indication (BFI) flag is set externally by the Bluetooth link layer or the application — not by the LC3 decoder itself. It is a binary signal:

BFI Value Meaning Decoder Action
BFI = 0 No bit errors detected — payload is assumed correct Decode payloadRX normally → output PCM
BFI ≠ 0 Bit errors detected, or the LC3 internal bitstream is flagged as corrupt Do NOT decode payloadRX. Apply PLC (Packet Loss Concealment) instead.

LC3 also defines internal BEC (Bit Error Condition) fields within the bitstream itself. These allow the encoder to mark its own payload as corrupt, and the decoder can detect invalid bitstream states during parsing and trigger PLC automatically.

Note: The LC3 payload has no timestamps, no sequence numbers. Timing and ordering is handled entirely by the Bluetooth LE ISO transport.

2.4 Decoder Interfaces

Session Configuration Parameters (set once)

Like the encoder, the decoder requires a one-time session configuration. These must match the encoder except for bits_per_audio_sample_dec.

Parameter Allowed Values Description
{Fs, Nms, Nf} Same as encoder Sampling rate, frame duration, frame size in samples. Must be identical to encoder session config.
Nc 1 to Nc_max Number of audio channels. Fixed for the session.
bits_per_audio_sample_dec 16, 24, or 32 bits Bit depth of the output PCM. May differ from encoder’s input bit depth.
byte_count_max_dec Maximum of byte_count range Maximum allowed byte_count per channel. Used to pre-allocate decoder buffers once — avoids runtime memory allocation during variable-bitrate operation.

Per-Frame Inputs to the Decoder

Input Size / Range Description
BFI[Nc] 0 or 1 per channel Bad Frame Indication per channel. Set by the link layer or application. 0 = good frame, 1 = bad/corrupt frame.
byte_count[Nc] 20 to 400 bytes per channel The size of the received payload for each channel. The decoder uses this to parse the bitstream correctly.
payloadRX[Nc] byte_count[k] bytes per channel k The received compressed audio payload for each channel. If BFI[k] ≠ 0, this data should not be decoded.

Per-Frame Output from the Decoder

Output Size Description
OutputPCM[Nc] Nc × Nf × (bits_dec/8) bytes The reconstructed PCM audio for all channels. Integer samples using bits_per_audio_sample_dec bits per sample. For a bad frame (BFI≠0), the output is the PLC substitute audio.

BFI Handling in BlueZ LE Audio

When reading LC3 audio data from a BlueZ ISO socket, the kernel delivers an ancillary message indicating whether the received Bluetooth SDU had errors. This maps directly to the BFI flag that you pass to the LC3 decoder.

/* Reading from BlueZ ISO socket and extracting BFI from the cmsg   */
/* The BT_SCM_PKT_STATUS ancillary message carries the packet status */

struct msghdr  msg = {};
struct iovec   iov;
struct cmsghdr *cmsg;
uint8_t        payload[400];    /* max LC3 byte_count per channel    */
uint8_t        control[64];
uint8_t        bfi = 0;         /* default: good frame               */

iov.iov_base = payload;
iov.iov_len  = sizeof(payload);

msg.msg_iov        = &iov;
msg.msg_iovlen     = 1;
msg.msg_control    = control;
msg.msg_controllen = sizeof(control);

ssize_t len = recvmsg(iso_fd, &msg, 0);
if (len < 0) { perror("recvmsg"); return; }

/* Walk control messages to find packet status */
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
    if (cmsg->cmsg_level == SOL_BLUETOOTH &&
        cmsg->cmsg_type  == BT_SCM_PKT_STATUS) {
        uint8_t pkt_status = *(uint8_t *)CMSG_DATA(cmsg);
        /* 0x00 = valid, 0x01 = possibly invalid, 0x02 = no data  */
        bfi = (pkt_status != 0x00) ? 1 : 0;
    }
}

/* Now pass payload and bfi to the LC3 decoder */
lc3_decode(decoder, payload, (int)len, LC3_PCM_FORMAT_S16,
           pcm_output, /* stride */ 1);
/* For a real implementation you would check bfi first:
   if (bfi) { run_plc(decoder, pcm_output); }
   else      { lc3_decode(decoder, payload, len, ...); }
*/

Quick Summary

Concept Key Point
Fixed bitrate byte_count is the same every frame. Simplest mode.
Variable bitrate byte_count can change per frame and per channel. Profile-driven.
BFI = 0 Good frame. Decode payloadRX normally.
BFI ≠ 0 Bad frame. Do not decode. Apply PLC.
Decoder session config Same as encoder except bits_per_audio_sample_dec can differ.
Decoder output OutputPCM: Nc × Nf × (bits_dec/8) bytes of reconstructed audio.

Next in this Series

Chapter 3 begins: General Codec Description — math symbols, operators, and the feature summary table

Next Tutorial → All Tutorials

Leave a Reply

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