BDK Audio - Cross-Platform Companion App for ESP32 Bluetooth Speakers
Control DSP | LED Effects | OTA Updates | Real-time Audio Meters
Kotlin Multiplatform app for Android and iOS
View ESP32 Firmware Project
- Overview
- Screenshots
- Features
- Architecture
- Requirements
- Installation
- OTA Updates
- BLE Protocol
- Project Structure
- Related Projects
- License
BDK Audio is a cross-platform companion application built with Kotlin Multiplatform for controlling ESP32-based Bluetooth speakers. It provides a unified interface for adjusting DSP parameters, selecting LED visualization effects, monitoring real-time audio levels, and performing encrypted firmware updates over-the-air.
| Feature | Description |
|---|---|
| BLE Scanning | Filter devices by service UUID |
| MTU Negotiation | Up to 517 bytes for fast OTA |
| Connection Monitoring | Real-time status updates |
| Multi-Device Support | Switch between speakers |
| Auto-Reconnect | Automatic reconnection on disconnect |
| Bluetooth State Detection | Auto-disconnect when BT disabled |
| Control | Range | Description |
|---|---|---|
| Bass | -12 to +12 dB | 80 Hz center frequency |
| Mid | -12 to +12 dB | 1 kHz center frequency |
| Treble | -12 to +12 dB | 8 kHz center frequency |
| Bass Boost | On/Off | Hardware bass enhancement |
| EQ Bypass | On/Off | Bypass all DSP processing |
| Channel Flip | On/Off | Swap L/R channels |
| Preset | Bass | Mid | Treble |
|---|---|---|---|
| Flat | 0 | 0 | 0 |
| Bass Boost | +6 | 0 | 0 |
| Treble Boost | 0 | 0 | +6 |
| V-Shape | +4 | -2 | +4 |
| Warm | +3 | +1 | -2 |
| Bright | -1 | 0 | +4 |
| Vocal | -2 | +4 | +1 |
| Electronic | +5 | -1 | +3 |
| Acoustic | +2 | +2 | +3 |
| Rock | +4 | -1 | +3 |
Audio-reactive visualization effects for 16x16 WS2812B LED matrix:
| ID | Effect | ID | Effect |
|---|---|---|---|
| 0 | Spectrum Bars | 11 | Particle Burst |
| 1 | Beat Pulse | 12 | Kaleidoscope |
| 2 | Ripple | 13 | Frequency Spiral |
| 3 | Fire | 14 | Bass Reactor |
| 4 | Plasma | 15 | Meteor Shower |
| 5 | Matrix Rain | 16 | Breathing |
| 6 | VU Meter | 17 | DNA Helix |
| 7 | Starfield | 18 | Audio Scope |
| 8 | Wave | 19 | Bouncing Balls |
| 9 | Fireworks | 20 | Lava Lamp |
| 10 | Rainbow Wave | 21 | Ambient (static color) |
| Parameter | Range | Description |
|---|---|---|
| Brightness | 0-100% | Global LED brightness |
| Speed | 0-100% | Effect animation speed |
| Color 1 | RGB | Primary effect color |
| Color 2 | RGB | Secondary/gradient color |
| Gradient Type | 0-2 | Color blend mode |
Change the A2DP audio codec directly from the app:
| Codec | Max Bitrate | Best For |
|---|---|---|
| SBC | 328 kbps | Universal compatibility |
| AAC | 256 kbps | Apple devices |
| aptX | 352 kbps | Low latency |
| aptX HD | 576 kbps | High-definition audio |
| LDAC | 990 kbps | Hi-Res listening |
| Feature | Description |
|---|---|
| Encrypted Transfer | AES-256-CBC encryption |
| Fast BLE Protocol | Batched ACK (7 fast + 1 ACK) |
| Progress Tracking | Real-time KB transferred |
| Verification | CHECK command before finalize |
| Auto-Reboot | Automatic ESP32 restart on completion |
shared/ # Cross-platform code (Kotlin)
├── commonMain/ # Shared business logic
│ ├── BleUnifiedProtocol.kt # BLE command/response protocol
│ ├── DeviceModels.kt # Data models
│ ├── EqPresets.kt # EQ preset definitions
│ └── LedEffects.kt # LED effect definitions
├── androidMain/ # Android-specific implementations
└── iosMain/ # iOS-specific implementations
app/ # Android app (Kotlin)
├── MainActivityRedesign.kt # Main control UI
├── OtaActivity.kt # OTA update screen
├── OtaDownloader.kt # Google Drive integration
└── SettingsActivity.kt # DSP toggles, codec selection
iosApp/ # iOS app (SwiftUI)
├── MainControlView.swift # Main control UI
├── SettingsView.swift # Settings screen
└── OtaView.swift # OTA update screen
The app uses a unified binary protocol with 3 BLE characteristics:
| Characteristic | UUID | Direction | Purpose |
|---|---|---|---|
| CMD | xxxx-b1xx |
Write | Send commands to ESP32 |
| STATUS | xxxx-b2xx |
Notify | Receive status updates |
| METER | xxxx-b3xx |
Notify | Real-time audio levels |
| Requirement | Value |
|---|---|
| Min SDK | 26 (Android 8.0 Oreo) |
| Target SDK | 36 (Android 16) |
| BLE Support | Required |
| Requirement | Value |
|---|---|
| Min iOS | 15.0 |
| Device | iPhone/iPad with BLE |
| Permission | Purpose |
|---|---|
BLUETOOTH_SCAN |
Scan for BLE devices (Android 12+) |
BLUETOOTH_CONNECT |
Connect to BLE devices (Android 12+) |
ACCESS_FINE_LOCATION |
Required for BLE scanning |
BLUETOOTH / BLUETOOTH_ADMIN |
Legacy (Android 11 and below) |
git clone https://github.com/WillyBilly06/BDK-AUDIO-APP.git
cd BDK-AUDIO-APP- Open in Android Studio (Hedgehog or newer)
- Sync Gradle dependencies
- Connect Android device or start emulator
- Build and run (
Shift+F10)
cd iosApp
open BDKAudio.xcodeproj- Open in Xcode 15+
- Select your development team
- Build and run on device (BLE requires physical device)
- Download latest APK from Releases
- Enable "Install from unknown sources" on Android
- Install the APK
-
Generate AES Key
cd tools python encrypt_firmware.py --generate-key -
Update Keys in:
tools/encrypt_firmware.py(AES_KEY)recovery/main/recovery_main.cpp(AES_KEY)app/.../OtaDownloader.kt(AES_KEY)- iOS:
OtaView.swift(aesKey)
-
Encrypt Firmware
python encrypt_firmware.py build/bt_audio_sink.bin --version 1.1.0
-
Upload to Google Drive
- Upload
ota_releases/1.1.0.encto Google Drive - Share with "Anyone with link"
- Create
latest.txtwith:1.1.0,<FILE_ID>
- Upload
-
Update File IDs in
OtaDownloader.kt:private const val GDRIVE_LATEST_TXT_ID = "YOUR_LATEST_TXT_FILE_ID"
All commands are binary packets:
[CMD_ID:1][PAYLOAD:N]
| Command | ID | Payload | Description |
|---|---|---|---|
| SET_EQ | 0x10 | bass, mid, treble | Set EQ values |
| SET_EQ_PRESET | 0x11 | presetId | Apply preset |
| SET_CONTROL | 0x12 | flags | DSP toggles |
| SET_NAME | 0x13 | name[20] | Rename device |
| SET_LED | 0x20 | effect, brightness, speed, colors | Full LED config |
| SET_LED_EFFECT | 0x21 | effectId | Change effect only |
| SET_LED_BRIGHTNESS | 0x22 | brightness | Brightness only |
| OTA_BEGIN | 0x40 | size[4] | Start OTA |
| OTA_DATA | 0x41 | seq[2], data[N] | Firmware chunk |
| OTA_END | 0x42 | - | Finalize OTA |
| REQUEST_STATUS | 0x50 | - | Request full status |
| PING | 0xFF | - | Connection check |
BDK-AUDIO-APP/
├── README.md
├── app/ # Android app module
│ ├── src/main/
│ │ ├── java/com/example/myspeaker/
│ │ │ ├── MainActivityRedesign.kt
│ │ │ ├── SettingsActivity.kt
│ │ │ ├── OtaActivity.kt
│ │ │ ├── OtaDownloader.kt
│ │ │ └── ...
│ │ └── res/
│ │ ├── layout/
│ │ ├── drawable/
│ │ └── values/
│ └── build.gradle.kts
├── shared/ # Kotlin Multiplatform shared code
│ ├── src/
│ │ ├── commonMain/kotlin/
│ │ ├── androidMain/kotlin/
│ │ └── iosMain/kotlin/
│ └── build.gradle.kts
├── iosApp/ # iOS SwiftUI app
│ └── BDKAudio/
│ ├── Views/
│ ├── ViewModels/
│ └── Utilities/
└── screenshots/
| Project | Description |
|---|---|
| ESP32-A2DP-SINK-WITH-CODECS-UPDATED | ESP32 firmware (ESP-IDF 5.5.2) |
| esp32-a2dp-sink-with-LDAC-APTX-AAC | Original ESP-IDF 5.3 version |
Created by WillyBilly
This project is proprietary software. All rights reserved.



