LC3 Deep Dive
2 — Sections 2.1 & 2.2
Beginner–Intermediate
This tutorial introduces the LC3 (Low Complexity Communication Codec) — the audio codec defined for Bluetooth LE Audio. You will learn what LC3 is, why it exists, how it handles audio frames, and exactly what the encoder needs as input and produces as output. No DSP background needed to follow this post.
2.1 What is LC3?
LC3 stands for Low Complexity Communication Codec. It was designed by the Bluetooth SIG (originally for hearing aid applications) and is now the core audio codec for Bluetooth LE Audio. It can encode both speech and music across a wide range of bitrates and sampling frequencies.
Think of LC3 like this: you have raw audio coming in as PCM samples (the same raw format stored in WAV files). LC3 takes a small chunk of that audio (called a frame), compresses it into a small packet of bytes, and sends those bytes over Bluetooth. On the other end, the LC3 decoder reconstructs the audio from those bytes.
The key design goals of LC3 are: low delay, low CPU complexity, and high quality at low bitrates — all critical for battery-powered hearing aids and wireless earbuds.
Frame Intervals and Sampling Frequencies
LC3 processes audio in fixed-size frames. A frame is a block of PCM samples covering either 10 ms or 7.5 ms of audio. You choose one or the other at session setup — it cannot change mid-stream.
For a sampling rate of 44.1 kHz, LC3 uses the same frame size as 48 kHz, which means the actual frame duration is slightly longer: 10.884 ms instead of 10 ms, and 8.163 ms instead of 7.5 ms. This is an important detail when working with 44.1 kHz audio.
| Sampling Rate (fs) | 10 ms Frame Size (Nf samples) | 7.5 ms Frame Size (Nf samples) |
|---|---|---|
| 8,000 Hz | 80 | 60 |
| 16,000 Hz | 160 | 120 |
| 24,000 Hz | 240 | 180 |
| 32,000 Hz | 320 | 240 |
| 44,100 Hz | 480 (same as 48k) | 360 (same as 48k) |
| 48,000 Hz | 480 | 360 |
Note: 44.1 kHz uses 480/360 samples but the real-time duration is slightly longer because 480/44100 ≠ 10/1000.
Algorithmic Delay
The Total Codec Algorithmic Delay is the end-to-end delay introduced by encoding + decoding. It equals the frame duration plus an extra look-ahead window that the MDCT (the transform inside the encoder) needs to process a complete frame.
| Frame Duration | Sampling Rate | Total Algorithmic Delay |
|---|---|---|
| 10 ms | 8–48 kHz | 12.5 ms |
| 10 ms | 44.1 kHz | 13.605 ms |
| 7.5 ms | 8–48 kHz | 11.5 ms |
| 7.5 ms | 44.1 kHz | 12.517 ms |
For real-time applications like hearing aids or Bluetooth calls, 11.5–12.5 ms total delay is excellent. Compare this to older codecs like SBC which can reach 20–30 ms.
Bitrate and Payload Size
LC3 does not have a fixed bitrate. Instead, the profile or application tells the encoder how many bytes to put in each compressed frame. This is called byte_count. The encoder always produces a payload of exactly that many bytes.
| Parameter | Value | What It Means |
|---|---|---|
byte_count range |
20 to 400 bytes | Per channel, per frame |
| Bitrate range (10 ms) | 16,000 – 320,000 bps | 20 bytes × 8 × 100 fps = 16 kbps |
| Bitrate range (7.5 ms) | 21,334 – 426,667 bps | Shorter frame = more frames per second |
| Bitrate mode | Constant or variable | byte_count can change per frame |
The LC3 payload does not include timestamps or sequence numbers. That is handled by the Bluetooth transport layer above it (e.g. the ISO channel in LE Audio).
2.2 Encoder Interfaces
The LC3 encoder works in two phases: session setup (done once at the beginning) and per-frame encoding (done for every audio frame). Here is what each phase requires.
Session Configuration Parameters (set once)
These parameters are fixed for the entire session. Both the encoder and decoder must be configured with the same values (except bits_per_audio_sample which can differ).
| Parameter | Allowed Values | Description |
|---|---|---|
{Fs, Nms, Nf} |
Fs = 8/16/24/32/44.1/48 kHz Nms = 7.5 or 10 ms |
The sampling rate, frame duration in ms, and resulting frame size in samples. All three must match the decoder. |
Nc |
1 to Nc_max (set by profile) | Number of audio channels. Each channel is encoded independently as a mono stream. |
bits_per_audio_sample_enc |
16, 24, or 32 bits | Bit depth of the input PCM. Can be different from the decoder’s output bit depth. |
Per-Frame Inputs (provided for every frame)
| Input | Size / Range | Description |
|---|---|---|
byte_count[Nc] |
20 to 400 bytes per channel | How many bytes the encoder should produce for this frame, per channel. This controls the bitrate. For mono, it is a single value equal to nbytes. |
InputPCM[Nc] |
Nc × Nf × (bits/8) bytes | Raw PCM audio data for all channels. Each channel has Nf samples. Samples are integers in the range [−2^(s-1), 2^(s-1)−1] where s is the bit depth. |
Per-Frame Output
| Output | Size | Description |
|---|---|---|
payloadTX[Nc] |
Exactly byte_count[k] bytes per channel k | The compressed audio payload for each channel. The total size for all channels is the sum of all byte_count[k] values. This payload is sent over the Bluetooth LE ISO channel. |
The output payload size is always exactly byte_count — the encoder fills the payload to the exact byte boundary. There is no padding or variable-length framing inside the payload itself.
LC3 in BlueZ — Codec Configuration
In BlueZ, the LC3 codec parameters (sampling rate, frame duration, bitrate) are exchanged through the BAP (Basic Audio Profile) codec configuration mechanism. When you register a codec via the BlueZ Media API, you supply a Capabilities structure that encodes these LC3 session parameters as LTV (Length-Type-Value) fields defined in the Bluetooth Assigned Numbers document.
Example: LC3 codec configuration structure used with BlueZ ISO sockets
/* LC3 Codec_Specific_Configuration LTV fields (Bluetooth Assigned Numbers) */
/* These are the session parameters passed to the LC3 encoder at init time */
struct lc3_config {
uint8_t sampling_freq; /* 0x01 = 8kHz, 0x03 = 16kHz, 0x05 = 24kHz,
0x06 = 32kHz, 0x07 = 44.1kHz, 0x08 = 48kHz */
uint8_t frame_duration; /* 0x00 = 7.5ms, 0x01 = 10ms */
uint8_t channel_alloc; /* Audio channel allocation bitmask */
uint16_t octets_per_frame; /* byte_count: 20 to 400 */
uint8_t frames_per_sdu; /* Number of LC3 frames per Bluetooth SDU */
};
/* BlueZ ISO socket: set codec config via setsockopt before connect/bind */
struct bt_iso_qos qos = {
.ucast = {
.cig = BT_ISO_QOS_CIG_UNSET,
.cis = BT_ISO_QOS_CIS_UNSET,
.sdu = 100, /* octets_per_frame for 48kHz 10ms at ~80kbps */
.phy = BT_ISO_PHY_2M,
.rtn = 2,
}
};
setsockopt(fd, SOL_BLUETOOTH, BT_ISO_QOS, &qos, sizeof(qos));
The octets_per_frame field in the codec config maps directly to the LC3 spec’s byte_count parameter — it is what the encoder reads each frame to know how many bytes to produce.
Quick Summary
| What | Key Point |
|---|---|
| LC3 is a | Block-based transform audio codec designed for Bluetooth LE Audio |
| Frame durations | 7.5 ms or 10 ms — chosen once at session start |
| Supported sample rates | 8 / 16 / 24 / 32 / 44.1 / 48 kHz |
| Bitrate | Controlled by byte_count (20–400 bytes/frame/channel) |
| Encoder inputs | PCM samples + byte_count per frame |
| Encoder output | payloadTX — exactly byte_count bytes per channel |
| Total delay | 11.5 ms (7.5 ms mode) or 12.5 ms (10 ms mode) |
Next in this Series
Continue with Section 2.3 and 2.4 — LC3 Fixed vs Variable Bitrate Operation and Decoder Interfaces
