Audio Subsystem
The audio subsystem provides bidirectional audio transport between the vintage telephone handset and a Bluetooth-connected mobile phone. It also generates telephone signaling tones (dial tone, busy signal, etc.).
Architecture Overview
The audio subsystem consists of three main modules:
┌─────────────────────────────────────────────────────────────────────┐
│ Audio Subsystem │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ audio_output │ │ audio_bridge │ │ tones │ │
│ │ │ │ │ │ │ │
│ │ - I2S TX/RX init │ │ - Ring buffers │ │ - Tone defs │ │
│ │ - Tone generator │ │ - BT↔Phone tasks │ │ - Frequencies │ │
│ │ - Audio write │ │ - HFP callbacks │ │ - Cadences │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Module Responsibilities:
audio_output- I2S hardware management, tone generation, audio output APIaudio_bridge- Bluetooth ↔ Phone audio routing via ring bufferstones- Telephone tone definitions (frequencies, cadences)
Audio Data Flow
Bidirectional audio flows between the phone handset and Bluetooth:
Phone → Bluetooth (Transmit Path):
Phone Microphone
│
▼
┌─────────────┐
│ PCM1808 │ External ADC
│ ADC │ (Analog → Digital)
└─────────────┘
│ I2S RX (GPIO 35)
▼
┌─────────────┐
│ audio_rx_ │ Reads I2S frames
│ task │ every 20ms
└─────────────┘
│
▼
┌─────────────┐
│ bt_tx_ │ Ring buffer
│ ringbuf │ (3600 bytes)
└─────────────┘
│
▼
┌─────────────┐
│ HFP outgoing│ Bluetooth callback
│ callback │ reads from buffer
└─────────────┘
│
▼
Mobile Phone (via Bluetooth)
Bluetooth → Phone (Receive Path):
Mobile Phone (via Bluetooth)
│
▼
┌─────────────┐
│ HFP incoming│ Bluetooth callback
│ callback │ writes to buffer
└─────────────┘
│
▼
┌─────────────┐
│ bt_rx_ │ Ring buffer
│ ringbuf │ (3600 bytes)
└─────────────┘
│
▼
┌─────────────┐
│ audio_tx_ │ Reads from buffer,
│ task │ writes via audio_output
└─────────────┘
│ (blocked if tone playing)
▼
┌─────────────┐
│ PCM5100 │ External DAC
│ DAC │ (Digital → Analog)
└─────────────┘
│ I2S TX (GPIO 26)
▼
Phone Earpiece/Speaker
I2S Configuration
Both TX and RX channels share a single I2S port with unified configuration.
Note
Codec Choice Rationale: The PCM5100 (DAC) and PCM1808 (ADC) were selected for “wire and go” simplicity - they require no I2C initialization or MCLK signal. While their specs (106 dB / 98 dB SNR) far exceed 8kHz telephony requirements, this trade-off eliminates codec driver complexity entirely. See the circuit documentation for details.
I2S Parameters:
Parameter |
Value |
Notes |
|---|---|---|
I2S Port |
I2S_NUM_0 |
Single port for both TX/RX |
Sample Rate |
8000 Hz |
Standard telephony rate |
Bit Depth |
16-bit |
Signed PCM samples |
Slot Mode |
MONO |
Single channel for voice |
Format |
I2S Standard (Philips) |
Standard I2S framing |
MCLK |
Disabled |
Not required for external codec |
GPIO Pin Assignments:
GPIO |
Signal |
Description |
|---|---|---|
25 |
PCM_FSYNC |
Word select / frame sync (LRCLK) |
5 |
PCM_CLK_OUT |
Bit clock (BCLK) |
26 |
PCM_DOUT |
Audio output to DAC (TX) |
35 |
PCM_DIN |
Audio input from ADC (RX, input-only pin) |
Unified Channel Creation:
ESP-IDF requires both TX and RX channels on the same I2S port to be created in a single call:
// Create both channels together (required by ESP-IDF)
i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle);
// Initialize each with matching configuration
i2s_channel_init_std_mode(tx_handle, &tx_std_cfg);
i2s_channel_init_std_mode(rx_handle, &rx_std_cfg);
Bluetooth HFP Audio Path
The system uses the HCI audio path for Bluetooth HFP audio, enabling software control over audio routing.
Why HCI Path (not PCM)?
Path |
How It Works |
Trade-offs |
|---|---|---|
HCI |
Audio flows through HCI callbacks. Software handles audio frames via ring buffers. |
Enables tone mixing, volume control, audio processing. Small CPU overhead. |
PCM |
Bluetooth controller routes audio directly via I2S DMA. CPU never sees audio. |
Zero CPU overhead but no software control. Tones can’t interrupt calls. |
The HCI path is configured in sdkconfig.defaults:
CONFIG_BT_HFP_AUDIO_DATA_PATH_HCI=y
HFP Data Callbacks:
Two callbacks handle Bluetooth audio:
// Called when Bluetooth needs audio to send (Phone → BT)
static uint32_t bt_app_hf_client_outgoing_cb(uint8_t *p_buf, uint32_t sz)
{
// Read from bt_tx_ringbuf (filled by audio_rx_task)
// Return number of bytes provided
}
// Called when Bluetooth has audio to deliver (BT → Phone)
static void bt_app_hf_client_incoming_cb(const uint8_t *buf, uint32_t sz)
{
// Write to bt_rx_ringbuf (read by audio_tx_task)
}
Tone Generation
The audio subsystem generates authentic North American telephone tones.
Supported Tones:
Tone |
Frequencies |
Cadence |
Usage |
|---|---|---|---|
Dial Tone |
350 + 440 Hz |
Continuous |
Phone off-hook, ready to dial |
Ringback |
440 + 480 Hz |
2s on, 4s off |
Outgoing call ringing |
Busy Signal |
480 + 620 Hz |
0.5s on/off |
Called party busy |
Reorder (Fast Busy) |
480 + 620 Hz |
0.25s on/off |
Call cannot be completed |
Off-Hook Warning |
1400+2060+2450+2600 Hz |
0.1s on/off |
Phone left off-hook |
Call Waiting |
440 Hz |
0.3s beep |
Incoming call during active call |
Tone Priority:
Tones have priority over Bluetooth audio. When a tone is playing:
audio_output_tone_active()returns trueaudio_output_write()silently drops Bluetooth audioTone continues until explicitly stopped
Tone API:
// Start playing a tone (thread-safe)
esp_err_t audio_output_play_tone(tone_type_t tone);
// Stop the current tone
esp_err_t audio_output_stop_tone(void);
// Check if tone is active
bool audio_output_tone_active(void);
Tone Generation Task:
A dedicated FreeRTOS task generates tone samples using sine wave synthesis:
// Dual-frequency tone generation
float sample1 = sinf(phase1); // First frequency
float sample2 = sinf(phase2); // Second frequency
float mixed = (sample1 + sample2) / 2.0f;
buffer[i] = (int16_t)(32767.0f * TONE_VOLUME * mixed);
Ring Buffers
Two ring buffers decouple audio tasks from Bluetooth callbacks:
Buffer Configuration:
Size: 3600 bytes each (
AUDIO_HFP_RINGBUF_SIZE)Type: Byte buffer (
RINGBUF_TYPE_BYTEBUF)Capacity: ~11 audio frames (~220ms of audio)
Buffer Roles:
Buffer |
Direction |
Producer → Consumer |
|---|---|---|
|
Bluetooth → Phone |
HFP incoming callback → audio_tx_task |
|
Phone → Bluetooth |
audio_rx_task → HFP outgoing callback |
Initialization Sequence
Audio modules are initialized in specific order:
main()
└─ audio_output_init() # Creates I2S TX+RX, starts tone task
└─ audio_bridge_init() # Creates ring buffers, verifies RX handle
└─ bluetooth_init()
└─ bt_app_hf_register_data_callbacks() # Registers HFP callbacks
Critical Dependencies:
audio_output_init()must complete beforeaudio_bridge_init()audio_bridge_init()must complete before Bluetooth audio connectsHFP data callbacks must be registered after HFP client initialization
Module Files
audio_output (main/audio/audio_output.c, audio_output.h):
Owns I2S TX and RX channel handles
Provides
audio_output_write()for BT audio passthroughProvides
audio_output_get_rx_handle()for audio_bridgeRuns tone generation task
audio_bridge (main/audio/audio_bridge.c, audio_bridge.h):
Creates and manages ring buffers
Runs
audio_rx_task(Phone → BT) andaudio_tx_task(BT → Phone)Provides ring buffer accessors for HFP callbacks
tones (main/audio/tones.c, tones.h):
Defines
tone_type_tenumStores tone frequency/cadence parameters
Provides
tone_get_definition()accessor
Diagnostic Output
Initialization Logs:
I (1234) audio_output: Initializing audio output subsystem
I (1240) audio_output: Audio I/O initialized (TX: GPIO26, RX: GPIO35)
I (1245) audio_bridge: Initializing audio bridge
I (1250) audio_bridge: Audio bridge initialized (ring buffers ready)
Runtime Logs:
I (5000) audio_output: Playing tone: 0 (DIAL_TONE)
I (8000) audio_output: Playing tone: 10 (TONE_NONE - stopping)
I (9000) audio_bridge: Audio RX task started (Phone → Bluetooth)
I (9001) audio_bridge: Audio TX task started (Bluetooth → Phone)
References
Firmware Architecture - Overall firmware architecture
State Management - State management system
docs/source/solution-design/circuit/audio.rst- Audio circuit designdocs/source/implementation/circuit/pin-assignments.rst- GPIO assignments