esp32-a2dp-xq 0.1.0
ESP32 A2DP source with SBC-XQ
Loading...
Searching...
No Matches
a2dp_xq.h File Reference

esp32-a2dp-xq: ESP32 as a Bluetooth A2DP source (sends audio to headphones and speakers) with SBC-XQ. More...

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "esp_err.h"
Include dependency graph for a2dp_xq.h:

Go to the source code of this file.

Data Structures

struct  a2dp_xq_device_t
 
struct  a2dp_xq_status_t
 
struct  a2dp_xq_event_t
 
struct  a2dp_xq_config_t
 
struct  a2dp_xq_stats_t
 

Macros

#define A2DP_XQ_VERSION_MAJOR   0
 
#define A2DP_XQ_VERSION_MINOR   1
 
#define A2DP_XQ_VERSION_PATCH   0
 
#define A2DP_XQ_VERSION_STRING   "0.1.0"
 
#define A2DP_XQ_SAMPLE_RATE   44100u
 The Bluedroid SBC encoder takes 16-bit stereo PCM at this rate.
 
#define A2DP_XQ_CONFIG_DEFAULT()
 

Typedefs

typedef void(* a2dp_xq_event_cb_t) (const a2dp_xq_event_t *event, void *user)
 Both callbacks run in the library's worker task, outside its locks: they may call back into the library.
 
typedef void(* a2dp_xq_remote_cb_t) (a2dp_xq_remote_t cmd, int value, void *user)
 

Enumerations

enum  a2dp_xq_state_t {
  A2DP_XQ_STATE_OFF = 0 , A2DP_XQ_STATE_IDLE , A2DP_XQ_STATE_SCANNING , A2DP_XQ_STATE_CONNECTING ,
  A2DP_XQ_STATE_CONNECTED
}
 
enum  a2dp_xq_remote_t {
  A2DP_XQ_REMOTE_PLAY_PAUSE = 0 , A2DP_XQ_REMOTE_PLAY , A2DP_XQ_REMOTE_PAUSE , A2DP_XQ_REMOTE_STOP ,
  A2DP_XQ_REMOTE_NEXT , A2DP_XQ_REMOTE_PREV , A2DP_XQ_REMOTE_VOL_UP , A2DP_XQ_REMOTE_VOL_DOWN ,
  A2DP_XQ_REMOTE_VOLUME_ABS , A2DP_XQ_REMOTE_LINK_DOWN
}
 
enum  a2dp_xq_event_type_t { A2DP_XQ_EVENT_STATE = 0 , A2DP_XQ_EVENT_DEVICE_FOUND , A2DP_XQ_EVENT_PAIRING_FAILED , A2DP_XQ_EVENT_CODEC }
 

Functions

bool a2dp_xq_supported (void)
 True when this build can negotiate SBC-XQ (ESP-IDF >= 6.0 with the internal SBC encoder).
 
esp_err_t a2dp_xq_init (const a2dp_xq_config_t *cfg)
 Start the controller (BR/EDR only, the BLE memory is released), Bluedroid, A2DP source and AVRCP.
 
void a2dp_xq_deinit (void)
 Disconnect and release everything (controller disabled, memory returned).
 
bool a2dp_xq_is_initialized (void)
 
esp_err_t a2dp_xq_start (void)
 Open the audio path: the stream to the headphones starts as soon as they are connected.
 
void a2dp_xq_stop (void)
 Close the audio path (queued audio is dropped) and suspend the stream.
 
void a2dp_xq_pause (bool paused)
 Pause keeps the queued audio; after 5 s of pause the stream is suspended so the headphones can sleep their decoder.
 
int32_t a2dp_xq_write_s16 (const int16_t *pcm, uint32_t frames, uint32_t timeout_ms)
 Queue interleaved stereo frames at A2DP_XQ_SAMPLE_RATE.
 
int32_t a2dp_xq_write_q31 (const int32_t *pcm, uint32_t frames, uint32_t timeout_ms)
 Same from left-justified 32-bit samples (24-bit audio in the top bits): reduced to 16 bits with TPDF dither, so quiet passages keep their resolution.
 
void a2dp_xq_flush (void)
 Drop what is queued (seek, skip).
 
uint32_t a2dp_xq_buffered_frames (void)
 Frames queued and not yet sent.
 
void a2dp_xq_set_volume_db (float db)
 Output volume in dB (<= 0).
 
float a2dp_xq_volume_to_db (int v127)
 AVRCP absolute volume (0..127) <-> dB: 127 = 0 dB, 1..127 span 60 dB in equal steps, 0 = mute.
 
uint8_t a2dp_xq_volume_from_db (float db)
 
void a2dp_xq_get_status (a2dp_xq_status_t *out)
 
void a2dp_xq_describe (char *out, size_t out_size)
 "SBC-XQ 452 kbps", "SBC 328 kbps", "" when not connected.
 
void a2dp_xq_scan (bool start)
 Inquiry for about 10 s; A2DP_XQ_EVENT_DEVICE_FOUND per device (audio devices only).
 
uint32_t a2dp_xq_devices (a2dp_xq_device_t *out, uint32_t max)
 Bonded devices and the ones found by the last scan: connected first, then paired, then by signal strength.
 
esp_err_t a2dp_xq_connect (const char *addr)
 
void a2dp_xq_disconnect (void)
 
void a2dp_xq_forget (const char *addr)
 Remove the bond and the stored results of a device.
 
esp_err_t a2dp_xq_set_xq_allowed (const char *addr, bool allowed)
 Allow or forbid SBC-XQ for one device (kept in state_dir); takes effect on the next connection.
 
void a2dp_xq_get_stats (a2dp_xq_stats_t *out)
 

Detailed Description

esp32-a2dp-xq: ESP32 as a Bluetooth A2DP source (sends audio to headphones and speakers) with SBC-XQ.

  • SBC-XQ: Dual Channel, bitpool 38, 452 kbps at 44.1 kHz, when the headphones accept it; otherwise Joint Stereo with bitpool up to 53 (328 kbps), the best standard SBC setting. Requires ESP-IDF 6.0 or newer (esp_a2d_source_set_pref_mcc). On older ESP-IDF, including the Arduino core 3.x, the library runs with the stack's standard SBC configuration.
  • Per-device memory: the result of XQ on each pair of headphones (works / failed twice) and the user's choice are kept in a small text file, plus a built-in list of models known to break with Dual Channel.
  • PCM FIFO (~300 ms, PSRAM when available) with TPDF dither from 24/32-bit to 16-bit.
  • AVRCP: buttons on the headphones (play, pause, next, volume...) and absolute volume in both directions.
  • Discovery, pairing (Secure Simple Pairing "just works", legacy PIN 0000), reconnect to the last device.

Classic ESP32 only (ESP32-S3/C3/C6 have no Bluetooth Classic). The application writes stereo PCM at 44.1 kHz:

a2dp_xq_config_t cfg = A2DP_XQ_CONFIG_DEFAULT(); cfg.device_name = "My speaker source"; a2dp_xq_init(&cfg); a2dp_xq_connect("aa:bb:cc:dd:ee:ff"); // or a2dp_xq_scan(true) and pick a device a2dp_xq_start(); for (;;) a2dp_xq_write_s16(pcm, frames, portMAX_DELAY);

Macro Definition Documentation

◆ A2DP_XQ_CONFIG_DEFAULT

#define A2DP_XQ_CONFIG_DEFAULT ( )
Value:
{ \
.device_name = "ESP32", .allow_sbc_xq = true, .auto_reconnect = true, .last_device = NULL, \
.state_dir = NULL, .event_cb = NULL, .event_user = NULL, .remote_cb = NULL, .remote_user = NULL, \
.worker_core = 0, \
}

◆ A2DP_XQ_SAMPLE_RATE

#define A2DP_XQ_SAMPLE_RATE   44100u

The Bluedroid SBC encoder takes 16-bit stereo PCM at this rate.

◆ A2DP_XQ_VERSION_MAJOR

#define A2DP_XQ_VERSION_MAJOR   0

◆ A2DP_XQ_VERSION_MINOR

#define A2DP_XQ_VERSION_MINOR   1

◆ A2DP_XQ_VERSION_PATCH

#define A2DP_XQ_VERSION_PATCH   0

◆ A2DP_XQ_VERSION_STRING

#define A2DP_XQ_VERSION_STRING   "0.1.0"

Typedef Documentation

◆ a2dp_xq_event_cb_t

typedef void(* a2dp_xq_event_cb_t) (const a2dp_xq_event_t *event, void *user)

Both callbacks run in the library's worker task, outside its locks: they may call back into the library.

Keep them short.

◆ a2dp_xq_remote_cb_t

typedef void(* a2dp_xq_remote_cb_t) (a2dp_xq_remote_t cmd, int value, void *user)

Enumeration Type Documentation

◆ a2dp_xq_event_type_t

Enumerator
A2DP_XQ_EVENT_STATE 

state changed: event.state

A2DP_XQ_EVENT_DEVICE_FOUND 

discovery found or named a device: event.device

A2DP_XQ_EVENT_PAIRING_FAILED 

event.device.addr

A2DP_XQ_EVENT_CODEC 

the stream configuration is known: a2dp_xq_get_status()

◆ a2dp_xq_remote_t

Enumerator
A2DP_XQ_REMOTE_PLAY_PAUSE 
A2DP_XQ_REMOTE_PLAY 
A2DP_XQ_REMOTE_PAUSE 
A2DP_XQ_REMOTE_STOP 
A2DP_XQ_REMOTE_NEXT 
A2DP_XQ_REMOTE_PREV 
A2DP_XQ_REMOTE_VOL_UP 
A2DP_XQ_REMOTE_VOL_DOWN 
A2DP_XQ_REMOTE_VOLUME_ABS 

value = 0..127 set on the headphones (their own volume changed)

A2DP_XQ_REMOTE_LINK_DOWN 

audio is started but no headphones are connected and no reconnect is pending (switched off, out of range): pause

◆ a2dp_xq_state_t

Enumerator
A2DP_XQ_STATE_OFF 

not initialized

A2DP_XQ_STATE_IDLE 

no headphones connected

A2DP_XQ_STATE_SCANNING 

discovery running

A2DP_XQ_STATE_CONNECTING 
A2DP_XQ_STATE_CONNECTED 

Function Documentation

◆ a2dp_xq_buffered_frames()

uint32_t a2dp_xq_buffered_frames ( void  )

Frames queued and not yet sent.

◆ a2dp_xq_connect()

esp_err_t a2dp_xq_connect ( const char *  addr)

◆ a2dp_xq_deinit()

void a2dp_xq_deinit ( void  )

Disconnect and release everything (controller disabled, memory returned).

◆ a2dp_xq_describe()

void a2dp_xq_describe ( char *  out,
size_t  out_size 
)

"SBC-XQ 452 kbps", "SBC 328 kbps", "" when not connected.

◆ a2dp_xq_devices()

uint32_t a2dp_xq_devices ( a2dp_xq_device_t *  out,
uint32_t  max 
)

Bonded devices and the ones found by the last scan: connected first, then paired, then by signal strength.

Returns the number written.

◆ a2dp_xq_disconnect()

void a2dp_xq_disconnect ( void  )

◆ a2dp_xq_flush()

void a2dp_xq_flush ( void  )

Drop what is queued (seek, skip).

◆ a2dp_xq_forget()

void a2dp_xq_forget ( const char *  addr)

Remove the bond and the stored results of a device.

◆ a2dp_xq_get_stats()

void a2dp_xq_get_stats ( a2dp_xq_stats_t *  out)

◆ a2dp_xq_get_status()

void a2dp_xq_get_status ( a2dp_xq_status_t *  out)

◆ a2dp_xq_init()

esp_err_t a2dp_xq_init ( const a2dp_xq_config_t *  cfg)

Start the controller (BR/EDR only, the BLE memory is released), Bluedroid, A2DP source and AVRCP.

Takes about 100 KB of internal RAM. ESP_ERR_NOT_SUPPORTED when Bluetooth Classic or A2DP is disabled in sdkconfig.

◆ a2dp_xq_is_initialized()

bool a2dp_xq_is_initialized ( void  )

◆ a2dp_xq_pause()

void a2dp_xq_pause ( bool  paused)

Pause keeps the queued audio; after 5 s of pause the stream is suspended so the headphones can sleep their decoder.

◆ a2dp_xq_scan()

void a2dp_xq_scan ( bool  start)

Inquiry for about 10 s; A2DP_XQ_EVENT_DEVICE_FOUND per device (audio devices only).

◆ a2dp_xq_set_volume_db()

void a2dp_xq_set_volume_db ( float  db)

Output volume in dB (<= 0).

Headphones with AVRCP absolute volume get it as SetAbsoluteVolume and play at their own gain; for other headphones the library attenuates in its 16-bit conversion. Their own volume changes arrive as A2DP_XQ_REMOTE_VOLUME_ABS.

◆ a2dp_xq_set_xq_allowed()

esp_err_t a2dp_xq_set_xq_allowed ( const char *  addr,
bool  allowed 
)

Allow or forbid SBC-XQ for one device (kept in state_dir); takes effect on the next connection.

Allowing again also clears an earlier failure verdict.

◆ a2dp_xq_start()

esp_err_t a2dp_xq_start ( void  )

Open the audio path: the stream to the headphones starts as soon as they are connected.

◆ a2dp_xq_stop()

void a2dp_xq_stop ( void  )

Close the audio path (queued audio is dropped) and suspend the stream.

◆ a2dp_xq_supported()

bool a2dp_xq_supported ( void  )

True when this build can negotiate SBC-XQ (ESP-IDF >= 6.0 with the internal SBC encoder).

◆ a2dp_xq_volume_from_db()

uint8_t a2dp_xq_volume_from_db ( float  db)

◆ a2dp_xq_volume_to_db()

float a2dp_xq_volume_to_db ( int  v127)

AVRCP absolute volume (0..127) <-> dB: 127 = 0 dB, 1..127 span 60 dB in equal steps, 0 = mute.

◆ a2dp_xq_write_q31()

int32_t a2dp_xq_write_q31 ( const int32_t *  pcm,
uint32_t  frames,
uint32_t  timeout_ms 
)

Same from left-justified 32-bit samples (24-bit audio in the top bits): reduced to 16 bits with TPDF dither, so quiet passages keep their resolution.

◆ a2dp_xq_write_s16()

int32_t a2dp_xq_write_s16 ( const int16_t *  pcm,
uint32_t  frames,
uint32_t  timeout_ms 
)

Queue interleaved stereo frames at A2DP_XQ_SAMPLE_RATE.

Blocks up to timeout_ms while the FIFO is full (it drains at the real-time rate while the stream runs). Returns the frames accepted (may be fewer than requested) or -1 when the audio path is not started.