From 99d6c664bea743ae9bbba280cc0e3effffe3cb78 Mon Sep 17 00:00:00 2001 From: yangfeng Date: Mon, 18 May 2026 15:44:45 +0800 Subject: [PATCH 1/3] feat(bt): A2DP source supports AAC codec --- .../bluedroid/api/include/api/esp_a2dp_api.h | 47 + .../btc/profile/std/a2dp/bta_av_co.c | 15 +- .../btc/profile/std/a2dp/btc_a2dp_latm_raw.c | 809 +++++++++++++++++- .../std/a2dp/btc_a2dp_sink_ext_coedc.c | 58 +- .../bluedroid/btc/profile/std/a2dp/btc_av.c | 51 +- .../std/a2dp/include/btc_a2dp_latm_raw.h | 104 ++- .../btc/profile/std/a2dp/include/btc_av_co.h | 8 +- .../btc/profile/std/include/btc_av.h | 7 +- 8 files changed, 1040 insertions(+), 59 deletions(-) diff --git a/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h b/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h index 9811a07ecfa..cf8165c7f32 100644 --- a/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h +++ b/components/bt/host/bluedroid/api/include/api/esp_a2dp_api.h @@ -67,6 +67,50 @@ typedef uint16_t esp_a2d_psc_t; /*!< Protocol service capabilitie #define ESP_A2D_SBC_CIE_ALLOC_MTHD_SRN _Pragma("GCC warning \"'ESP_A2D_SBC_CIE_ALLOC_MTHD_SRN' macro is deprecated, use 'ESP_A2D_SBC_CIE_ALLOC_MTHD_SNR'\"") ESP_A2D_SBC_CIE_ALLOC_MTHD_SNR #define ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS (0x1) /*!< SBC allocation method Loudness */ +/* A2DP M24 (MPEG-2, 4 AAC) DRC bit mask in CIE */ +#define ESP_A2D_M24_CIE_DRC_SUPPORT (0x1) /*!< M24 MPEG-D DRC supported */ +#define ESP_A2D_M24_CIE_DRC_NS (0x0) /*!< M24 MPEG-D DRC not supported */ + +/* A2DP M24 object type bit mask in CIE */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_2_AAC_LC (0x40) /*!< M24 MPEG-2 AAC LC */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_LC (0x20) /*!< M24 MPEG-4 AAC LC */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_LTP (0x10) /*!< M24 MPEG-4 AAC LTP */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_SCALABLE (0x08) /*!< M24 MPEG-4 AAC Scalable */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC (0x04) /*!< M24 MPEG-4 HE-AAC */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC_V2 (0x02) /*!< M24 MPEG-4 HE-AAC v2 */ +#define ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_ELD_V2 (0x01) /*!< M24 MPEG-4 AAC-ELD v2 */ + +/* A2DP M24 sampling frequency part 1 bit mask in CIE */ +#define ESP_A2D_M24_CIE_SF1_8K (0x80) /*!< M24 sampling frequency 8 kHz */ +#define ESP_A2D_M24_CIE_SF1_11K (0x40) /*!< M24 sampling frequency 11.025 kHz */ +#define ESP_A2D_M24_CIE_SF1_12K (0x20) /*!< M24 sampling frequency 12 kHz */ +#define ESP_A2D_M24_CIE_SF1_16K (0x10) /*!< M24 sampling frequency 16 kHz */ +#define ESP_A2D_M24_CIE_SF1_22K (0x08) /*!< M24 sampling frequency 22.05 kHz */ +#define ESP_A2D_M24_CIE_SF1_24K (0x04) /*!< M24 sampling frequency 24 kHz */ +#define ESP_A2D_M24_CIE_SF1_32K (0x02) /*!< M24 sampling frequency 32 kHz */ +#define ESP_A2D_M24_CIE_SF1_44K (0x01) /*!< M24 sampling frequency 44.1 kHz */ + +/* A2DP M24 sampling frequency part 2 bit mask in CIE */ +#define ESP_A2D_M24_CIE_SF2_48K (0x08) /*!< M24 sampling frequency 48 kHz */ +#define ESP_A2D_M24_CIE_SF2_64K (0x04) /*!< M24 sampling frequency 64 kHz */ +#define ESP_A2D_M24_CIE_SF2_88K (0x02) /*!< M24 sampling frequency 88.2 kHz */ +#define ESP_A2D_M24_CIE_SF2_96K (0x01) /*!< M24 sampling frequency 96 kHz */ + +/* A2DP M24 channels bit mask in CIE */ +#define ESP_A2D_M24_CIE_CH_1 (0x08) /*!< M24 1 channel */ +#define ESP_A2D_M24_CIE_CH_2 (0x04) /*!< M24 2 channels */ +#define ESP_A2D_M24_CIE_CH_6 (0x02) /*!< M24 6 channels */ +#define ESP_A2D_M24_CIE_CH_8 (0x01) /*!< M24 8 channels */ + +/* A2DP M24 VBR bit mask in CIE */ +#define ESP_A2D_M24_CIE_VBR_SUPPORT (0x1) /*!< M24 VBR supported */ +#define ESP_A2D_M24_CIE_VBR_NS (0x0) /*!< M24 VBR not supported */ + +/* A2DP M24 bit rate bit mask in CIE */ +#define ESP_A2D_M24_CIE_BR1_MSK (0x7F) /*!< M24 bit rate part 1 mask */ +#define ESP_A2D_M24_CIE_BR2_MSK (0xFF) /*!< M24 bit rate part 2 mask */ +#define ESP_A2D_M24_CIE_BR3_MSK (0xFF) /*!< M24 bit rate part 3 mask */ + /** * @brief A2DP SBC media codec capabilities information struct */ @@ -658,6 +702,9 @@ esp_err_t esp_a2d_source_deinit(void); * audio connection mtu (retrieved from ESP_A2D_CONNECTION_STATE_EVT). if the return value is * ESP_OK, then the audio buff is consumed, otherwise, audio buff can be reused by user. * + * @note If use AAC codec, the raw AAC data length shall not be bigger than (mtu - 20). + * 20 is the reserved length for encapsulating LATM. + * * @param[in] conn_hdl: connection handle * * @param[in] audio_buf: encoded audio data diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/bta_av_co.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/bta_av_co.c index fa6d5326dea..6f94a19d997 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/bta_av_co.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/bta_av_co.c @@ -2488,26 +2488,21 @@ BOOLEAN bta_av_co_get_peer_sink_caps(tBTA_AV_HNDL hndl, UINT8 *p_codec_caps, UIN /******************************************************************************* ** - ** Function bta_av_co_get_cur_codec_type + ** Function bta_av_co_get_cur_codec_info ** - ** Description Get current codec type - ** - ** Returns codec type + ** Description Get current codec info ** *******************************************************************************/ -UINT8 bta_av_co_get_cur_codec_type(void) +void bta_av_co_get_cur_codec_info(tBTC_AV_CODEC_INFO *cur_codec_info) { - UINT8 codec_id = BTC_AV_CODEC_NONE; - /* Protect access to bta_av_co_cb.codec_cfg */ osi_mutex_global_lock(); - codec_id = bta_av_co_cb.codec_cfg.id; + cur_codec_info->id = bta_av_co_cb.codec_cfg.id; + memcpy(cur_codec_info->info, bta_av_co_cb.codec_cfg.info, AVDT_CODEC_SIZE); /* Protect access to bta_av_co_cb.codec_cfg */ osi_mutex_global_unlock(); - - return codec_id; } /* the call out functions for audio stream */ diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_latm_raw.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_latm_raw.c index 29f2d06ef04..d1bc8da805b 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_latm_raw.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_latm_raw.c @@ -5,16 +5,19 @@ */ /* - * MPEG-4 LATM AudioMuxElement parsing for A2DP AAC (MPEG-2,4): skip StreamMuxConfig - * when useSameStreamMux is 0, then extract one raw AAC frame (payloadLengthInfo + - * RawDataBlock). Used by the BTC A2DP sink external-codec path. + * MPEG-4 LATM AudioMuxElement helpers for A2DP AAC (MPEG-2,4). + * Sink: parse LATM and extract one raw AAC frame. + * Source: build LATM from a raw AAC frame and codec configuration. */ #include #include +#if (BTC_AV_SRC_INCLUDED == TRUE) +#include "stack/a2d_m24.h" +#endif #include "btc_a2dp_latm_raw.h" -#if (BTC_AV_SINK_INCLUDED == TRUE) && (BTC_AV_CODEC_AAC_INCLUDED == TRUE) +#if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) /* * ISO/IEC 14496-3 (LATM / AudioSpecificConfig / GASpecificConfig) — numeric literals @@ -37,8 +40,12 @@ #define BTC_LATM_MPEG4_OT_ER_TWINVQ (21U) #define BTC_LATM_MPEG4_OT_ER_BSAC (22U) #define BTC_LATM_MPEG4_OT_ER_AAC_LD (23U) +#define BTC_LATM_MPEG4_OT_AAC_ELD (39U) /* MPEG-4 AAC-ELD (A2DP ELD v2) */ #define BTC_LATM_MPEG4_OT_AAC_HE_SBR (5U) /* HE-AAC dual-rate wrapper */ #define BTC_LATM_MPEG4_OT_AAC_HE_PS_WRAPPER (29U) /* HE-AAC v2 style wrapper */ +#define BTC_LATM_ASC_GA_BITS (3U) /* frameLengthFlag + dependsOnCoreCoder + extensionFlag */ +#define BTC_LATM_ASC_ELD_SPECIFIC_BITS (5U) /* minimal ELDSpecificConfig (no ldSbr) */ +#define BTC_LATM_ASC_ELD_EPCONFIG_BITS (2U) /* epConfig for audioObjectType 39 */ #define BTC_LATM_SAMPLING_FREQ_INDEX_ESCAPE (15U) /* explicit 24-bit Hz follows */ @@ -48,9 +55,10 @@ #define BTC_LATM_MPEG4_PS_EXT_SYNCWORD (0x548U) #define BTC_LATM_PAYLOAD_LEN_ESC_BYTE (255U) -#define BTC_LATM_PAYLOAD_LEN_ESC_MAX_LOOPS (32U) +#define BTC_LATM_PAYLOAD_LEN_ESC_MAX_LOOPS (32U) #define BTC_LATM_STREAM_MUX_ALL_FRAMING_AND_SUBFR_BITS (7U) /* 1 + 6 bits */ +#define BTC_LATM_VALUE_BITS_L0 (2U) /* put_value L=0: 2 bits */ #define BTC_LATM_MUX_PROGRAM_IDX_FIRST (0U) #define BTC_LATM_MUX_LAYER_IDX_FIRST (0U) @@ -66,6 +74,29 @@ #define BTC_LATM_FRAME_LENGTH_TYPE_FIXED_IDX_6 (6U) #define BTC_LATM_FRAME_LENGTH_TYPE_FIXED_IDX_7 (7U) +#if (BTC_AV_SRC_INCLUDED == TRUE) +/* LATM header: 5 bytes + max_ASC_len(5 bytes) */ +#define BTC_LATM_AU_PREFIX_MAX_BYTES 10U +/* Dynamic StreamMuxConfig */ +#define BTC_LATM_MUX_AUDIO_VERSION_A2DP 1U +#define BTC_LATM_MUX_AUDIO_VERSION_A_A2DP 0U +#define BTC_TARA_BUFFER_FULLNESS_A2DP 0xFFU +#define BTC_LATM_BUFFER_FULLNESS_A2DP 0xC0U + +typedef struct { + uint8_t *buf; + size_t cap; + unsigned bit_pos; +} btc_a2dp_latm_bw_t; + +typedef struct { + unsigned asc_obj; /* MPEG-4 audioObjectType (core type when HE wrapper is set) */ + unsigned he_wrapper; /* 0, or BTC_LATM_MPEG4_OT_AAC_HE_SBR / _HE_PS_WRAPPER */ +} btc_a2dp_latm_m24_asc_t; +#endif /* #if (BTC_AV_SRC_INCLUDED == TRUE) */ + +#if (BTC_AV_SINK_INCLUDED == TRUE) + typedef struct { const uint8_t *buf; size_t byte_len; @@ -162,6 +193,16 @@ static int btc_a2dp_latm_skip_audio_specific_config(btc_a2dp_latm_br_t *r) return -1; } + if (object_type == BTC_LATM_MPEG4_OT_AAC_ELD) { + if (btc_a2dp_latm_skip_bits(r, BTC_LATM_ASC_ELD_SPECIFIC_BITS) != 0) { + return -1; + } + if (btc_a2dp_latm_skip_bits(r, BTC_LATM_ASC_ELD_EPCONFIG_BITS) != 0) { + return -1; + } + return 0; + } + /* HE-AAC v1/v2 dual-rate wrapper (AAC scalable / PS wrapper object types). */ if (object_type == BTC_LATM_MPEG4_OT_AAC_HE_SBR || object_type == BTC_LATM_MPEG4_OT_AAC_HE_PS_WRAPPER) { unsigned ext_sf = 0; @@ -212,7 +253,7 @@ static int btc_a2dp_latm_skip_audio_specific_config(btc_a2dp_latm_br_t *r) if (btc_a2dp_latm_get_bits(r, 1, &dep) != 0) { return -1; } - if (dep == 0U) { + if (dep) { if (btc_a2dp_latm_skip_bits(r, 14) != 0) { /* coreCoderDelay */ return -1; } @@ -457,6 +498,47 @@ static int btc_a2dp_latm_get_payload_length_type0(btc_a2dp_latm_br_t *r, unsigne return 0; } +int btc_a2dp_latm_check_hdr_len(const uint8_t *in, size_t in_len, size_t *out_bits_len) +{ + if (in == NULL || out_bits_len == NULL || in_len == 0) { + return -1; + } + + btc_a2dp_latm_br_t r = { .buf = in, .byte_len = in_len, .bit_pos = 0 }; + unsigned use_same = 0; + if (btc_a2dp_latm_get_bits(&r, 1, &use_same) != 0) { + return -1; + } + + unsigned frame_length_type = 0; + if (use_same == 0U) { + unsigned audio_mux_version_a = 0; + btc_a2dp_latm_br_t mux = { .buf = in, .byte_len = in_len, .bit_pos = 1U }; + if (btc_a2dp_latm_skip_stream_mux_config(&mux, &audio_mux_version_a, &frame_length_type) != 0) { + return -1; + } + r.bit_pos = mux.bit_pos; + } else { + /* + * useSameStreamMux=1 reuses previous StreamMuxConfig. In A2DP AAC-LATM + * streams this is typically frameLengthType=0. + */ + frame_length_type = BTC_LATM_FRAME_LENGTH_TYPE_FIXED_PAYLOAD; + } + + if (frame_length_type != BTC_LATM_FRAME_LENGTH_TYPE_FIXED_PAYLOAD) { + return -1; + } + + unsigned payload_len_bytes = 0; + if (btc_a2dp_latm_get_payload_length_type0(&r, &payload_len_bytes) != 0) { + return -1; + } + + *out_bits_len = r.bit_pos; + return 0; +} + int btc_a2dp_latm_extract_raw_data_block(const uint8_t *in, size_t in_len, uint8_t *out, size_t out_cap, size_t *out_len) { @@ -527,4 +609,717 @@ int btc_a2dp_latm_extract_raw_data_block(const uint8_t *in, size_t in_len, return 0; } -#endif /* #if (BTC_AV_SINK_INCLUDED == TRUE) && (BTC_AV_CODEC_AAC_INCLUDED == TRUE) */ +#endif /* #if (BTC_AV_SINK_INCLUDED == TRUE) */ + +#if (BTC_AV_SRC_INCLUDED == TRUE) +/* --- LATM mux writer --- */ + +static inline unsigned btc_a2dp_latm_bw_bits_written(const btc_a2dp_latm_bw_t *w) +{ + return w->bit_pos; +} + +static void btc_a2dp_latm_bw_init(btc_a2dp_latm_bw_t *w, uint8_t *buf, size_t cap) +{ + w->buf = buf; + w->cap = cap; + w->bit_pos = 0; + if (buf != NULL && cap > 0U) { + memset(buf, 0, cap); + } +} + +static int btc_a2dp_latm_put1(btc_a2dp_latm_bw_t *w, int bit) +{ + size_t need = (size_t)((w->bit_pos + 8U) / 8U); + + if (w->buf == NULL || need > w->cap) { + return -1; + } + unsigned byte = w->bit_pos / 8U; + unsigned bit_in_byte = 7U - (w->bit_pos % 8U); + if (bit != 0) { + w->buf[byte] |= (uint8_t)(1U << bit_in_byte); + } + w->bit_pos++; + return 0; +} + +static int btc_a2dp_latm_put_bits(btc_a2dp_latm_bw_t *w, unsigned n, unsigned v) +{ + if (n == 0U || n > 32U) { + return -1; + } + for (unsigned i = 0; i < n; i++) { + unsigned sh = n - 1U - i; + if (btc_a2dp_latm_put1(w, (int)((v >> sh) & 1U)) != 0) { + return -1; + } + } + return 0; +} + +/* LATM value: 2-bit length L, then (L+1)*8 bits payload (ISO / common mux). */ +static int btc_a2dp_latm_put_value(btc_a2dp_latm_bw_t *w, unsigned v) +{ + unsigned l = 0; + + for (l = 0; l < 4U; l++) { + unsigned nbytes = l + 1U; + unsigned maxv; + + if (nbytes >= 4U) { + maxv = 0xffffffffU; + } else { + maxv = (1U << (nbytes * 8U)) - 1U; + } + if (v <= maxv) { + break; + } + } + if (l >= 4U) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 2, l) != 0) { + return -1; + } + unsigned nbytes = l + 1U; + for (unsigned i = 0; i < nbytes; i++) { + unsigned sh = (nbytes - 1U - i) * 8U; + unsigned b = (v >> sh) & 0xFFU; + if (btc_a2dp_latm_put_bits(w, 8, b) != 0) { + return -1; + } + } + return 0; +} + +static inline int btc_a2dp_latm_popcount_u(unsigned v) +{ + int n = 0; + while (v != 0U) { + n++; + v &= (v - 1U); + } + return n; +} + +static int btc_a2dp_latm_m24_cie0_to_asc_profile(uint8_t cie0, btc_a2dp_latm_m24_asc_t *prof) +{ + unsigned m_bits = (unsigned)((cie0 >> 1) & 0x7FU); + + if (m_bits == 0U || btc_a2dp_latm_popcount_u(m_bits) != 1) { + return -1; + } + + prof->he_wrapper = 0U; + prof->asc_obj = 0U; + + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_2_AAC_LC) { + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_LC; + return 0; + } + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_4_AAC_LC) { + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_LC; + return 0; + } + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_4_AAC_LTP) { + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_LTP; + return 0; + } + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_4_AAC_SCALABLE) { + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_SCALABLE; + return 0; + } + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_4_HE_AAC) { + prof->he_wrapper = BTC_LATM_MPEG4_OT_AAC_HE_SBR; + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_LC; + return 0; + } + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_4_HE_AAC_V2) { + prof->he_wrapper = BTC_LATM_MPEG4_OT_AAC_HE_PS_WRAPPER; + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_LC; + return 0; + } + if (cie0 & (unsigned)A2D_M24_IE_OBJ_TYPE_4_AAC_ELD_V2) { + prof->asc_obj = BTC_LATM_MPEG4_OT_AAC_ELD; + return 0; + } + return -1; +} + +static int btc_a2dp_latm_m24_sample_rate_hz(const uint8_t *cie, uint32_t *hz_out) +{ + const uint8_t sf1 = cie[1]; + const uint8_t sf2_hi = (uint8_t)((cie[2] & A2D_M24_IE_SAMP_FREQ2_MSK) >> 4); + const unsigned c1 = (unsigned)btc_a2dp_latm_popcount_u((unsigned)sf1); + const unsigned c2 = (unsigned)btc_a2dp_latm_popcount_u((unsigned)sf2_hi); + + if (c1 + c2 != 1U) { + return -1; + } + if (c2 != 0U) { + if (sf2_hi == (unsigned)(A2D_M24_IE_SAMP_FREQ2_48 >> 4)) { + *hz_out = 48000U; + } else if (sf2_hi == (unsigned)(A2D_M24_IE_SAMP_FREQ2_64 >> 4)) { + *hz_out = 64000U; + } else if (sf2_hi == (unsigned)(A2D_M24_IE_SAMP_FREQ2_88 >> 4)) { + *hz_out = 88200U; + } else if (sf2_hi == (unsigned)(A2D_M24_IE_SAMP_FREQ2_96 >> 4)) { + *hz_out = 96000U; + } else { + return -1; + } + return 0; + } + if (sf1 & A2D_M24_IE_SAMP_FREQ1_8) { + *hz_out = 8000U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_11) { + *hz_out = 11025U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_12) { + *hz_out = 12000U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_16) { + *hz_out = 16000U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_22) { + *hz_out = 22050U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_24) { + *hz_out = 24000U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_32) { + *hz_out = 32000U; + } else if (sf1 & A2D_M24_IE_SAMP_FREQ1_44) { + *hz_out = 44100U; + } else { + return -1; + } + return 0; +} + +static int btc_a2dp_latm_sf_index_to_hz(unsigned sf_index, uint32_t *hz_out) +{ + switch (sf_index) { + case 0U: + *hz_out = 96000U; + return 0; + case 1U: + *hz_out = 88200U; + return 0; + case 2U: + *hz_out = 64000U; + return 0; + case 3U: + *hz_out = 48000U; + return 0; + case 4U: + *hz_out = 44100U; + return 0; + case 5U: + *hz_out = 32000U; + return 0; + case 6U: + *hz_out = 24000U; + return 0; + case 7U: + *hz_out = 22050U; + return 0; + case 8U: + *hz_out = 16000U; + return 0; + case 9U: + *hz_out = 12000U; + return 0; + case 10U: + *hz_out = 11025U; + return 0; + case 11U: + *hz_out = 8000U; + return 0; + default: + return -1; + } +} + +static int btc_a2dp_latm_hz_to_sf_index(uint32_t hz, unsigned *sf_index_out) +{ + switch (hz) { + case 96000U: + *sf_index_out = 0U; + return 0; + case 88200U: + *sf_index_out = 1U; + return 0; + case 64000U: + *sf_index_out = 2U; + return 0; + case 48000U: + *sf_index_out = 3U; + return 0; + case 44100U: + *sf_index_out = 4U; + return 0; + case 32000U: + *sf_index_out = 5U; + return 0; + case 24000U: + *sf_index_out = 6U; + return 0; + case 22050U: + *sf_index_out = 7U; + return 0; + case 16000U: + *sf_index_out = 8U; + return 0; + case 12000U: + *sf_index_out = 9U; + return 0; + case 11025U: + *sf_index_out = 10U; + return 0; + case 8000U: + *sf_index_out = 11U; + return 0; + default: + return -1; + } +} + +static int btc_a2dp_latm_m24_channel_config(const uint8_t *cie, unsigned *ch_cfg_out) +{ + const unsigned ch = (unsigned)(cie[2] & A2D_M24_IE_CH_MSK); + + if (ch == 0U || btc_a2dp_latm_popcount_u(ch) != 1) { + return -1; + } + if (ch == (unsigned)A2D_M24_IE_CH_1) { + *ch_cfg_out = 1U; + return 0; + } + if (ch == (unsigned)A2D_M24_IE_CH_2) { + *ch_cfg_out = 2U; + return 0; + } + if (ch == (unsigned)A2D_M24_IE_CH_6) { + *ch_cfg_out = 6U; + return 0; + } + if (ch == (unsigned)A2D_M24_IE_CH_8) { + *ch_cfg_out = 7U; + return 0; + } + return -1; +} + +static unsigned btc_a2dp_latm_asc_object_type_bits(unsigned object_type) +{ + return (object_type <= 30U) ? 5U : 11U; +} + +static int btc_a2dp_latm_put_asc_object_type(btc_a2dp_latm_bw_t *w, unsigned object_type) +{ + if (object_type <= 30U) { + return btc_a2dp_latm_put_bits(w, 5, object_type); + } + if (object_type > 63U) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 5, BTC_LATM_MPEG4_OT_EXTENDED_ESCAPE) != 0) { + return -1; + } + return btc_a2dp_latm_put_bits(w, 6, object_type - BTC_LATM_MPEG4_OT_EXTENDED_BASE); +} + +/* A2DP M24 CIE sample rate is the HE-AAC output rate; ASC core rate is half. */ +static int btc_a2dp_latm_sf_index_core_from_output(unsigned output_sf_index, unsigned *core_sf_out) +{ + uint32_t hz = 0; + + if (btc_a2dp_latm_sf_index_to_hz(output_sf_index, &hz) != 0 || hz < 2U) { + return -1; + } + return btc_a2dp_latm_hz_to_sf_index(hz / 2U, core_sf_out); +} + +static int btc_a2dp_latm_put_ga_specific_config(btc_a2dp_latm_bw_t *w, unsigned depends_on_core_coder) +{ + if (btc_a2dp_latm_put_bits(w, 1, 0U) != 0) { /* frameLengthFlag */ + return -1; + } + if (btc_a2dp_latm_put_bits(w, 1, depends_on_core_coder) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 1, 0U) != 0) { /* extensionFlag */ + return -1; + } + return 0; +} + +static unsigned btc_a2dp_latm_bits_pad_to_byte(unsigned bits) +{ + unsigned rem = bits % 8U; + + return (rem == 0U) ? 0U : (8U - rem); +} + +static unsigned btc_a2dp_latm_asc_bits_for_profile(const btc_a2dp_latm_m24_asc_t *prof) +{ + if (prof->he_wrapper != 0U) { + return btc_a2dp_latm_asc_object_type_bits(prof->he_wrapper) + 4U + 4U + 4U + + btc_a2dp_latm_asc_object_type_bits(prof->asc_obj) + BTC_LATM_ASC_GA_BITS; + } + if (prof->asc_obj == BTC_LATM_MPEG4_OT_AAC_ELD) { + return btc_a2dp_latm_asc_object_type_bits(BTC_LATM_MPEG4_OT_AAC_ELD) + 4U + 4U + + BTC_LATM_ASC_ELD_SPECIFIC_BITS + BTC_LATM_ASC_ELD_EPCONFIG_BITS; + } + return btc_a2dp_latm_asc_object_type_bits(prof->asc_obj) + 4U + 4U + BTC_LATM_ASC_GA_BITS; +} + +/* AudioSpecificConfig */ +static int btc_a2dp_latm_write_audio_specific_config(btc_a2dp_latm_bw_t *w, const btc_a2dp_latm_m24_asc_t *prof, + unsigned sf_index, unsigned ch_cfg) +{ + unsigned core_sf = 0; + + if (sf_index == BTC_LATM_SAMPLING_FREQ_INDEX_ESCAPE) { + return -1; + } + + if (prof->he_wrapper != 0U) { + if (btc_a2dp_latm_sf_index_core_from_output(sf_index, &core_sf) != 0) { + return -1; + } + if (btc_a2dp_latm_put_asc_object_type(w, prof->he_wrapper) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, core_sf) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, ch_cfg) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, sf_index) != 0) { + return -1; + } + if (btc_a2dp_latm_put_asc_object_type(w, prof->asc_obj) != 0) { + return -1; + } + return btc_a2dp_latm_put_ga_specific_config(w, 0U); + } + + if (prof->asc_obj == BTC_LATM_MPEG4_OT_AAC_ELD) { + if (btc_a2dp_latm_put_asc_object_type(w, BTC_LATM_MPEG4_OT_AAC_ELD) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, sf_index) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, ch_cfg) != 0) { + return -1; + } + /* ELDSpecificConfig: frameLengthFlag=0, no resilience, ldSbrPresent=0 */ + if (btc_a2dp_latm_put_bits(w, BTC_LATM_ASC_ELD_SPECIFIC_BITS, 0U) != 0) { + return -1; + } + return btc_a2dp_latm_put_bits(w, BTC_LATM_ASC_ELD_EPCONFIG_BITS, 0U); + } + + if (btc_a2dp_latm_put_asc_object_type(w, prof->asc_obj) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, sf_index) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 4, ch_cfg) != 0) { + return -1; + } + return btc_a2dp_latm_put_ga_specific_config(w, 0U); +} + +/* + * Write StreamMuxConfig (after useSameStreamMux=0). @p cie points to 6 A2DP M24 CIE octets. + * @p audio_mux_version 0: inline AudioSpecificConfig bits. + * 1 with @p audio_mux_version_a 0: LATM value taraBufferFullness, then value-length ASC + * (matches btc_a2dp_latm_skip_stream_mux_config when version != 0). + */ +static int btc_a2dp_latm_write_stream_mux_config(btc_a2dp_latm_bw_t *w, const uint8_t *cie, + uint8_t latm_buffer_fullness, unsigned audio_mux_version, + unsigned audio_mux_version_a, unsigned tara_buffer_fullness) +{ + btc_a2dp_latm_m24_asc_t prof; + uint32_t hz = 0; + unsigned sf_index = 0; + unsigned ch_cfg = 0; + unsigned asc_bits = 0; + + if (btc_a2dp_latm_m24_cie0_to_asc_profile(cie[0], &prof) != 0) { + return -1; + } + asc_bits = btc_a2dp_latm_asc_bits_for_profile(&prof); + if (btc_a2dp_latm_m24_sample_rate_hz(cie, &hz) != 0) { + return -1; + } + if (btc_a2dp_latm_hz_to_sf_index(hz, &sf_index) != 0) { + return -1; + } + if (btc_a2dp_latm_m24_channel_config(cie, &ch_cfg) != 0) { + return -1; + } + + if (audio_mux_version > 1U) { + return -1; + } + if (audio_mux_version != 0U && audio_mux_version_a != 0U) { + /* audioMuxVersionA == 1 path not supported in this mux writer. */ + return -1; + } + + if (btc_a2dp_latm_put_bits(w, 1, audio_mux_version) != 0) { + return -1; + } + if (audio_mux_version != 0U) { + if (btc_a2dp_latm_put_bits(w, 1, audio_mux_version_a) != 0) { + return -1; + } + if (btc_a2dp_latm_put_value(w, tara_buffer_fullness) != 0) { + return -1; + } + } + + /* allStreamsSameTimeFraming=1, numSubFrames=0 */ + if (btc_a2dp_latm_put_bits(w, 1, 1U) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 6, 0U) != 0) { + return -1; + } + + if (btc_a2dp_latm_put_bits(w, 4, BTC_LATM_MUX_PROGRAM_IDX_FIRST) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 3, BTC_LATM_MUX_LAYER_IDX_FIRST) != 0) { + return -1; + } + + if (audio_mux_version == 0U) { + if (btc_a2dp_latm_write_audio_specific_config(w, &prof, sf_index, ch_cfg) != 0) { + return -1; + } + } else { + /* ASC length (LATM value L=0) + payload byte-aligned. */ + unsigned asc_pad = btc_a2dp_latm_bits_pad_to_byte(asc_bits + BTC_LATM_VALUE_BITS_L0); + unsigned asc_bits_ba = asc_bits + asc_pad; + + if (btc_a2dp_latm_put_value(w, asc_bits_ba) != 0) { + return -1; + } + if (btc_a2dp_latm_write_audio_specific_config(w, &prof, sf_index, ch_cfg) != 0) { + return -1; + } + if (asc_pad != 0U && btc_a2dp_latm_put_bits(w, asc_pad, 0U) != 0) { + return -1; + } + } + + if (btc_a2dp_latm_put_bits(w, 3, BTC_LATM_FRAME_LENGTH_TYPE_FIXED_PAYLOAD) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 8, (unsigned)latm_buffer_fullness) != 0) { + return -1; + } + if (btc_a2dp_latm_put_bits(w, 1, 0U) != 0) { /* otherDataPresent */ + return -1; + } + if (btc_a2dp_latm_put_bits(w, 1, 0U) != 0) { /* crcCheckPresent */ + return -1; + } + return 0; +} + +static int btc_a2dp_latm_write_payload_length_type0(btc_a2dp_latm_bw_t *w, uint16_t payload_len) +{ + unsigned rem = payload_len; + unsigned loops = 0; + + while (rem >= BTC_LATM_PAYLOAD_LEN_ESC_BYTE) { + if (btc_a2dp_latm_put_bits(w, 8, BTC_LATM_PAYLOAD_LEN_ESC_BYTE) != 0) { + return -1; + } + rem -= BTC_LATM_PAYLOAD_LEN_ESC_BYTE; + loops++; + if (loops > BTC_LATM_PAYLOAD_LEN_ESC_MAX_LOOPS) { + return -1; + } + } + if (btc_a2dp_latm_put_bits(w, 8, rem) != 0) { + return -1; + } + return 0; +} + +static int btc_a2dp_latm_write_raw_payload(btc_a2dp_latm_bw_t *w, const uint8_t *raw_frame, uint16_t raw_len) +{ + for (unsigned i = 0; i < raw_len; i++) { + if (btc_a2dp_latm_put_bits(w, 8, (unsigned)raw_frame[i]) != 0) { + return -1; + } + } + return 0; +} + +/* useSameStreamMux=0 + StreamMuxConfig bit length from @p codec_cfg (dry-run, no AU payload). */ +uint64_t btc_a2dp_latm_stream_mux_config_bits(uint8_t *codec_cfg) +{ + uint8_t scratch[BTC_LATM_AU_PREFIX_MAX_BYTES]; + btc_a2dp_latm_bw_t w; + const uint8_t *cie; + + if (codec_cfg == NULL) { + return 0; + } + cie = (const uint8_t *)(codec_cfg + A2D_M24_CIE_OFF); + + btc_a2dp_latm_bw_init(&w, scratch, sizeof(scratch)); + if (btc_a2dp_latm_put_bits(&w, 1, 0U) != 0) { + return 0; + } + if (btc_a2dp_latm_write_stream_mux_config(&w, cie, BTC_LATM_BUFFER_FULLNESS_A2DP, + BTC_LATM_MUX_AUDIO_VERSION_A2DP, + BTC_LATM_MUX_AUDIO_VERSION_A_A2DP, + BTC_TARA_BUFFER_FULLNESS_A2DP) != 0) { + return 0; + } + return (uint64_t)btc_a2dp_latm_bw_bits_written(&w); +} + +/* payloadLengthInfo (frameLengthType==0) bit length for @p payload_len bytes. */ +uint64_t btc_a2dp_latm_payload_length_bits(uint16_t payload_len) +{ + unsigned bits = 0; + unsigned rem = payload_len; + + while (rem >= BTC_LATM_PAYLOAD_LEN_ESC_BYTE) { + bits += 8U; + rem -= BTC_LATM_PAYLOAD_LEN_ESC_BYTE; + } + return bits + 8U; +} + +uint8_t btc_a2dp_latm_au_prefix_bytes(uint8_t *codec_cfg, uint16_t raw_len) +{ + uint64_t bits; + + if (codec_cfg == NULL) { + return 0; + } + /* stream_mux_config_bits already includes useSameStreamMux (1 bit). */ + bits = btc_a2dp_latm_stream_mux_config_bits(codec_cfg); + if (bits == 0U) { + return 0; + } + bits += btc_a2dp_latm_payload_length_bits(raw_len); + if (bits % 8U) { + return 0; + } + return (uint8_t)(bits / 8U); +} + +/* Max LATM AU byte length: StreamMuxConfig + payloadLengthInfo + raw AAC (bit-aligned). */ +uint16_t btc_a2dp_latm_frame_byte_len(uint8_t *codec_cfg, uint16_t raw_len) +{ + unsigned frame_bits; + + frame_bits = (unsigned)btc_a2dp_latm_stream_mux_config_bits(codec_cfg); + if (frame_bits == 0U) { + return 0; + } + frame_bits += (unsigned)btc_a2dp_latm_payload_length_bits(raw_len) + ((unsigned)raw_len * 8U); + return (uint16_t)((frame_bits + 7U) / 8U); +} + +/* + * Build one complete LATM AudioMuxElement (StreamMuxConfig + payloadLengthInfo + raw AAC) + * into @p out_buf. Returns total byte length, or 0 on error. + */ +uint16_t btc_a2dp_latm_build_frame(uint8_t *codec_cfg, const uint8_t *raw_frame, uint16_t raw_len, + uint8_t *out_buf, uint16_t out_cap) +{ + btc_a2dp_latm_bw_t w; + const uint8_t *cie; + unsigned frame_bits; + + if (codec_cfg == NULL || out_buf == NULL || raw_frame == NULL || raw_len == 0U) { + return 0; + } + if (out_buf == raw_frame) { + return 0; + } + cie = (const uint8_t *)(codec_cfg + A2D_M24_CIE_OFF); + + btc_a2dp_latm_bw_init(&w, out_buf, out_cap); + if (btc_a2dp_latm_put_bits(&w, 1, 0U) != 0) { + return 0; + } + if (btc_a2dp_latm_write_stream_mux_config(&w, cie, BTC_LATM_BUFFER_FULLNESS_A2DP, + BTC_LATM_MUX_AUDIO_VERSION_A2DP, + BTC_LATM_MUX_AUDIO_VERSION_A_A2DP, + BTC_TARA_BUFFER_FULLNESS_A2DP) != 0) { + return 0; + } + if (btc_a2dp_latm_write_payload_length_type0(&w, raw_len) != 0) { + return 0; + } + if (btc_a2dp_latm_write_raw_payload(&w, raw_frame, raw_len) != 0) { + return 0; + } + + frame_bits = btc_a2dp_latm_bw_bits_written(&w); + if (frame_bits == 0U) { + return 0; + } + { + uint16_t frame_len = (uint16_t)((frame_bits + 7U) / 8U); + if (frame_len > out_cap) { + return 0; + } + return frame_len; + } +} + +/* + * Build one LATM header (StreamMuxConfig + payloadLengthInfo) + * into @p out_buf. Returns total byte length, or 0 on error. + */ +bool btc_a2dp_latm_build_hdr(uint8_t *codec_cfg, uint16_t raw_len, + uint8_t *out_buf, uint16_t out_cap) +{ + btc_a2dp_latm_bw_t w; + const uint8_t *cie; + unsigned frame_bits; + + if (codec_cfg == NULL || out_buf == NULL || raw_len == 0U) { + return false; + } + cie = (const uint8_t *)(codec_cfg + A2D_M24_CIE_OFF); + + btc_a2dp_latm_bw_init(&w, out_buf, out_cap); + if (btc_a2dp_latm_put_bits(&w, 1, 0U) != 0) { + return false; + } + if (btc_a2dp_latm_write_stream_mux_config(&w, cie, BTC_LATM_BUFFER_FULLNESS_A2DP, + BTC_LATM_MUX_AUDIO_VERSION_A2DP, + BTC_LATM_MUX_AUDIO_VERSION_A_A2DP, + BTC_TARA_BUFFER_FULLNESS_A2DP) != 0) { + return false; + } + if (btc_a2dp_latm_write_payload_length_type0(&w, raw_len) != 0) { + return false; + } + + frame_bits = btc_a2dp_latm_bw_bits_written(&w); + if (frame_bits == 0U || frame_bits % 8U) { + return false; + } + return true; +} + +#endif /* #if (BTC_AV_SRC_INCLUDED == TRUE) */ + +#endif /* #if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) */ diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink_ext_coedc.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink_ext_coedc.c index c64d8853137..cb958679250 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink_ext_coedc.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_sink_ext_coedc.c @@ -30,10 +30,6 @@ #if (BTC_AV_SINK_INCLUDED == TRUE) && (BTC_AV_EXT_CODEC == TRUE) -#ifndef BTC_A2DP_LATM_RAW_SCRATCH_BYTES -#define BTC_A2DP_LATM_RAW_SCRATCH_BYTES 2048 -#endif - #define MAX_OUTPUT_A2DP_SNK_FRAME_QUEUE_SZ (25) #define BTC_A2DP_SNK_DATA_QUEUE_IDX (1) @@ -241,7 +237,9 @@ static void btc_a2dp_sink_handle_inc_media(BT_HDR *p_msg) UINT32 frame_len = p_msg->len; UINT16 num_frames = 0; UINT32 timestamp; - UINT8 codec_id = bta_av_co_get_cur_codec_type(); + tBTC_AV_CODEC_INFO cur_codec_info; + + bta_av_co_get_cur_codec_info(&cur_codec_info); if (a2dp_sink_local_param.btc_aa_snk_cb.rx_flush) { osi_free(p_msg); @@ -266,7 +264,9 @@ static void btc_a2dp_sink_handle_inc_media(BT_HDR *p_msg) a2dp_sink_local_param.media_pkt_seq_num.expected_seq_num = p_msg->layer_specific + 1; a2dp_sink_local_param.media_pkt_seq_num.seq_num_recount = false; - switch (codec_id) { + memcpy(×tamp, (UINT8 *)(p_msg + 1), sizeof(UINT32)); + + switch (cur_codec_info.id) { case BTC_AV_CODEC_SBC: if (p_msg->len < 1) { APPL_TRACE_WARNING("%s: SBC packet too short (%d)", __func__, p_msg->len); @@ -280,33 +280,53 @@ static void btc_a2dp_sink_handle_inc_media(BT_HDR *p_msg) #if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) case BTC_AV_CODEC_M24: { - uint8_t scratch[BTC_A2DP_LATM_RAW_SCRATCH_BYTES]; - size_t raw_len = 0; + unsigned latm_hdr_bit_len = 0; num_frames = 1; - frame_ptr = payload; - frame_len = p_msg->len; + if (p_msg->len > 0 && - btc_a2dp_latm_extract_raw_data_block(payload, (size_t)p_msg->len, scratch, - sizeof(scratch), &raw_len) == 0 && - raw_len > 0 && raw_len <= (size_t)p_msg->len && raw_len <= 0xffffU) { - memmove(payload, scratch, raw_len); - frame_ptr = payload; - frame_len = (UINT32)raw_len; + btc_a2dp_latm_check_hdr_len(payload, (size_t)p_msg->len, &latm_hdr_bit_len) == 0) { + if (latm_hdr_bit_len != 0 && latm_hdr_bit_len % 8 == 0) { + frame_ptr = payload + latm_hdr_bit_len / 8; + frame_len = p_msg->len - latm_hdr_bit_len / 8; + } else { + size_t raw_len = 0; + BT_HDR *p_raw_buf = (BT_HDR *)osi_malloc(sizeof(BT_HDR) + p_msg->offset + p_msg->len); + assert(p_raw_buf != NULL); + uint8_t *p_raw_data = (uint8_t *)(p_raw_buf + 1) + p_msg->offset; + if (p_msg->len > 0 && + btc_a2dp_latm_extract_raw_data_block(payload, (size_t)p_msg->len, p_raw_data, + (size_t)p_msg->len, &raw_len) == 0 && + raw_len > 0 && raw_len <= (size_t)p_msg->len && raw_len <= 0xffffU) { + frame_ptr = p_raw_data; + frame_len = (UINT32)raw_len; + osi_free(p_msg); + p_msg = p_raw_buf; + } else { + APPL_TRACE_WARNING("M24 LATM extract failed, drop len=%u", (unsigned)p_msg->len); + osi_free(p_msg); + osi_free(p_raw_buf); + return; + } + } + } else { + APPL_TRACE_WARNING("M24 LATM invalid data"); + osi_free(p_msg); + return; } + break; } #endif default: - APPL_TRACE_WARNING("%s: unsupported codec type 0x%02x, drop", __func__, codec_id); + APPL_TRACE_WARNING("%s: unsupported codec type 0x%02x, drop", __func__, cur_codec_info.id); osi_free(p_msg); return; } - APPL_TRACE_DEBUG("codec 0x%02x, frames %d, frame_len %d", codec_id, num_frames, (int)frame_len); + APPL_TRACE_DEBUG("codec 0x%02x, frames %d, frame_len %d", cur_codec_info.id, num_frames, (int)frame_len); - memcpy(×tamp, (UINT8 *)(p_msg + 1), sizeof(UINT32)); UINT16 conn_hdl = btc_a2d_conn_handle_get(); btc_a2d_audio_data_cb_to_app(conn_hdl, (uint8_t *)p_msg, frame_ptr, frame_len, num_frames, timestamp); /* dont free p_msg here */ diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c index d7ba6f5fcf5..cd808898553 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_av.c @@ -24,6 +24,7 @@ #include "btc/btc_manage.h" #include "btc_av.h" #include "btc_av_co.h" +#include "btc_a2dp_latm_raw.h" #include "btc_avrc.h" #include "btc/btc_util.h" #include "btc/btc_profile_queue.h" @@ -2204,15 +2205,32 @@ bt_status_t btc_a2d_src_audio_data_send(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_aud } BT_HDR *p_buf = (BT_HDR *)audio_buf; uint8_t buf_offset = BTC_AUDIO_BUFF_OFFSET; - UINT8 codec_id = bta_av_co_get_cur_codec_type(); + uint16_t data_len = 0; + tBTC_AV_CODEC_INFO cur_codec_info; - switch (codec_id) { + bta_av_co_get_cur_codec_info(&cur_codec_info); + + switch (cur_codec_info.id) { case BTC_AV_CODEC_SBC: + buf_offset += BTA_AV_SBC_HDR_SIZE; + data_len = audio_buf->data_len; break; #if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) - case BTC_AV_CODEC_M24: - buf_offset -= BTA_AV_SBC_HDR_SIZE; + case BTC_AV_CODEC_M24: { + uint8_t *p_latm_buf = (uint8_t *)(p_buf + 1) + buf_offset; + uint16_t latm_hdr_cap = (uint16_t)(audio_buf->data - p_latm_buf); + + data_len = audio_buf->data_len; + + if (latm_hdr_cap == 0U || data_len + latm_hdr_cap > btc_av_cb.mtu) { + return BT_STATUS_FAIL; + } + if (!btc_a2dp_latm_build_hdr(cur_codec_info.info, data_len, p_latm_buf, latm_hdr_cap)) { + return BT_STATUS_FAIL; + } + data_len += latm_hdr_cap; break; + } #endif default: @@ -2222,9 +2240,8 @@ bt_status_t btc_a2d_src_audio_data_send(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_aud assert(audio_buf->data - (UINT8 *)(p_buf + 1) >= buf_offset); /* since p_buf and audio_buf point to the same memory, backup those value before modify p_buf */ uint16_t number_frame = audio_buf->number_frame; - uint16_t data_len = audio_buf->data_len; uint32_t timestamp = audio_buf->timestamp; - p_buf->offset = audio_buf->data - (UINT8 *)(p_buf + 1); + p_buf->offset = buf_offset; p_buf->layer_specific = number_frame; p_buf->len = data_len; *((UINT32 *) (p_buf + 1)) = timestamp; @@ -2272,23 +2289,31 @@ void btc_av_audio_buff_alloc(uint16_t size, uint8_t **pp_buff, uint8_t **pp_data *pp_data = NULL; #if (BTC_AV_EXT_CODEC == TRUE) - UINT8 codec_id = bta_av_co_get_cur_codec_type(); + tBTC_AV_CODEC_INFO cur_codec_info; - switch (codec_id) { + bta_av_co_get_cur_codec_info(&cur_codec_info); + + switch (cur_codec_info.id) { case BTC_AV_CODEC_SBC: + buf_offset += BTA_AV_SBC_HDR_SIZE; break; -#if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) - case BTC_AV_CODEC_M24: - buf_offset -= BTA_AV_SBC_HDR_SIZE; +#if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) && (BTC_AV_SRC_INCLUDED == TRUE) + case BTC_AV_CODEC_M24: { + uint8_t latm_hdr = btc_a2dp_latm_au_prefix_bytes(cur_codec_info.info, size); + + if (latm_hdr == 0U) { + return; + } + buf_offset += latm_hdr; break; + } #endif default: return; } #endif - /* todo */ - BT_HDR *p_buf= (BT_HDR *)osi_calloc(sizeof(BT_HDR) + buf_offset + size); + BT_HDR *p_buf = (BT_HDR *)osi_calloc(sizeof(BT_HDR) + buf_offset + size); if (p_buf != NULL) { *pp_buff = (uint8_t *)p_buf; *pp_data = (uint8_t *)(p_buf + 1) + buf_offset; diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_a2dp_latm_raw.h b/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_a2dp_latm_raw.h index 5b597fc37e5..147d9369ec4 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_a2dp_latm_raw.h +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_a2dp_latm_raw.h @@ -13,7 +13,21 @@ extern "C" { #endif -#if (BTC_AV_SINK_INCLUDED == TRUE) && (BTC_AV_CODEC_AAC_INCLUDED == TRUE) +#if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) + +#if (BTC_AV_SINK_INCLUDED == TRUE) + +/** + * @brief Parse a LATM AudioMuxElement header and validate the AAC payload size. + * + * @param in LATM input buffer (full AudioMuxElement or prefix) + * @param in_len Length of @p in in bytes + * @param out_bits_len On success, number of header bits consumed before AAC payload + * + * @return 0 on success, -1 on parse error, unsupported mux, insufficient @p in_len, + * or if decoded payload length exceeds @p out_cap + */ +int btc_a2dp_latm_check_hdr_len(const uint8_t *in, size_t in_len, size_t *out_bits_len); /** * @brief Extract one AAC RawDataBlock from one MPEG-4 LATM AudioMuxElement. @@ -26,8 +40,94 @@ extern "C" { */ int btc_a2dp_latm_extract_raw_data_block(const uint8_t *in, size_t in_len, uint8_t *out, size_t out_cap, size_t *out_len); +#endif /* #if (BTC_AV_SINK_INCLUDED == TRUE) */ -#endif +#if (BTC_AV_SRC_INCLUDED == TRUE) +/** + * @brief Compute the byte length of one LATM AudioMuxElement for a raw AAC frame. + * + * Sums useSameStreamMux + StreamMuxConfig (from @p codec_cfg), payloadLengthInfo + * (frameLengthType 0), and @p raw_len bytes of AAC payload, rounded up to whole bytes. + * + * @param codec_cfg A2DP codec configuration (M24 CIE at A2D_M24_CIE_OFF) + * @param raw_len Raw AAC frame length in bytes + * + * @return Required output size in bytes, or 0 if @p codec_cfg is invalid or unsupported + */ +uint16_t btc_a2dp_latm_frame_byte_len(uint8_t *codec_cfg, uint16_t raw_len); + +/** + * @brief Build one MPEG-4 LATM AudioMuxElement from a raw AAC frame. + * + * Writes useSameStreamMux=0, StreamMuxConfig derived from @p codec_cfg, + * payloadLengthInfo, and @p raw_frame into @p out_buf (bit-aligned). + * Supported subset: single program/layer, frameLengthType 0 (typical A2DP source). + * M24 object types: MPEG-2/4 AAC LC, AAC LTP, AAC scalable, HE-AAC, HE-AAC v2, AAC-ELD v2. + * + * @param codec_cfg A2DP codec configuration (M24 CIE at A2D_M24_CIE_OFF) + * @param raw_frame Raw AAC access unit (no ADTS) + * @param raw_len Length of @p raw_frame in bytes + * @param out_buf Output buffer (must not overlap @p raw_frame) + * @param out_cap Capacity of @p out_buf in bytes + * + * @return Total bytes written, or 0 on error or if @p out_cap is too small + */ +uint16_t btc_a2dp_latm_build_frame(uint8_t *codec_cfg, const uint8_t *raw_frame, uint16_t raw_len, + uint8_t *out_buf, uint16_t out_cap); + +/** + * @brief Write the LATM header + * + * The header bit length must be a multiple of 8 so the AAC payload stays byte-aligned. + * + * @param codec_cfg A2DP codec configuration (M24 CIE at A2D_M24_CIE_OFF) + * @param raw_len Raw AAC access unit length in bytes (also encoded in payloadLengthInfo) + * @param out_buf Output buffer start; must not overlap the raw AAC region that follows + * @param out_cap Bytes available from @p out_buf up to (but not including) the raw AAC data + * + * @return true on success, false if @p codec_cfg or @p out_buf is NULL, @p raw_len is 0, + * @p codec_cfg is unsupported, @p out_cap is too small, or the header is not byte-aligned + */ +bool btc_a2dp_latm_build_hdr(uint8_t *codec_cfg, uint16_t raw_len, + uint8_t *out_buf, uint16_t out_cap); + +/** + * @brief Bit length of useSameStreamMux=0 plus StreamMuxConfig for @p codec_cfg. + * + * Dry-run mux write into a scratch buffer; does not include payloadLengthInfo or AAC data. + * + * @param codec_cfg A2DP codec configuration (M24 CIE at A2D_M24_CIE_OFF) + * + * @return Number of bits, or 0 if @p codec_cfg is NULL or unsupported + */ +uint64_t btc_a2dp_latm_stream_mux_config_bits(uint8_t *codec_cfg); + +/** + * @brief Byte length of the LATM header before raw AAC (in-place build with btc_a2dp_latm_build_hdr). + * + * Covers useSameStreamMux=0, StreamMuxConfig (from @p codec_cfg), and payloadLengthInfo for + * @p raw_len. The result is byte-aligned so raw AAC can follow immediately after the prefix. + * Typical values: 10 bytes (AAC LC / LTP / Scalable) or 11 bytes (HE-AAC, HE-AAC v2, AAC-ELD v2) + * when @p raw_len is under 255 bytes. + * + * @param codec_cfg A2DP codec configuration (M24 CIE at A2D_M24_CIE_OFF) + * @param raw_len Raw AAC access unit length in bytes (encoded in payloadLengthInfo) + * + * @return Prefix size in bytes, or 0 if @p codec_cfg is NULL, unsupported, or not byte-aligned + */ +uint8_t btc_a2dp_latm_au_prefix_bytes(uint8_t *codec_cfg, uint16_t raw_len); + +/** + * @brief Bit length of payloadLengthInfo for frameLengthType 0. + * + * @param payload_len Raw AAC payload length in bytes + * + * @return Number of bits in the ISO 14496-3 length escape sequence + */ +uint64_t btc_a2dp_latm_payload_length_bits(uint16_t payload_len); +#endif /* #if (BTC_AV_SRC_INCLUDED == TRUE) */ + +#endif /* #if (BTC_AV_CODEC_AAC_INCLUDED == TRUE) */ #ifdef __cplusplus } diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h b/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h index 66820c88900..1c989ad8281 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/include/btc_av_co.h @@ -293,14 +293,12 @@ BOOLEAN bta_av_co_get_peer_sink_caps(tBTA_AV_HNDL hndl, UINT8 *p_codec_caps, UIN /******************************************************************************* ** - ** Function bta_av_co_get_cur_codec_type + ** Function bta_av_co_get_cur_codec_info ** - ** Description Get current codec type - ** - ** Returns codec type + ** Description Get current codec info ** *******************************************************************************/ -UINT8 bta_av_co_get_cur_codec_type(void); +void bta_av_co_get_cur_codec_info(tBTC_AV_CODEC_INFO *cur_codec_info); #endif ///BTA_AV_INCLUDED == TRUE diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_av.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_av.h index f0025db3a1d..dc229d6f942 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_av.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_av.h @@ -29,12 +29,13 @@ #if (BTC_AV_INCLUDED == TRUE) #if (BTA_AV_CO_CP_SCMS_T == TRUE) -#define BTC_MEDIA_AA_SBC_OFFSET (AVDT_MEDIA_OFFSET + BTA_AV_SBC_HDR_SIZE + 1) +#define BTC_MEDIA_AA_OFFSET (AVDT_MEDIA_OFFSET + 1) #else -#define BTC_MEDIA_AA_SBC_OFFSET (AVDT_MEDIA_OFFSET + BTA_AV_SBC_HDR_SIZE) +#define BTC_MEDIA_AA_OFFSET (AVDT_MEDIA_OFFSET) #endif -#define BTC_AUDIO_BUFF_OFFSET BTC_MEDIA_AA_SBC_OFFSET +#define BTC_MEDIA_AA_SBC_OFFSET (BTC_MEDIA_AA_OFFSET + BTA_AV_SBC_HDR_SIZE) +#define BTC_AUDIO_BUFF_OFFSET (BTC_MEDIA_AA_OFFSET) // global variable to indicate avrc is initialized with a2dp extern bool g_av_with_rc; From 22f116eed5712bfeb8e0c2d7cff5b4b8239cdeed Mon Sep 17 00:00:00 2001 From: yangfeng Date: Mon, 18 May 2026 20:32:51 +0800 Subject: [PATCH 2/3] feat(bt): Add a2dp_sink_stream_aac example --- docs/en/api-reference/bluetooth/esp_a2dp.rst | 2 + .../a2dp_sink_stream_aac/CMakeLists.txt | 8 + .../classic_bt/a2dp_sink_stream_aac/README.md | 79 +++ .../a2dp_sink_stream_aac/main/CMakeLists.txt | 4 + .../main/Kconfig.projbuild | 8 + .../main/idf_component.yml | 12 + .../a2dp_sink_stream_aac/main/main.c | 537 ++++++++++++++++++ .../a2dp_sink_stream_aac/sdkconfig.ci.ca_dis | 9 + .../a2dp_sink_stream_aac/sdkconfig.ci.test | 1 + .../a2dp_sink_stream_aac/sdkconfig.defaults | 12 + .../sdkconfig.defaults.esp32s31 | 2 + .../a2dp_sink_ext_codec_utils.c | 42 +- .../audio_sink_service_dac.c | 60 +- .../audio_sink_service_i2s.c | 49 +- 14 files changed, 821 insertions(+), 4 deletions(-) create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 diff --git a/docs/en/api-reference/bluetooth/esp_a2dp.rst b/docs/en/api-reference/bluetooth/esp_a2dp.rst index 8e1105e2583..eebf1e764be 100644 --- a/docs/en/api-reference/bluetooth/esp_a2dp.rst +++ b/docs/en/api-reference/bluetooth/esp_a2dp.rst @@ -13,6 +13,8 @@ Application Examples - :example:`bluetooth/bluedroid/classic_bt/a2dp_source` demonstrates how to use A2DP APIs to transmit audio streams. +- :example:`bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac` demonstrates how to use the AAC codec in an A2DP sink. + - :example:`bluetooth/bluedroid/coex/a2dp_gatts_coex` demonstrates how to use the ESP-IDF A2DP-GATTS_COEX demo to create a GATT service and A2DP profile. API Reference diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt new file mode 100644 index 00000000000..6f3e5633b03 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(a2dp_sink_stream_aac) diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md new file mode 100644 index 00000000000..c673a4f4dda --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/README.md @@ -0,0 +1,79 @@ +| Supported Targets | ESP32 | ESP32-S31 | +| ----------------- | ----- | --------- | + +A2DP-SINK-STREAM-AAC EXAMPLE +====================== + +Example of A2DP audio sink role with **external codec** and **software decoding** (SBC and AAC). + +This example uses the Bluedroid **external codec** path: encoded media (SBC or MPEG-2/4 AAC) is delivered to the application through `esp_a2d_sink_register_audio_data_callback()`. The application decodes the stream with [esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec). + +## Required components + +- [bt_app_core_utils](../common/bt_app_core_utils) +- [bredr_app_common_utils](../common/bredr_app_common_utils) +- [a2dp_sink_common_utils](../common/a2dp_utils/a2dp_sink_common_utils) +- [a2dp_sink_int_codec_utils](../common/a2dp_utils/a2dp_sink_int_codec_utils) (I2S / DAC output) +- [a2dp_sink_ext_codec_utils](../common/a2dp_utils/a2dp_sink_ext_codec_utils) +- [espressif/esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec) + +Detailed information can be viewed through the [../common/README.md](../common/README.md). + +## How to use this example + +### Hardware Required + +To play the sound, there is a need of loudspeaker and possibly an external I2S codec. Otherwise the example will only show a count of audio data packets received silently. Internal DAC can be selected and in this case external I2S codec may not be needed. + +For the I2S codec, pick whatever chip or board works for you; this code was written using a PCM5102 chip, but other I2S boards and chips will probably work as well. The default I2S connections are shown below, but these can be changed in menuconfig: + +| ESP pin | I2S signal | +| :-------- | :----------- | +| GPIO27 | LRCK | +| GPIO25 | DATA | +| GPIO26 | BCK | + +If the internal DAC is selected, analog audio will be available on GPIO25 and GPIO26. The output resolution on these pins will always be limited to 8 bit because of the internal structure of the DACs. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Sink Internal Codec Example Configuration. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output. + +``` +idf.py -p PORT flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +After the program is started, the example starts inquiry scan and page scan, awaiting being discovered and connected. Other bluetooth devices such as smart phones can discover a device named "ESP_SPEAKER_AAC". A smartphone or another ESP-IDF example of A2DP source can be used to connect to the local device. + +Once A2DP connection is set up, there will be a notification message with the remote device's bluetooth MAC address like the following: + +``` +I (106427) BT_AV: A2DP connection state: Connected, [64:a2:f9:69:57:a4] +``` + +If a smartphone is used to connect to local device, starting to play music with an APP will result in the transmission of audio stream. The transmitting of audio stream will be visible in the application log including a count of audio data packets, like this: + +``` +I (120627) BT_AV: A2DP audio state: Started +I (122697) BT_AV: Undecoded audio package count: 100 +I (124697) BT_AV: Undecoded audio package count: 200 +I (126697) BT_AV: Undecoded audio package count: 300 +I (128697) BT_AV: Undecoded audio package count: 400 +``` + + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt new file mode 100644 index 00000000000..8042bdcecdc --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "main.c" + PRIV_REQUIRES bt bt_app_core_utils bredr_app_common_utils a2dp_sink_common_utils + PRIV_REQUIRES a2dp_sink_ext_codec_utils a2dp_sink_int_codec_utils + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild new file mode 100644 index 00000000000..0753260782d --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/Kconfig.projbuild @@ -0,0 +1,8 @@ +menu "A2DP Example Configuration" + config EXAMPLE_LOCAL_DEVICE_NAME + string "Local Device Name" + default "ESP_SPEAKER_AAC" + help + Use this option to set local device name. + +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml new file mode 100644 index 00000000000..c14ba5bff24 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/idf_component.yml @@ -0,0 +1,12 @@ +dependencies: + bt_app_core_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bt_app_core_utils + bredr_app_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/bredr_app_common_utils + a2dp_sink_common_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_common_utils + a2dp_sink_int_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils + a2dp_sink_ext_codec_utils: + path: ${IDF_PATH}/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils + espressif/esp_audio_codec: ^2.4.0 diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c new file mode 100644 index 00000000000..487090177f6 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/main/main.c @@ -0,0 +1,537 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_audio_dec.h" +#include "esp_aac_dec.h" +#include "esp_sbc_dec.h" +#include "esp_audio_dec_reg.h" +#include "bt_app_core_utils.h" +#include "bredr_app_common_utils.h" +#include "a2dp_sink_common_utils.h" +#include "a2dp_utils_tags.h" +#include "a2dp_sink_int_codec_utils.h" +#include "a2dp_sink_ext_codec_utils.h" +#include "audio_sink_service.h" + +#define BT_A2DP_RAW_Q_DEPTH 24 +#define BT_A2DP_DECODE_TASK_STACK 4096 +#ifndef BT_A2DP_DECODE_TASK_PRIO +#define BT_A2DP_DECODE_TASK_PRIO (tskIDLE_PRIORITY + 5) +#endif + +static QueueHandle_t s_a2dp_raw_q; +static TaskHandle_t s_a2dp_dec_task; +static SemaphoreHandle_t s_a2dp_raw_mux; +static bool s_a2dp_raw_started; +static uint32_t s_a2dp_raw_pkt_cnt; +static esp_audio_dec_handle_t s_dec_hdl; +static bool s_dec_opened; + +/* device name */ +static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME; + +/* event for stack up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/******************************** + * STATIC FUNCTION DECLARATIONS + *******************************/ + +/* Device callback function */ +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param); + +/* GAP callback function */ +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); + +/* callback function for A2DP sink */ +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/* callback function for A2DP sink undecoded audio data */ +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf); + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/******************************* + * STATIC FUNCTION DEFINITIONS + ******************************/ + +static int bt_app_mcc_get_sample_rate(const esp_a2d_mcc_t *mcc) +{ + if (mcc->type == ESP_A2D_MCT_SBC) { + const esp_a2d_cie_sbc_t *sbc = &mcc->cie.sbc_info; + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_48K) { + return 48000; + } + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_44K) { + return 44100; + } + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_32K) { + return 32000; + } + if (sbc->samp_freq & ESP_A2D_SBC_CIE_SF_16K) { + return 16000; + } + return 44100; + } + if (mcc->type == ESP_A2D_MCT_M24) { + const esp_a2d_cie_m24_t *m24 = &mcc->cie.m24_info; + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + return 96000; + } + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + return 88200; + } + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + return 64000; + } + if (m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + return 48000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + return 44100; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + return 32000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + return 24000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + return 22050; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + return 16000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + return 12000; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + return 11025; + } + if (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + return 8000; + } + return 44100; + } + return 44100; +} + +static int bt_app_mcc_get_channel_count(const esp_a2d_mcc_t *mcc) +{ + if (mcc->type == ESP_A2D_MCT_SBC) { + if (mcc->cie.sbc_info.ch_mode & ESP_A2D_SBC_CIE_CH_MODE_MONO) { + return 1; + } + return 2; + } + if (mcc->type == ESP_A2D_MCT_M24) { + if (mcc->cie.m24_info.ch & ESP_A2D_M24_CIE_CH_1) { + return 1; + } + return 2; + } + return 2; +} + +static void bt_app_audio_decoder_close(void) +{ + if (!s_dec_opened) { + return; + } + esp_audio_dec_close(s_dec_hdl); + s_dec_hdl = NULL; + s_dec_opened = false; +} + +static esp_audio_err_t bt_app_audio_decoder_apply_mcc(const esp_a2d_mcc_t *mcc) +{ + esp_audio_err_t ret; + + if (mcc == NULL) { + return ESP_AUDIO_ERR_INVALID_PARAMETER; + } + if (mcc->type != ESP_A2D_MCT_SBC && mcc->type != ESP_A2D_MCT_M24) { + ESP_LOGW(BT_AV_TAG, "Unsupported A2DP codec type: %d", mcc->type); + return ESP_AUDIO_ERR_FAIL; + } + + bt_app_audio_decoder_close(); + + if (mcc->type == ESP_A2D_MCT_SBC) { + esp_sbc_dec_cfg_t sbc_cfg = ESP_SBC_DEC_CONFIG_DEFAULT(); + sbc_cfg.sbc_mode = ESP_SBC_MODE_STD; + sbc_cfg.ch_num = bt_app_mcc_get_channel_count(mcc); + sbc_cfg.enable_plc = true; + + esp_audio_dec_cfg_t dec_cfg = { + .type = ESP_AUDIO_TYPE_SBC, + .cfg = &sbc_cfg, + .cfg_sz = sizeof(esp_sbc_dec_cfg_t), + }; + + esp_sbc_dec_register(); + ret = esp_audio_dec_open(&dec_cfg, &s_dec_hdl); + if (ret != ESP_AUDIO_ERR_OK) { + ESP_LOGE(BT_AV_TAG, "esp_audio_dec_open (SBC) failed: %d", ret); + return ret; + } + ESP_LOGI(BT_AV_TAG, "SBC decoder open: %" PRIu32 " Hz, %d ch", + (uint32_t)bt_app_mcc_get_sample_rate(mcc), sbc_cfg.ch_num); + } else { + const esp_a2d_cie_m24_t *m24 = &mcc->cie.m24_info; + esp_aac_dec_cfg_t aac_cfg = ESP_AAC_DEC_CONFIG_DEFAULT(); + aac_cfg.sample_rate = bt_app_mcc_get_sample_rate(mcc); + aac_cfg.channel = bt_app_mcc_get_channel_count(mcc); + aac_cfg.bits_per_sample = ESP_AUDIO_BIT16; + aac_cfg.no_adts_header = true; + aac_cfg.aac_plus_enable = (m24->obj_type & (ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC_V2)) != 0; + + esp_audio_dec_cfg_t dec_cfg = { + .type = ESP_AUDIO_TYPE_AAC, + .cfg = &aac_cfg, + .cfg_sz = sizeof(esp_aac_dec_cfg_t), + }; + + esp_aac_dec_register(); + ret = esp_audio_dec_open(&dec_cfg, &s_dec_hdl); + if (ret != ESP_AUDIO_ERR_OK) { + ESP_LOGE(BT_AV_TAG, "esp_audio_dec_open (AAC) failed: %d", ret); + return ret; + } + ESP_LOGI(BT_AV_TAG, "AAC decoder open: %" PRIu32 " Hz, %d ch, aac_plus=%d", + (uint32_t)aac_cfg.sample_rate, aac_cfg.channel, aac_cfg.aac_plus_enable); + } + + s_dec_opened = true; + return ESP_AUDIO_ERR_OK; +} + +static void bt_app_a2d_raw_audio_task(void *arg) +{ + esp_a2d_audio_buff_t *audio_buf; + uint32_t max_out_size = 4096; + uint8_t *out_buf = (uint8_t *)malloc((size_t)max_out_size); + if (out_buf == NULL) { + ESP_LOGE(BT_AV_TAG, "raw dec: out buffer alloc failed"); + vTaskDelete(NULL); + return; + } + + for (;;) { + xQueueReceive(s_a2dp_raw_q, &audio_buf, portMAX_DELAY); + + if (!s_dec_opened) { + esp_a2d_audio_buff_free(audio_buf); + continue; + } + + esp_audio_dec_in_raw_t in_raw = { + .buffer = audio_buf->data, + .len = audio_buf->data_len, + }; + esp_audio_dec_out_frame_t out_frame = { + .buffer = out_buf, + .len = max_out_size, + }; + + while (in_raw.len > 0) { + esp_audio_err_t dec_ret = esp_audio_dec_process(s_dec_hdl, &in_raw, &out_frame); + if (dec_ret != ESP_AUDIO_ERR_OK && dec_ret != ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) { + ESP_LOGW(BT_AV_TAG, "esp_audio_dec_process failed: %d", dec_ret); + break; + } + + if (dec_ret == ESP_AUDIO_ERR_BUFF_NOT_ENOUGH) { + uint8_t *new_dec_buff = (uint8_t *)realloc(out_frame.buffer, (size_t)max_out_size * 2u); + if (new_dec_buff == NULL) { + break; + } + max_out_size *= 2; + out_buf = new_dec_buff; + out_frame.buffer = new_dec_buff; + out_frame.len = max_out_size; + continue; + } + + audio_sink_srv_data_output(out_frame.buffer, out_frame.decoded_size); + in_raw.buffer += in_raw.consumed; + in_raw.len -= in_raw.consumed; + } + + esp_a2d_audio_buff_free(audio_buf); + } +} + +void bt_a2d_sink_ext_codec_postproc_start(void) +{ + if (s_a2dp_raw_mux == NULL) { + s_a2dp_raw_mux = xSemaphoreCreateMutex(); + if (s_a2dp_raw_mux == NULL) { + return; + } + } + if (xSemaphoreTake(s_a2dp_raw_mux, portMAX_DELAY) != pdTRUE) { + return; + } + if (s_a2dp_raw_started) { + xSemaphoreGive(s_a2dp_raw_mux); + return; + } + + s_a2dp_raw_q = xQueueCreate(BT_A2DP_RAW_Q_DEPTH, sizeof(esp_a2d_audio_buff_t *)); + if (s_a2dp_raw_q == NULL) { + xSemaphoreGive(s_a2dp_raw_mux); + ESP_LOGE(BT_AV_TAG, "A2DP raw queue create failed"); + return; + } + if (xTaskCreate(bt_app_a2d_raw_audio_task, "a2dp_raw_dec", BT_A2DP_DECODE_TASK_STACK, + NULL, BT_A2DP_DECODE_TASK_PRIO, &s_a2dp_dec_task) != pdPASS) { + vQueueDelete(s_a2dp_raw_q); + s_a2dp_raw_q = NULL; + s_a2dp_dec_task = NULL; + xSemaphoreGive(s_a2dp_raw_mux); + ESP_LOGE(BT_AV_TAG, "A2DP raw decoder task create failed"); + return; + } + s_a2dp_raw_started = true; + xSemaphoreGive(s_a2dp_raw_mux); + ESP_LOGI(BT_AV_TAG, "A2DP raw postproc (queue %d) started", BT_A2DP_RAW_Q_DEPTH); +} + +void bt_a2d_sink_ext_codec_postproc_flush(void) +{ + esp_a2d_audio_buff_t *audio_buf; + + if (s_a2dp_raw_mux == NULL) { + return; + } + if (xSemaphoreTake(s_a2dp_raw_mux, portMAX_DELAY) != pdTRUE) { + return; + } + if (s_a2dp_dec_task != NULL) { + vTaskSuspend(s_a2dp_dec_task); + } + if (s_a2dp_raw_q != NULL) { + while (xQueueReceive(s_a2dp_raw_q, &audio_buf, 0) == pdTRUE) { + esp_a2d_audio_buff_free(audio_buf); + } + } + if (s_a2dp_dec_task != NULL) { + vTaskResume(s_a2dp_dec_task); + } + bt_app_audio_decoder_close(); + xSemaphoreGive(s_a2dp_raw_mux); +} + +static void bt_app_register_a2dp_sink_seps(void) +{ + esp_a2d_mcc_t mcc_aac = {0}; + mcc_aac.type = ESP_A2D_MCT_M24; + mcc_aac.cie.m24_info.drc = ESP_A2D_M24_CIE_DRC_NS; + mcc_aac.cie.m24_info.obj_type = ESP_A2D_M24_CIE_OBJ_TYPE_2_AAC_LC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_LC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_HE_AAC_V2; + mcc_aac.cie.m24_info.samp_freq1 = ESP_A2D_M24_CIE_SF1_8K | + ESP_A2D_M24_CIE_SF1_11K | + ESP_A2D_M24_CIE_SF1_12K | + ESP_A2D_M24_CIE_SF1_16K | + ESP_A2D_M24_CIE_SF1_22K | + ESP_A2D_M24_CIE_SF1_24K | + ESP_A2D_M24_CIE_SF1_32K | + ESP_A2D_M24_CIE_SF1_44K; + mcc_aac.cie.m24_info.samp_freq2 = ESP_A2D_M24_CIE_SF2_48K | + ESP_A2D_M24_CIE_SF2_64K | + ESP_A2D_M24_CIE_SF2_88K | + ESP_A2D_M24_CIE_SF2_96K; + mcc_aac.cie.m24_info.ch = ESP_A2D_M24_CIE_CH_1 | + ESP_A2D_M24_CIE_CH_2; + mcc_aac.cie.m24_info.vbr = ESP_A2D_M24_CIE_VBR_SUPPORT; + mcc_aac.cie.m24_info.br1 = 0x7F & ESP_A2D_M24_CIE_BR1_MSK; + mcc_aac.cie.m24_info.br2 = 0xFF & ESP_A2D_M24_CIE_BR2_MSK; + mcc_aac.cie.m24_info.br3 = 0xFF & ESP_A2D_M24_CIE_BR3_MSK; + esp_a2d_sink_register_stream_endpoint(0, &mcc_aac); + + esp_a2d_mcc_t mcc_sbc = {0}; + mcc_sbc.type = ESP_A2D_MCT_SBC; + mcc_sbc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_16K | + ESP_A2D_SBC_CIE_SF_32K | + ESP_A2D_SBC_CIE_SF_44K | + ESP_A2D_SBC_CIE_SF_48K; + mcc_sbc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO | + ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | + ESP_A2D_SBC_CIE_CH_MODE_STEREO | + ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO; + mcc_sbc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_4 | + ESP_A2D_SBC_CIE_BLOCK_LEN_8 | + ESP_A2D_SBC_CIE_BLOCK_LEN_12 | + ESP_A2D_SBC_CIE_BLOCK_LEN_16; + mcc_sbc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + mcc_sbc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_SNR | ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + mcc_sbc.cie.sbc_info.min_bitpool = 2; + mcc_sbc.cie.sbc_info.max_bitpool = 250; + esp_a2d_sink_register_stream_endpoint(1, &mcc_sbc); +} + +static void bt_app_dev_cb(esp_bt_dev_cb_event_t event, esp_bt_dev_cb_param_t *param) +{ + bredr_app_dev_evt_def_hdl(event, param); +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + bredr_app_gap_evt_def_hdl(event, param); +} + +static void bt_app_a2d_hdl(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = (esp_a2d_cb_param_t *)param; + + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: { + bt_a2d_evt_def_hdl(event, param); + break; + } + case ESP_A2D_CONNECTION_STATE_EVT: { + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + bt_a2d_sink_ext_codec_postproc_flush(); + } + bt_a2d_evt_int_codec_hdl(event, param); + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + if (a2d->audio_stat.state == ESP_A2D_AUDIO_STATE_STARTED) { + bt_a2d_sink_ext_codec_postproc_start(); + } else if (a2d->audio_stat.state == ESP_A2D_AUDIO_STATE_SUSPEND) { + bt_a2d_sink_ext_codec_postproc_flush(); + } + bt_a2d_evt_int_codec_hdl(event, param); + break; + } + case ESP_A2D_AUDIO_CFG_EVT: + bt_app_audio_decoder_apply_mcc(&a2d->audio_cfg.mcc); + bt_a2d_evt_int_codec_hdl(event, param); + break; + case ESP_A2D_SEP_REG_STATE_EVT: { + bt_a2d_evt_ext_codec_hdl(event, param); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_PROF_STATE_EVT: + case ESP_A2D_SNK_PSC_CFG_EVT: + case ESP_A2D_SNK_SET_DELAY_VALUE_EVT: + case ESP_A2D_SNK_GET_DELAY_VALUE_EVT: + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_SEP_REG_STATE_EVT: { + bt_app_work_dispatch(bt_app_a2d_hdl, event, param, sizeof(esp_a2d_cb_param_t), NULL, NULL); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +static void bt_app_a2d_audio_data_cb(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buf) +{ + (void)conn_hdl; + + if (audio_buf == NULL || audio_buf->data == NULL || audio_buf->data_len == 0) { + if (audio_buf != NULL) { + esp_a2d_audio_buff_free(audio_buf); + } + return; + } + + if (!s_a2dp_raw_started) { + esp_a2d_audio_buff_free(audio_buf); + return; + } + + if (++s_a2dp_raw_pkt_cnt % 100 == 0) { + ESP_LOGI(BT_AV_TAG, "Undecoded audio package count: %" PRIu32, s_a2dp_raw_pkt_cnt); + } + + if (xQueueSend(s_a2dp_raw_q, &audio_buf, 0) != pdTRUE) { + ESP_LOGW(BT_AV_TAG, "raw queue full, drop packet"); + esp_a2d_audio_buff_free(audio_buf); + } +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + switch (event) { + /* when do the stack up, this event comes */ + case BT_APP_EVT_STACK_UP: { + esp_bt_gap_set_device_name(local_device_name); + esp_bt_dev_register_callback(bt_app_dev_cb); + esp_bt_gap_register_callback(bt_app_gap_cb); + + esp_a2d_register_callback(&bt_app_a2d_cb); + assert(esp_a2d_sink_init() == ESP_OK); + + bt_app_register_a2dp_sink_seps(); + esp_a2d_sink_register_audio_data_callback(bt_app_a2d_audio_data_cb); + + /* Get the default value of the delay value */ + esp_a2d_sink_get_delay_value(); + /* Get local device name */ + esp_bt_gap_get_device_name(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + /* others */ + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +/******************************* + * MAIN ENTRY POINT + ******************************/ + +void app_main(void) +{ + ESP_ERROR_CHECK(bredr_app_common_init()); + + bt_app_task_start_up(); + /* bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL, NULL); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis new file mode 100644 index 00000000000..9d5c2a060d0 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.ca_dis @@ -0,0 +1,9 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=n +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test new file mode 100644 index 00000000000..18281d6e860 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.ci.test @@ -0,0 +1 @@ +CONFIG_EXAMPLE_LOCAL_DEVICE_NAME="${CI_PIPELINE_ID}_A2DP_SINK_AAC" diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults new file mode 100644 index 00000000000..7a28de674f7 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults @@ -0,0 +1,12 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_BLE_ENABLED=n +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n +CONFIG_BT_A2DP_USE_EXTERNAL_CODEC=y +CONFIG_BT_A2DP_SEP_NUM_MAX=2 +CONFIG_BT_A2DP_CODEC_AAC_ENABLED=y diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 new file mode 100644 index 00000000000..9a77739a3d4 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac/sdkconfig.defaults.esp32s31 @@ -0,0 +1,2 @@ +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y +CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_IDLE=y diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c index e19ca2d95d8..7b13596a5b1 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_ext_codec_utils/a2dp_sink_ext_codec_utils.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -63,7 +63,7 @@ void bt_a2d_evt_ext_codec_hdl(uint16_t event, void *param) case ESP_A2D_AUDIO_CFG_EVT: { esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); - /* for now only SBC stream is supported */ + if (p_mcc->type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; if (p_mcc->cie.sbc_info.samp_freq & ESP_A2D_SBC_CIE_SF_32K) { @@ -82,6 +82,44 @@ void bt_a2d_evt_ext_codec_hdl(uint16_t event, void *param) p_mcc->cie.sbc_info.min_bitpool, p_mcc->cie.sbc_info.max_bitpool); ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate: %d", sample_rate); + } else if (p_mcc->type == ESP_A2D_MCT_M24) { + int sample_rate = 16000; + if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + sample_rate = 96000; + } else if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + sample_rate = 88200; + } else if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + sample_rate = 64000; + } else if (p_mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + sample_rate = 48000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + sample_rate = 44100; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + sample_rate = 32000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + sample_rate = 24000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + sample_rate = 22050; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + sample_rate = 16000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + sample_rate = 12000; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + sample_rate = 11025; + } else if (p_mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + sample_rate = 8000; + } + ESP_LOGI(BT_AV_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + p_mcc->cie.m24_info.drc, + p_mcc->cie.m24_info.obj_type, + p_mcc->cie.m24_info.samp_freq1, + p_mcc->cie.m24_info.samp_freq2, + p_mcc->cie.m24_info.ch, + p_mcc->cie.m24_info.vbr, + p_mcc->cie.m24_info.br1, + p_mcc->cie.m24_info.br2, + p_mcc->cie.m24_info.br3); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate: %d", sample_rate); } break; } diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c index 44132db0df8..87406ac73cf 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_dac.c @@ -179,7 +179,7 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) { ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "A2DP audio stream configuration, codec type: %d", mcc->type); audio_sink_srv_stop(); - /* for now only SBC stream is supported */ + if (mcc->type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; int ch_count = 2; @@ -218,6 +218,64 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) mcc->cie.sbc_info.min_bitpool, mcc->cie.sbc_info.max_bitpool); ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Audio player configured, sample rate: %d", sample_rate); + } else if (mcc->type == ESP_A2D_MCT_M24) { + int sample_rate = 16000; + int ch_count = 2; + if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + sample_rate = 96000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + sample_rate = 88200; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + sample_rate = 64000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + sample_rate = 48000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + sample_rate = 44100; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + sample_rate = 32000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + sample_rate = 24000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + sample_rate = 22050; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + sample_rate = 16000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + sample_rate = 12000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + sample_rate = 11025; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + sample_rate = 8000; + } + + if (mcc->cie.m24_info.ch & ESP_A2D_M24_CIE_CH_1) { + ch_count = 1; + } + if (s_dac_cb.tx_chan) { + dac_continuous_del_channels(s_dac_cb.tx_chan); + s_dac_cb.tx_chan = NULL; + } + dac_continuous_config_t cont_cfg = { + .chan_mask = DAC_CHANNEL_MASK_ALL, + .desc_num = 8, + .buf_size = 2048, + .freq_hz = sample_rate, + .offset = 127, + .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // Using APLL as clock source to get a wider frequency range + .chan_mode = (ch_count == 1) ? DAC_CHANNEL_MODE_SIMUL : DAC_CHANNEL_MODE_ALTER, + }; + /* Allocate continuous channels */ + ESP_ERROR_CHECK(dac_continuous_new_channels(&cont_cfg, &s_dac_cb.tx_chan)); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + mcc->cie.m24_info.drc, + mcc->cie.m24_info.obj_type, + mcc->cie.m24_info.samp_freq1, + mcc->cie.m24_info.samp_freq2, + mcc->cie.m24_info.ch, + mcc->cie.m24_info.vbr, + mcc->cie.m24_info.br1, + mcc->cie.m24_info.br2, + mcc->cie.m24_info.br3); + ESP_LOGI(AUDIO_SNK_SRV_DAC_TAG, "Audio player configured, sample rate: %d", sample_rate); } } diff --git a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c index 9049d74710f..182a7a73ed7 100644 --- a/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c +++ b/examples/bluetooth/bluedroid/classic_bt/common/a2dp_utils/a2dp_sink_int_codec_utils/audio_sink_service_i2s.c @@ -189,7 +189,7 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) { ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "A2DP audio stream configuration, codec type: %d", mcc->type); audio_sink_srv_stop(); - /* for now only SBC stream is supported */ + if (mcc->type == ESP_A2D_MCT_SBC) { int sample_rate = 16000; int ch_count = 2; @@ -217,6 +217,53 @@ void audio_sink_srv_codec_info_update(esp_a2d_mcc_t *mcc) mcc->cie.sbc_info.min_bitpool, mcc->cie.sbc_info.max_bitpool); ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Audio player configured, sample rate: %d", sample_rate); + } else if (mcc->type == ESP_A2D_MCT_M24) { + int sample_rate = 16000; + int ch_count = 2; + if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_96K) { + sample_rate = 96000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_88K) { + sample_rate = 88200; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_64K) { + sample_rate = 64000; + } else if (mcc->cie.m24_info.samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) { + sample_rate = 48000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) { + sample_rate = 44100; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_32K) { + sample_rate = 32000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_24K) { + sample_rate = 24000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_22K) { + sample_rate = 22050; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_16K) { + sample_rate = 16000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_12K) { + sample_rate = 12000; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_11K) { + sample_rate = 11025; + } else if (mcc->cie.m24_info.samp_freq1 & ESP_A2D_M24_CIE_SF1_8K) { + sample_rate = 8000; + } + + if (mcc->cie.m24_info.ch & ESP_A2D_M24_CIE_CH_1) { + ch_count = 1; + } + i2s_std_clk_config_t clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate); + i2s_std_slot_config_t slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, ch_count); + ESP_ERROR_CHECK(i2s_channel_reconfig_std_clock(s_i2s_cb.tx_chan, &clk_cfg)); + ESP_ERROR_CHECK(i2s_channel_reconfig_std_slot(s_i2s_cb.tx_chan, &slot_cfg)); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + mcc->cie.m24_info.drc, + mcc->cie.m24_info.obj_type, + mcc->cie.m24_info.samp_freq1, + mcc->cie.m24_info.samp_freq2, + mcc->cie.m24_info.ch, + mcc->cie.m24_info.vbr, + mcc->cie.m24_info.br1, + mcc->cie.m24_info.br2, + mcc->cie.m24_info.br3); + ESP_LOGI(AUDIO_SNK_SRV_I2S_TAG, "Audio player configured, sample rate: %d", sample_rate); } } From e7d079c4223b2a6bf34f8021c5199c6936545c61 Mon Sep 17 00:00:00 2001 From: yangfeng Date: Wed, 20 May 2026 16:33:41 +0800 Subject: [PATCH 3/3] feat(bt): Add a2dp_source_aac example --- docs/en/api-reference/bluetooth/esp_a2dp.rst | 2 + .../classic_bt/a2dp_source_aac/CMakeLists.txt | 8 + .../classic_bt/a2dp_source_aac/README.md | 99 ++ .../a2dp_source_aac/main/CMakeLists.txt | 5 + .../a2dp_source_aac/main/Kconfig.projbuild | 15 + .../a2dp_source_aac/main/bt_app_core.c | 152 +++ .../a2dp_source_aac/main/bt_app_core.h | 78 ++ .../a2dp_source_aac/main/decode_task.c | 515 ++++++++++ .../a2dp_source_aac/main/decode_task.h | 71 ++ .../a2dp_source_aac/main/idf_component.yml | 2 + .../classic_bt/a2dp_source_aac/main/main.c | 948 ++++++++++++++++++ .../a2dp_source_aac/sdkconfig.ci.test | 1 + .../a2dp_source_aac/sdkconfig.defaults | 13 + .../sdkconfig.defaults.esp32s31 | 1 + 14 files changed, 1910 insertions(+) create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/README.md create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/CMakeLists.txt create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/Kconfig.projbuild create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.h create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/idf_component.yml create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/main.c create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.ci.test create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults create mode 100644 examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults.esp32s31 diff --git a/docs/en/api-reference/bluetooth/esp_a2dp.rst b/docs/en/api-reference/bluetooth/esp_a2dp.rst index eebf1e764be..042a452d96e 100644 --- a/docs/en/api-reference/bluetooth/esp_a2dp.rst +++ b/docs/en/api-reference/bluetooth/esp_a2dp.rst @@ -15,6 +15,8 @@ Application Examples - :example:`bluetooth/bluedroid/classic_bt/a2dp_sink_stream_aac` demonstrates how to use the AAC codec in an A2DP sink. +- :example:`bluetooth/bluedroid/classic_bt/a2dp_source_aac` demonstrates how to use the AAC codec in an A2DP source. + - :example:`bluetooth/bluedroid/coex/a2dp_gatts_coex` demonstrates how to use the ESP-IDF A2DP-GATTS_COEX demo to create a GATT service and A2DP profile. API Reference diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/CMakeLists.txt new file mode 100644 index 00000000000..1b4cf567a9c --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.22) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(a2dp_source_aac) diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/README.md b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/README.md new file mode 100644 index 00000000000..83915e46f29 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/README.md @@ -0,0 +1,99 @@ +| Supported Targets | ESP32 | ESP32-S31 | +| ----------------- | ----- | --------- | + +A2DP-SOURCE-AAC EXAMPLE +======================== + +Example of A2DP audio **source** role with **external codec** and **software AAC encoding**. + +This example uses the Bluedroid **external codec** path: the application generates PCM, encodes it to MPEG-2/4 AAC (M24) with [esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec), and sends the encoded stream. + +## Required components + +- [espressif/esp_audio_codec](https://components.espressif.com/components/espressif/esp_audio_codec) + +## How to use this example + +### Hardware Required + +This example runs on any commonly available ESP32 or ESP32-S31 development board. + +It is intended to connect to an A2DP sink that supports AAC, such as [A2DP sink stream AAC example](../a2dp_sink_stream_aac). + +### Configure the project + +``` +idf.py menuconfig +``` + +* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output. + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +For the first step, this example performs device discovery to search for a target device (A2DP sink) whose device name is "ESP_SPEAKER_AAC" and whose "Rendering" bit of its Service Class field is set in its Class of Device (COD). If a candidate target is found, the local device will initiate connection with it. + +After connection with A2DP sink is established, the example performs the following running loop +1-2-3-4-1: +1. audio transmission starts and lasts for a while +2. audio transmission stops +3. disconnect with target device +4. reconnect to target device + +The example implements an event loop triggered by a periodic "heart beat" timer and events from Bluetooth protocol stack callback functions. + +After the local device discovers the target device and initiates connection, there will be logging message like this: + +``` +I (4090) BT_AV: Found a target device, address xx:xx:xx:xx:xx:xx, name ESP_SPEAKER_AAC +I (4090) BT_AV: Cancel device discovery ... +I (4100) BT_AV: Device discovery stopped. +I (4100) BT_AV: a2dp connecting to peer: ESP_SPEAKER_AAC +``` + +If connection is set up successfully, there will be such message: + +``` +I (5100) BT_AV: a2dp connected +``` + +Starting of audio transmission has the following notification message: + +``` +I (10880) BT_AV: a2dp media ready checking ... +... +I (10880) BT_AV: a2dp media ready, starting ... +... +I (11400) BT_AV: a2dp media start successfully. +I (11410) BT_AV_AAC: AAC enc: 44100 Hz, ch 2, bitrate 160000 (vbr=0) +I (11420) BT_AV_AAC: stream: 23219 us/frame, 44100 Hz, 1024 samples +``` + +Stop of audio transmission, and disconnection with remote device generate the following notification message: + +``` +I (110880) BT_AV: a2dp media suspending... +I (110920) BT_AV: a2dp media suspend successfully, disconnecting... +I (111040) BT_AV: a2dp disconnected +``` + +### Audio content + +PCM is a **440 Hz sine tone** (stereo by default, 16-bit). Each frame is encoded to AAC and paced to match the negotiated sample rate. On the sink side you should hear a steady tone, not random noise. + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/CMakeLists.txt b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/CMakeLists.txt new file mode 100644 index 00000000000..0209100dc6b --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "bt_app_core.c" + "main.c" + "decode_task.c" + PRIV_REQUIRES bt nvs_flash + INCLUDE_DIRS ".") diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/Kconfig.projbuild b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/Kconfig.projbuild new file mode 100644 index 00000000000..7226ef638bc --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/Kconfig.projbuild @@ -0,0 +1,15 @@ +menu "A2DP Example Configuration" + config EXAMPLE_SSP_ENABLED + bool "Secure Simple Pairing" + depends on BT_CLASSIC_ENABLED + default y + help + This enables the Secure Simple Pairing. If disable this option, + Bluedroid will only support Legacy Pairing + + config EXAMPLE_PEER_DEVICE_NAME + string "Target Device Name" + default "ESP_SPEAKER_AAC" + help + Use this option to set target device name to connect. +endmenu diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.c new file mode 100644 index 00000000000..34698b951b6 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.c @@ -0,0 +1,152 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" + +/********************************* + * STATIC FUNCTION DECLARATIONS + ********************************/ + +/* application task handler */ +static void bt_app_task_handler(void *arg); +/* message sender for Work queue */ +static bool bt_app_send_msg(bt_app_msg_t *msg); +/* handler for dispatched message */ +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +/********************************* + * STATIC VARIABLE DEFINITIONS + ********************************/ +static QueueHandle_t s_bt_app_task_queue = NULL; +static TaskHandle_t s_bt_app_task_handle = NULL; + +/********************************* + * STATIC FUNCTION DEFINITIONS + ********************************/ + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL || s_bt_app_task_queue == NULL) { + return false; + } + + if (pdTRUE != xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_PERIOD_MS)) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + + for (;;) { + /* receive message from work queue and handle it */ + if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (TickType_t)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, signal: 0x%x, event: 0x%x", __func__, msg.sig, msg.event); + + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled signal: %d", __func__, msg.sig); + break; + } + + if (msg.param) { + if (msg.free_cb) { + msg.free_cb(msg.param); + } + free(msg.param); + } + } + } +} + +/********************************* + * EXTERN FUNCTION DEFINITIONS + ********************************/ + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, + bt_app_copy_cb_t p_copy_cback, bt_app_free_cb_t p_free_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event: 0x%x, param len: %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + msg.free_cb = p_free_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(msg.param, p_params, param_len); + } + if (!bt_app_send_msg(&msg)) { + if (p_free_cback) { + p_free_cback(msg.param); + } + free(msg.param); + return false; + } + return true; + } + } + + return false; +} + +void bt_app_task_start_up(void) +{ + s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppTask", 3072, NULL, 10, &s_bt_app_task_handle); +} + +void bt_app_task_shut_down(void) +{ + if (s_bt_app_task_handle) { + vTaskDelete(s_bt_app_task_handle); + s_bt_app_task_handle = NULL; + } + if (s_bt_app_task_queue) { + bt_app_msg_t msg; + while (xQueueReceive(s_bt_app_task_queue, &msg, 0) == pdTRUE) { + if (msg.param) { + if (msg.free_cb) { + msg.free_cb(msg.param); + } + free(msg.param); + } + } + vQueueDelete(s_bt_app_task_queue); + s_bt_app_task_queue = NULL; + } +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.h b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.h new file mode 100644 index 00000000000..e6884dd2e00 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/bt_app_core.h @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +/* log tag */ +#define BT_APP_CORE_TAG "BT_APP_CORE" + +/* signal for dispatcher */ +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + * + * @param [in] event message event id + * @param [in] param pointer to the parameter + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/** + * @brief parameter deep-free function + * + * @param [in] p_param pointer to the parameter + */ + typedef void (* bt_app_free_cb_t) (void *p_param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + bt_app_free_cb_t free_cb; /*!< parameter deep-free function */ + void *param; /*!< parameter area needs to be last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + * + * @param [in] p_dest pointer to the destination + * @param [in] p_src pointer to the source + * @param [in] len data length in byte + */ +typedef void (* bt_app_copy_cb_t) (void *p_dest, void *p_src, int len); + +/** + * @brief work dispatcher for the application task + * + * @param [in] p_cback handler for the dispatched work (event handler) + * @param [in] event message event id + * @param [in] p_params pointer to the parameter + * @param [in] param_len length of the parameter + * @param [in] p_copy_cback parameter deep-copy function + * @param [in] p_free_cback parameter deep-free function + * + * @return true if work dispatch successfully, false otherwise + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, + bt_app_copy_cb_t p_copy_cback, bt_app_free_cb_t p_free_cback); + +/** + * @brief start up the application task + */ +void bt_app_task_start_up(void); + +/** + * @brief shut down the application task + */ +void bt_app_task_shut_down(void); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.c new file mode 100644 index 00000000000..46bf89557b1 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.c @@ -0,0 +1,515 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_a2dp_api.h" +#include "esp_audio_enc.h" +#include "esp_aac_enc.h" +#include "decode_task.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define ENCODE_TASK_TAG "BT_AV_AAC" + +#define A2DP_SRC_SEND_RETRY_DELAY_MS (2U) +#define A2DP_SRC_SEND_RETRY_MAX (10U) +#define A2DP_SRC_AAC_MTU_OVERHEAD_BYTES (20U) +#define A2DP_SRC_STREAM_TASK_STACK (4 * 1024) +#define A2DP_SRC_STREAM_TASK_PRIO (10) +#define A2DP_SRC_STREAM_STOP_WAIT_MS (3000U) + +#define A2DP_SRC_TONE_FREQ_HZ (440.0f) +#define A2DP_SRC_TONE_AMPLITUDE (12000.0f) + +#define ENCODE_DEFAULT_SAMPLE_RATE_HZ (44100) +#define ENCODE_DEFAULT_CHANNEL (ESP_AUDIO_DUAL) +#define ENCODE_DEFAULT_AAC_BITRATE (128000) + +typedef struct { + esp_audio_enc_handle_t enc_hdl; + uint8_t *pcm; + uint8_t *aac; + int inbuf_sz; + int outbuf_sz; + int sample_rate_hz; + int channel_count; + float tone_phase; + uint32_t samples_per_frame; + uint64_t frame_us; + int64_t next_frame_us; + uint32_t rtp_ts; +} aac_stream_ctx_t; + +static esp_a2d_conn_hdl_t s_conn_hdl; +static uint16_t s_audio_mtu; +static esp_a2d_mcc_t s_stream_mcc; +static bool s_stream_mcc_valid; +static TaskHandle_t s_stream_task_hdl; +static volatile bool s_stream_run; +static bool s_enc_registered; + +void encode_task_set_a2dp_link(esp_a2d_conn_hdl_t conn_hdl, uint16_t audio_mtu) +{ + s_conn_hdl = conn_hdl; + s_audio_mtu = audio_mtu; +} + +void encode_task_set_stream_config(const esp_a2d_mcc_t *mcc) +{ + if (mcc == NULL || mcc->type != ESP_A2D_MCT_M24) { + s_stream_mcc_valid = false; + return; + } + + s_stream_mcc = *mcc; + s_stream_mcc_valid = true; +} + +bool encode_task_has_stream_config(void) +{ + return s_stream_mcc_valid; +} + +static unsigned encode_task_popcount_u8(uint8_t v) +{ + unsigned n = 0; + + while (v != 0) { + n += (unsigned)(v & 1U); + v >>= 1; + } + return n; +} + +static bool encode_task_m24_sample_rate_hz(const esp_a2d_cie_m24_t *m24, int *hz_out) +{ + uint8_t sf1 = m24->samp_freq1; + uint8_t sf2 = m24->samp_freq2; + + if (encode_task_popcount_u8(sf1) + encode_task_popcount_u8(sf2) != 1U) { + return false; + } + + if (sf1 != 0) { + if (sf1 & ESP_A2D_M24_CIE_SF1_8K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_8K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_11K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_11K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_12K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_12K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_16K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_16K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_22K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_22K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_24K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_24K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_32K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_32K; + } else if (sf1 & ESP_A2D_M24_CIE_SF1_44K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_44K; + } else { + return false; + } + return true; + } + + if (sf2 & ESP_A2D_M24_CIE_SF2_48K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_48K; + } else if (sf2 & ESP_A2D_M24_CIE_SF2_64K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_64K; + } else if (sf2 & ESP_A2D_M24_CIE_SF2_88K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_88K; + } else if (sf2 & ESP_A2D_M24_CIE_SF2_96K) { + *hz_out = ESP_AUDIO_SAMPLE_RATE_96K; + } else { + return false; + } + + return true; +} + +static bool encode_task_m24_channel(const esp_a2d_cie_m24_t *m24, int *channel_out) +{ + uint8_t ch = m24->ch; + + if (encode_task_popcount_u8(ch) != 1U) { + return false; + } + + if (ch & ESP_A2D_M24_CIE_CH_1) { + *channel_out = ESP_AUDIO_MONO; + return true; + } + if (ch & ESP_A2D_M24_CIE_CH_2) { + *channel_out = ESP_AUDIO_DUAL; + return true; + } + + ESP_LOGW(ENCODE_TASK_TAG, "M24 channel 0x%02x not supported, use stereo", ch); + *channel_out = ESP_AUDIO_DUAL; + return true; +} + +static uint32_t encode_task_m24_bitrate_bps(const esp_a2d_cie_m24_t *m24) +{ + return ((uint32_t)(m24->br1 & ESP_A2D_M24_CIE_BR1_MSK) << 16) | + ((uint32_t)(m24->br2 & ESP_A2D_M24_CIE_BR2_MSK) << 8) | + (uint32_t)(m24->br3 & ESP_A2D_M24_CIE_BR3_MSK); +} + +static void encode_task_build_aac_config(esp_aac_enc_config_t *aac_cfg) +{ + *aac_cfg = (esp_aac_enc_config_t) { + .sample_rate = ENCODE_DEFAULT_SAMPLE_RATE_HZ, + .channel = ENCODE_DEFAULT_CHANNEL, + .bits_per_sample = ESP_AUDIO_BIT16, + .bitrate = ENCODE_DEFAULT_AAC_BITRATE, + .adts_used = false, + }; + + if (!s_stream_mcc_valid) { + ESP_LOGW(ENCODE_TASK_TAG, "No M24 config, use AAC defaults"); + return; + } + + const esp_a2d_cie_m24_t *m24 = &s_stream_mcc.cie.m24_info; + int sample_rate_hz = 0; + int channel = 0; + + if (!encode_task_m24_sample_rate_hz(m24, &sample_rate_hz)) { + ESP_LOGW(ENCODE_TASK_TAG, "Invalid M24 sample rate, use %d Hz", aac_cfg->sample_rate); + } else { + aac_cfg->sample_rate = sample_rate_hz; + } + + if (!encode_task_m24_channel(m24, &channel)) { + ESP_LOGW(ENCODE_TASK_TAG, "Invalid M24 channels, use ch %d", aac_cfg->channel); + } else { + aac_cfg->channel = channel; + } + + uint32_t br = encode_task_m24_bitrate_bps(m24); + bool vbr = (m24->vbr & ESP_A2D_M24_CIE_VBR_SUPPORT) != 0; + + if (br > 0) { + aac_cfg->bitrate = (int)br; + } else if (vbr) { + aac_cfg->bitrate = 0; + } + + ESP_LOGI(ENCODE_TASK_TAG, "AAC enc: %d Hz, ch %d, bitrate %d (vbr=%d)", + aac_cfg->sample_rate, aac_cfg->channel, aac_cfg->bitrate, vbr); +} + +static uint64_t encode_task_frame_us(int sample_rate_hz, uint32_t samples_per_frame) +{ + if (sample_rate_hz <= 0 || samples_per_frame == 0) { + return 23000ULL; + } + + return ((uint64_t)samples_per_frame * 1000000ULL) / (uint64_t)sample_rate_hz; +} + +static void encode_task_wait_ms(uint32_t wait_ms) +{ + if (wait_ms == 0) { + return; + } + + (void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(wait_ms)); +} + +static void encode_task_pace_frame(aac_stream_ctx_t *ctx) +{ + int64_t now_us = esp_timer_get_time(); + + if (ctx->next_frame_us <= 0) { + ctx->next_frame_us = now_us + (int64_t)ctx->frame_us; + return; + } + + if (now_us < ctx->next_frame_us) { + uint32_t wait_ms = (uint32_t)((ctx->next_frame_us - now_us + 999) / 1000); + encode_task_wait_ms(wait_ms); + } else if ((uint64_t)(now_us - ctx->next_frame_us) > ctx->frame_us) { + ctx->next_frame_us = esp_timer_get_time(); + } + + ctx->next_frame_us += (int64_t)ctx->frame_us; +} + +static uint16_t encode_task_max_encoded_bytes(uint16_t mtu) +{ + if (mtu <= A2DP_SRC_AAC_MTU_OVERHEAD_BYTES) { + return 0; + } + return (uint16_t)(mtu - A2DP_SRC_AAC_MTU_OVERHEAD_BYTES); +} + +static bool encode_task_payload_fits_mtu(uint16_t encoded_bytes, uint16_t mtu) +{ + if (encoded_bytes == 0 || mtu == 0) { + return false; + } + + return encoded_bytes <= encode_task_max_encoded_bytes(mtu); +} + +static esp_err_t encode_task_send_audio_frame(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_audio_buff_t *audio_buff) +{ + for (unsigned retry = 0; retry < A2DP_SRC_SEND_RETRY_MAX && s_stream_run; retry++) { + esp_err_t err = esp_a2d_source_audio_data_send(conn_hdl, audio_buff); + if (err == ESP_OK) { + return ESP_OK; + } + if (err == ESP_ERR_INVALID_ARG || err == ESP_ERR_INVALID_SIZE || err == ESP_ERR_INVALID_STATE) { + return err; + } + vTaskDelay(pdMS_TO_TICKS(A2DP_SRC_SEND_RETRY_DELAY_MS)); + } + + return ESP_FAIL; +} + +static void encode_task_fill_pcm_sine(aac_stream_ctx_t *ctx, uint8_t *pcm, int pcm_len) +{ + int16_t *samples = (int16_t *)pcm; + int ch = ctx->channel_count > 0 ? ctx->channel_count : 2; + int total = pcm_len / (int)sizeof(int16_t); + int frames = total / ch; + float phase_inc = (2.0f * (float)M_PI * A2DP_SRC_TONE_FREQ_HZ) / (float)ctx->sample_rate_hz; + + for (int i = 0; i < frames; i++) { + int16_t v = (int16_t)(A2DP_SRC_TONE_AMPLITUDE * sinf(ctx->tone_phase)); + + for (int c = 0; c < ch; c++) { + samples[i * ch + c] = v; + } + + ctx->tone_phase += phase_inc; + if (ctx->tone_phase >= 2.0f * (float)M_PI) { + ctx->tone_phase -= 2.0f * (float)M_PI; + } + } +} + +static bool encode_task_register_encoder(void) +{ + if (s_enc_registered) { + return true; + } + + if (esp_aac_enc_register() != ESP_AUDIO_ERR_OK) { + ESP_LOGE(ENCODE_TASK_TAG, "esp_aac_enc_register failed"); + return false; + } + + s_enc_registered = true; + return true; +} + +static bool encode_task_open_encoder(aac_stream_ctx_t *ctx) +{ + static esp_aac_enc_config_t aac_cfg; + esp_audio_enc_config_t enc_cfg; + esp_audio_enc_handle_t enc_hdl = NULL; + esp_audio_enc_info_t enc_info = {0}; + + encode_task_build_aac_config(&aac_cfg); + + enc_cfg.type = ESP_AUDIO_TYPE_AAC; + enc_cfg.cfg = &aac_cfg; + enc_cfg.cfg_sz = sizeof(aac_cfg); + + if (!encode_task_register_encoder()) { + return false; + } + + if (esp_audio_enc_open(&enc_cfg, &enc_hdl) != ESP_AUDIO_ERR_OK) { + ESP_LOGE(ENCODE_TASK_TAG, "esp_audio_enc_open failed"); + return false; + } + + esp_audio_enc_get_frame_size(enc_hdl, &ctx->inbuf_sz, &ctx->outbuf_sz); + esp_audio_enc_get_info(enc_hdl, &enc_info); + + ctx->enc_hdl = enc_hdl; + ctx->channel_count = (int)enc_info.channel > 0 ? (int)enc_info.channel : 2; + ctx->sample_rate_hz = (int)enc_info.sample_rate > 0 ? (int)enc_info.sample_rate : aac_cfg.sample_rate; + ctx->samples_per_frame = (uint32_t)(ctx->inbuf_sz / ((int)sizeof(int16_t) * ctx->channel_count)); + if (ctx->samples_per_frame == 0) { + ctx->samples_per_frame = 1024; + } + + ESP_LOGI(ENCODE_TASK_TAG, "AAC in=%d out=%d bitrate=%" PRId32, + ctx->inbuf_sz, ctx->outbuf_sz, enc_info.bitrate); + + return true; +} + +static void encode_task_stream_task(void *arg) +{ + aac_stream_ctx_t ctx = {0}; + + (void)arg; + + if (!encode_task_open_encoder(&ctx)) { + vTaskDelete(NULL); + return; + } + + ctx.frame_us = encode_task_frame_us(ctx.sample_rate_hz, ctx.samples_per_frame); + ctx.next_frame_us = 0; + + ESP_LOGI(ENCODE_TASK_TAG, "stream: %" PRIu64 " us/frame, %d Hz, %" PRIu32 " samples", + ctx.frame_us, ctx.sample_rate_hz, ctx.samples_per_frame); + ESP_LOGI(ENCODE_TASK_TAG, "A2DP MTU %u, max AAC payload %u", s_audio_mtu, + encode_task_max_encoded_bytes(s_audio_mtu)); + + ctx.pcm = malloc(ctx.inbuf_sz); + ctx.aac = malloc(ctx.outbuf_sz); + if (ctx.pcm == NULL || ctx.aac == NULL) { + ESP_LOGE(ENCODE_TASK_TAG, "stream buffer alloc failed"); + goto cleanup; + } + + while (s_stream_run) { + encode_task_pace_frame(&ctx); + if (!s_stream_run) { + break; + } + + encode_task_fill_pcm_sine(&ctx, ctx.pcm, ctx.inbuf_sz); + + esp_audio_enc_in_frame_t in_frame = { + .buffer = ctx.pcm, + .len = ctx.inbuf_sz, + }; + esp_audio_enc_out_frame_t out_frame = { + .buffer = ctx.aac, + .len = ctx.outbuf_sz, + }; + + esp_audio_err_t enc_ret = esp_audio_enc_process(ctx.enc_hdl, &in_frame, &out_frame); + if (enc_ret != ESP_AUDIO_ERR_OK || out_frame.encoded_bytes == 0) { + ESP_LOGE(ENCODE_TASK_TAG, "AAC encode failed: 0x%x, bytes %d", + enc_ret, out_frame.encoded_bytes); + continue; + } + + uint16_t encoded_bytes = (uint16_t)out_frame.encoded_bytes; + if (!encode_task_payload_fits_mtu(encoded_bytes, s_audio_mtu)) { + ESP_LOGW(ENCODE_TASK_TAG, "encoded %u bytes exceeds MTU %u (max %u), drop", + (unsigned)encoded_bytes, s_audio_mtu, encode_task_max_encoded_bytes(s_audio_mtu)); + continue; + } + + esp_a2d_audio_buff_t *audio_buff = esp_a2d_audio_buff_alloc(encoded_bytes); + if (audio_buff == NULL) { + ESP_LOGE(ENCODE_TASK_TAG, "esp_a2d_audio_buff_alloc failed"); + continue; + } + + audio_buff->data_len = encoded_bytes; + audio_buff->number_frame = 1; + audio_buff->timestamp = ctx.rtp_ts; + memcpy(audio_buff->data, ctx.aac, encoded_bytes); + + esp_err_t err = encode_task_send_audio_frame(s_conn_hdl, audio_buff); + if (err != ESP_OK) { + ESP_LOGE(ENCODE_TASK_TAG, "audio send failed: %s", esp_err_to_name(err)); + esp_a2d_audio_buff_free(audio_buff); + if (err == ESP_ERR_INVALID_STATE) { + break; + } + } else { + ctx.rtp_ts += ctx.samples_per_frame; + } + } + + ESP_LOGI(ENCODE_TASK_TAG, "stream task exiting"); + +cleanup: + free(ctx.pcm); + free(ctx.aac); + if (ctx.enc_hdl != NULL) { + esp_audio_enc_close(ctx.enc_hdl); + } + s_stream_task_hdl = NULL; + s_stream_run = false; + vTaskDelete(NULL); +} + +bool encode_task_is_running(void) +{ + return s_stream_task_hdl != NULL; +} + +void encode_task_stop(void) +{ + TaskHandle_t task = s_stream_task_hdl; + + if (task == NULL) { + return; + } + + s_stream_run = false; + xTaskNotifyGive(task); + + for (unsigned waited_ms = 0; waited_ms < A2DP_SRC_STREAM_STOP_WAIT_MS; waited_ms += 10) { + if (s_stream_task_hdl == NULL) { + ESP_LOGI(ENCODE_TASK_TAG, "stream task stopped"); + s_conn_hdl = 0; + s_audio_mtu = 0; + return; + } + vTaskDelay(pdMS_TO_TICKS(10)); + } + + if (s_stream_task_hdl != NULL) { + ESP_LOGW(ENCODE_TASK_TAG, "stream task stop timeout, delete task"); + vTaskDelete(s_stream_task_hdl); + s_stream_task_hdl = NULL; + s_stream_run = false; + } + + s_conn_hdl = 0; + s_audio_mtu = 0; +} + +esp_err_t encode_task_launch(void) +{ + if (s_stream_task_hdl != NULL) { + ESP_LOGW(ENCODE_TASK_TAG, "stream task already running"); + return ESP_ERR_INVALID_STATE; + } + + if (!s_stream_mcc_valid) { + ESP_LOGE(ENCODE_TASK_TAG, "No M24 stream config"); + return ESP_ERR_INVALID_STATE; + } + + s_stream_run = true; + if (xTaskCreate(encode_task_stream_task, "a2dp_aac", A2DP_SRC_STREAM_TASK_STACK, NULL, + A2DP_SRC_STREAM_TASK_PRIO, &s_stream_task_hdl) != pdPASS) { + ESP_LOGE(ENCODE_TASK_TAG, "stream task create failed"); + s_stream_run = false; + s_stream_task_hdl = NULL; + return ESP_FAIL; + } + + return ESP_OK; +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.h b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.h new file mode 100644 index 00000000000..173bb744cc4 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/decode_task.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "esp_a2dp_api.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Set A2DP connection handle and audio MTU used for sending. + * + * @param[in] conn_hdl Connection handle + * @param[in] audio_mtu Audio MTU + * + * @note Call after connection is established and before encode_task_launch(). + * Cleared internally when encode_task_stop() completes. + */ +void encode_task_set_a2dp_link(esp_a2d_conn_hdl_t conn_hdl, uint16_t audio_mtu); + +/** + * @brief Save negotiated stream codec configuration (AAC only). + * + * @param[in] mcc Media codec configuration from ESP_A2D_AUDIO_CFG_EVT. + * Must be ESP_A2D_MCT_M24; NULL or other types clear the stored config. + */ +void encode_task_set_stream_config(const esp_a2d_mcc_t *mcc); + +/** + * @brief Check whether a valid M24 stream configuration has been stored. + * + * @return true if encode_task_set_stream_config() was called with ESP_A2D_MCT_M24 + */ +bool encode_task_has_stream_config(void); + +/** + * @brief Start the AAC encode/send task. + * + * @note Spawns task "a2dp_aac", which loops: pace frame → PCM sine → AAC encode → + * A2DP send. Requires prior encode_task_set_stream_config() and encode_task_set_a2dp_link(). + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_STATE if the task is already running or no M24 config + * - ESP_FAIL if task creation or encoder open fails + */ +esp_err_t encode_task_launch(void); + +/** + * @brief Stop the AAC stream task and release encoder resources. + */ +void encode_task_stop(void); + +/** + * @brief Query whether the AAC stream task is active. + * + * @return true while the internal "a2dp_aac" task is running + */ +bool encode_task_is_running(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/idf_component.yml b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/idf_component.yml new file mode 100644 index 00000000000..a2fa8b63fef --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + espressif/esp_audio_codec: ^2.4.0 diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/main.c new file mode 100644 index 00000000000..11a073e140a --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/main/main.c @@ -0,0 +1,948 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" +#include "bt_app_core.h" +#include "decode_task.h" + +/* log tags */ +#define BT_AV_TAG "BT_AV" +#define BT_RC_CT_TAG "RC_CT" + +/* device name */ +#define LOCAL_DEVICE_NAME "ESP_A2DP_SRC" + +/* AVRCP used transaction label */ +#define APP_RC_CT_TL_GET_CAPS (0) +#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1) + +enum { + BT_APP_STACK_UP_EVT = 0x0000, /* event for stack up */ + BT_APP_HEART_BEAT_EVT = 0xff00, /* event for heart beat */ +}; + +/* A2DP global states */ +enum { + APP_AV_STATE_IDLE, + APP_AV_STATE_DISCOVERING, + APP_AV_STATE_DISCOVERED, + APP_AV_STATE_UNCONNECTED, + APP_AV_STATE_CONNECTING, + APP_AV_STATE_CONNECTED, + APP_AV_STATE_DISCONNECTING, +}; + +/* sub states of APP_AV_STATE_CONNECTED */ +enum { + APP_AV_MEDIA_STATE_IDLE, + APP_AV_MEDIA_STATE_STARTING, + APP_AV_MEDIA_STATE_STARTED, + APP_AV_MEDIA_STATE_STOPPING, +}; + +/********************************* + * STATIC FUNCTION DECLARATIONS + ********************************/ + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/* avrc controller event handler */ +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); + +/* GAP callback function */ +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param); + +/* callback function for A2DP source */ +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/* callback function for AVRCP controller */ +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +/* handler for heart beat timer */ +static void bt_app_a2d_heart_beat(TimerHandle_t arg); + +/* A2DP application state machine */ +static void bt_app_av_sm_hdlr(uint16_t event, void *param); + +/* utils for transfer BLuetooth Deveice Address into string form */ +static char *bda2str(esp_bd_addr_t bda, char *str, size_t size); + +/* A2DP application state machine handler for each state */ +static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param); +static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param); +static void bt_app_av_state_connected_hdlr(uint16_t event, void *param); +static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param); + +/********************************* + * STATIC VARIABLE DEFINITIONS + ********************************/ + +static esp_bd_addr_t s_peer_bda = {0}; /* Bluetooth Device Address of peer device*/ +static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; /* Bluetooth Device Name of peer device*/ +static int s_a2d_state = APP_AV_STATE_IDLE; /* A2DP global state */ +static int s_media_state = APP_AV_MEDIA_STATE_IDLE; /* sub states of APP_AV_STATE_CONNECTED */ +static int s_intv_cnt = 0; /* count of heart beat intervals */ +static int s_connecting_intv = 0; /* count of heart beat intervals for connecting */ +static uint32_t s_pkt_cnt = 0; /* count of packets */ +static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; /* AVRC target notification event capability bit mask */ +static TimerHandle_t s_tmr; /* handle of heart beat timer */ +static esp_a2d_conn_hdl_t s_a2d_conn_hndl = 0; +static uint16_t s_a2d_audio_mtu; + +static const char remote_device_name[] = CONFIG_EXAMPLE_PEER_DEVICE_NAME; + +/********************************* + * STATIC FUNCTION DEFINITIONS + ********************************/ + +static char *bda2str(esp_bd_addr_t bda, char *str, size_t size) +{ + if (bda == NULL || str == NULL || size < 18) { + return NULL; + } + + sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + return str; +} + +static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len) +{ + uint8_t *rmt_bdname = NULL; + uint8_t rmt_bdname_len = 0; + + if (!eir) { + return false; + } + + /* get complete or short local name from eir data */ + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len); + if (!rmt_bdname) { + rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len); + } + + if (rmt_bdname) { + if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) { + rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN; + } + + if (bdname) { + memcpy(bdname, rmt_bdname, rmt_bdname_len); + bdname[rmt_bdname_len] = '\0'; + } + if (bdname_len) { + *bdname_len = rmt_bdname_len; + } + return true; + } + + return false; +} + +static void filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param) +{ + char bda_str[18]; + uint32_t cod = 0; /* class of device */ + int32_t rssi = -129; /* invalid value */ + uint8_t *eir = NULL; + esp_bt_gap_dev_prop_t *p; + + /* handle the discovery results */ + ESP_LOGI(BT_AV_TAG, "Scanned device: %s", bda2str(param->disc_res.bda, bda_str, 18)); + for (int i = 0; i < param->disc_res.num_prop; i++) { + p = param->disc_res.prop + i; + switch (p->type) { + case ESP_BT_GAP_DEV_PROP_COD: + cod = *(uint32_t *)(p->val); + ESP_LOGI(BT_AV_TAG, "--Class of Device: 0x%"PRIx32, cod); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + rssi = *(int8_t *)(p->val); + ESP_LOGI(BT_AV_TAG, "--RSSI: %"PRId32, rssi); + break; + case ESP_BT_GAP_DEV_PROP_EIR: + eir = (uint8_t *)(p->val); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: + default: + break; + } + } + + /* search for device with MAJOR service class as "rendering" in COD */ + if (!esp_bt_gap_is_valid_cod(cod) || + !(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) { + return; + } + + /* search for target device in its Extended Inqury Response */ + if (eir) { + get_name_from_eir(eir, s_peer_bdname, NULL); + if (strcmp((char *)s_peer_bdname, remote_device_name) == 0) { + ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname); + s_a2d_state = APP_AV_STATE_DISCOVERED; + memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); + ESP_LOGI(BT_AV_TAG, "Cancel device discovery ..."); + esp_bt_gap_cancel_discovery(); + } + } +} + +static void bt_app_encode_stream_stop(void) +{ + if (encode_task_is_running()) { + ESP_LOGI(BT_AV_TAG, "stopping encode stream task"); + encode_task_stop(); + } +} + +static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + /* when device discovered a result, this event comes */ + case ESP_BT_GAP_DISC_RES_EVT: { + if (s_a2d_state == APP_AV_STATE_DISCOVERING) { + filter_inquiry_scan_result(param); + } + break; + } + /* when discovery state changed, this event comes */ + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { + if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { + if (s_a2d_state == APP_AV_STATE_DISCOVERED) { + s_a2d_state = APP_AV_STATE_CONNECTING; + ESP_LOGI(BT_AV_TAG, "Device discovery stopped."); + ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname); + /* connect source to peer device specified by Bluetooth Device Address */ + esp_a2d_source_connect(s_peer_bda); + } else { + /* not discovered, continue to discover */ + ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + } else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { + ESP_LOGI(BT_AV_TAG, "Discovery started."); + } + break; + } + /* when authentication completed, this event comes */ + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name); + ESP_LOG_BUFFER_HEX(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(BT_AV_TAG, "authentication failed, status: %d", param->auth_cmpl.stat); + } + break; + } + /* when Legacy Pairing pin code requested, this event comes */ + case ESP_BT_GAP_PIN_REQ_EVT: { + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit: %d", param->pin_req.min_16_digit); + if (param->pin_req.min_16_digit) { + ESP_LOGI(BT_AV_TAG, "Input pin code: 0000 0000 0000 0000"); + esp_bt_pin_code_t pin_code = {0}; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code); + } else { + ESP_LOGI(BT_AV_TAG, "Input pin code: 1234"); + esp_bt_pin_code_t pin_code; + pin_code[0] = '1'; + pin_code[1] = '2'; + pin_code[2] = '3'; + pin_code[3] = '4'; + esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code); + } + break; + } + +#if (CONFIG_EXAMPLE_SSP_ENABLED == true) + /* when Security Simple Pairing user confirmation requested, this event comes */ + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %06"PRIu32, param->cfm_req.num_val); + esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true); + break; + /* when Security Simple Pairing passkey notified, this event comes */ + case ESP_BT_GAP_KEY_NOTIF_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey: %06"PRIu32, param->key_notif.passkey); + break; + /* when Security Simple Pairing passkey requested, this event comes */ + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + /* when GAP mode changed, this event comes */ + case ESP_BT_GAP_MODE_CHG_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_MODE_CHG_EVT mode: %d", param->mode_chg.mode); + break; + case ESP_BT_GAP_GET_DEV_NAME_CMPL_EVT: + if (param->get_dev_name_cmpl.status == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_GET_DEV_NAME_CMPL_EVT device name: %s", param->get_dev_name_cmpl.name); + } else { + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_GET_DEV_NAME_CMPL_EVT failed, state: %d", param->get_dev_name_cmpl.status); + } + break; + /* other */ + default: { + ESP_LOGI(BT_AV_TAG, "event: %d", event); + break; + } + } + + return; +} + +static void bt_app_register_a2dp_src_seps(void) +{ + esp_a2d_mcc_t mcc_aac = {0}; + mcc_aac.type = ESP_A2D_MCT_M24; + mcc_aac.cie.m24_info.drc = ESP_A2D_M24_CIE_DRC_NS; + mcc_aac.cie.m24_info.obj_type = ESP_A2D_M24_CIE_OBJ_TYPE_2_AAC_LC | + ESP_A2D_M24_CIE_OBJ_TYPE_4_AAC_LC; + mcc_aac.cie.m24_info.samp_freq1 = ESP_A2D_M24_CIE_SF1_8K | + ESP_A2D_M24_CIE_SF1_11K | + ESP_A2D_M24_CIE_SF1_12K | + ESP_A2D_M24_CIE_SF1_16K | + ESP_A2D_M24_CIE_SF1_22K | + ESP_A2D_M24_CIE_SF1_24K | + ESP_A2D_M24_CIE_SF1_32K | + ESP_A2D_M24_CIE_SF1_44K; + mcc_aac.cie.m24_info.samp_freq2 = ESP_A2D_M24_CIE_SF2_48K | + ESP_A2D_M24_CIE_SF2_64K | + ESP_A2D_M24_CIE_SF2_88K | + ESP_A2D_M24_CIE_SF2_96K; + mcc_aac.cie.m24_info.ch = ESP_A2D_M24_CIE_CH_1 | + ESP_A2D_M24_CIE_CH_2; + mcc_aac.cie.m24_info.vbr = ESP_A2D_M24_CIE_VBR_SUPPORT; + /* bit rate: 160000 */ + mcc_aac.cie.m24_info.br1 = 0x02 & ESP_A2D_M24_CIE_BR1_MSK; + mcc_aac.cie.m24_info.br2 = 0x71 & ESP_A2D_M24_CIE_BR2_MSK; + mcc_aac.cie.m24_info.br3 = 0x00 & ESP_A2D_M24_CIE_BR3_MSK; + esp_a2d_source_register_stream_endpoint(0, &mcc_aac); + + esp_a2d_mcc_t mcc_sbc = {0}; + mcc_sbc.type = ESP_A2D_MCT_SBC; + mcc_sbc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_16K | + ESP_A2D_SBC_CIE_SF_32K | + ESP_A2D_SBC_CIE_SF_44K | + ESP_A2D_SBC_CIE_SF_48K; + mcc_sbc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO | + ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | + ESP_A2D_SBC_CIE_CH_MODE_STEREO | + ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO; + mcc_sbc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_4 | + ESP_A2D_SBC_CIE_BLOCK_LEN_8 | + ESP_A2D_SBC_CIE_BLOCK_LEN_12 | + ESP_A2D_SBC_CIE_BLOCK_LEN_16; + mcc_sbc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + mcc_sbc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_SNR | ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + mcc_sbc.cie.sbc_info.min_bitpool = 2; + mcc_sbc.cie.sbc_info.max_bitpool = 250; + esp_a2d_source_register_stream_endpoint(1, &mcc_sbc); +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s event: %d", __func__, event); + + switch (event) { + /* when stack up worked, this event comes */ + case BT_APP_STACK_UP_EVT: { + char *dev_name = LOCAL_DEVICE_NAME; + esp_bt_gap_set_device_name(dev_name); + esp_bt_gap_register_callback(bt_app_gap_cb); + + esp_avrc_ct_init(); + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + ESP_ERROR_CHECK(esp_avrc_tg_set_rn_evt_cap(&evt_set)); + + esp_a2d_source_init(); + esp_a2d_register_callback(&bt_app_a2d_cb); + + bt_app_register_a2dp_src_seps(); + + /* Avoid the state error of s_a2d_state caused by the connection initiated by the peer device. */ + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + esp_bt_gap_get_device_name(); + + ESP_LOGI(BT_AV_TAG, "Starting device discovery..."); + s_a2d_state = APP_AV_STATE_DISCOVERING; + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + + /* create and start heart beat timer */ + do { + int tmr_id = 0; + s_tmr = xTimerCreate("connTmr", (10000 / portTICK_PERIOD_MS), + pdTRUE, (void *) &tmr_id, bt_app_a2d_heart_beat); + xTimerStart(s_tmr, portMAX_DELAY); + } while (0); + break; + } + /* other */ + default: { + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } + } +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL, NULL); +} + +static void bt_app_a2d_heart_beat(TimerHandle_t arg) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL, NULL); +} + +static void bt_app_av_sm_hdlr(uint16_t event, void *param) +{ + ESP_LOGI(BT_AV_TAG, "%s state: %d, event: 0x%x", __func__, s_a2d_state, event); + + /* select handler according to different states */ + switch (s_a2d_state) { + case APP_AV_STATE_DISCOVERING: + case APP_AV_STATE_DISCOVERED: + break; + case APP_AV_STATE_UNCONNECTED: + bt_app_av_state_unconnected_hdlr(event, param); + break; + case APP_AV_STATE_CONNECTING: + bt_app_av_state_connecting_hdlr(event, param); + break; + case APP_AV_STATE_CONNECTED: + bt_app_av_state_connected_hdlr(event, param); + break; + case APP_AV_STATE_DISCONNECTING: + bt_app_av_state_disconnecting_hdlr(event, param); + break; + default: + ESP_LOGE(BT_AV_TAG, "%s invalid state: %d", __func__, s_a2d_state); + break; + } +} + +static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + /* handle the events of interest in unconnected state */ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + break; + case BT_APP_HEART_BEAT_EVT: { + uint8_t *bda = s_peer_bda; + ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x", + bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + esp_a2d_source_connect(s_peer_bda); + s_a2d_state = APP_AV_STATE_CONNECTING; + s_connecting_intv = 0; + break; + } + case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value); + break; + } + default: { + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } + } +} + +static void bt_app_av_state_connecting_hdlr(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + + /* handle the events of interest in connecting state */ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp connected"); + s_a2d_state = APP_AV_STATE_CONNECTED; + s_media_state = APP_AV_MEDIA_STATE_IDLE; + s_a2d_conn_hndl = a2d->conn_stat.conn_hdl; + s_a2d_audio_mtu = a2d->conn_stat.audio_mtu; + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + bt_app_encode_stream_stop(); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + s_intv_cnt = 0; + s_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + break; + case ESP_A2D_AUDIO_CFG_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; + if (p_mcc == NULL) { + ESP_LOGE(BT_AV_TAG, "p_mcc null"); + break; + } + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); + if (p_mcc->type == ESP_A2D_MCT_M24) { + ESP_LOGI(BT_AV_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + p_mcc->cie.m24_info.drc, + p_mcc->cie.m24_info.obj_type, + p_mcc->cie.m24_info.samp_freq1, + p_mcc->cie.m24_info.samp_freq2, + p_mcc->cie.m24_info.ch, + p_mcc->cie.m24_info.vbr, + p_mcc->cie.m24_info.br1, + p_mcc->cie.m24_info.br2, + p_mcc->cie.m24_info.br3); + encode_task_set_stream_config(p_mcc); + } + break; + } + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + break; + case BT_APP_HEART_BEAT_EVT: + /** + * Switch state to APP_AV_STATE_UNCONNECTED + * when connecting lasts more than 2 heart beat intervals. + */ + if (++s_connecting_intv >= 2) { + s_a2d_state = APP_AV_STATE_UNCONNECTED; + s_connecting_intv = 0; + } + break; + case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value); + break; + } + case ESP_A2D_REPORT_SNK_ALL_CODEC_CAPS_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + uint8_t n = a2d->a2d_report_snk_all_codec_caps_stat.sep_num; + ESP_LOGI(BT_AV_TAG, "%s all sink caps conn_hdl=%u sep_num=%u", __func__, + (unsigned)a2d->a2d_report_snk_all_codec_caps_stat.conn_hdl, (unsigned)n); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } +} + +static void bt_app_av_media_proc(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + + switch (s_media_state) { + case APP_AV_MEDIA_STATE_IDLE: { + if (event == BT_APP_HEART_BEAT_EVT) { + ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); + } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START); + s_media_state = APP_AV_MEDIA_STATE_STARTING; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media start successfully."); + s_intv_cnt = 0; + s_media_state = APP_AV_MEDIA_STATE_STARTED; + encode_task_set_a2dp_link(s_a2d_conn_hndl, s_a2d_audio_mtu); + if (!encode_task_has_stream_config()) { + ESP_LOGE(BT_AV_TAG, "No negotiated AAC (M24) config, skip encode launch"); + } else if (encode_task_launch() != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "encode_task_launch failed"); + } + } else { + /* not started successfully, transfer to idle state */ + ESP_LOGI(BT_AV_TAG, "a2dp media start failed."); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTED: { + if (event == BT_APP_HEART_BEAT_EVT) { + /* stop media after 10 heart beat intervals */ + if (++s_intv_cnt >= 10) { + ESP_LOGI(BT_AV_TAG, "a2dp media suspending..."); + bt_app_encode_stream_stop(); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND); + s_media_state = APP_AV_MEDIA_STATE_STOPPING; + s_intv_cnt = 0; + } + } + break; + } + case APP_AV_MEDIA_STATE_STOPPING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_SUSPEND && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media suspend successfully, disconnecting..."); + bt_app_encode_stream_stop(); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + esp_a2d_source_disconnect(s_peer_bda); + s_a2d_state = APP_AV_STATE_DISCONNECTING; + } else { + ESP_LOGI(BT_AV_TAG, "a2dp media suspending..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_SUSPEND); + } + } + break; + } + default: { + break; + } + } +} + +static void bt_app_av_state_connected_hdlr(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + + /* handle the events of interest in connected state */ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp disconnected"); + bt_app_encode_stream_stop(); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + s_intv_cnt = 0; + s_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } else if (ESP_A2D_AUDIO_STATE_SUSPEND == a2d->audio_stat.state) { + ESP_LOGI(BT_AV_TAG, "a2dp audio suspended"); + bt_app_encode_stream_stop(); + if (s_media_state == APP_AV_MEDIA_STATE_STARTED || + s_media_state == APP_AV_MEDIA_STATE_STOPPING) { + s_media_state = APP_AV_MEDIA_STATE_IDLE; + s_intv_cnt = 0; + } + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + esp_a2d_mcc_t *p_mcc = &a2d->audio_cfg.mcc; + if (p_mcc == NULL) { + ESP_LOGE(BT_AV_TAG, "p_mcc null"); + break; + } + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type: %d", p_mcc->type); + if (p_mcc->type == ESP_A2D_MCT_M24) { + ESP_LOGI(BT_AV_TAG, "Configure audio player: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + p_mcc->cie.m24_info.drc, + p_mcc->cie.m24_info.obj_type, + p_mcc->cie.m24_info.samp_freq1, + p_mcc->cie.m24_info.samp_freq2, + p_mcc->cie.m24_info.ch, + p_mcc->cie.m24_info.vbr, + p_mcc->cie.m24_info.br1, + p_mcc->cie.m24_info.br2, + p_mcc->cie.m24_info.br3); + encode_task_set_stream_config(p_mcc); + } + break; + } + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + case BT_APP_HEART_BEAT_EVT: { + bt_app_av_media_proc(event, param); + break; + } + case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + ESP_LOGI(BT_AV_TAG, "%s, delay value: %u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value); + break; + } + case ESP_A2D_REPORT_SNK_CODEC_CAPS_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + esp_a2d_mcc_t *sink_mcc = &a2d->a2d_report_snk_codec_caps_stat.mcc; + ESP_LOGI(BT_AV_TAG, "sink codec type: %d", sink_mcc->type); + if (sink_mcc->type == ESP_A2D_MCT_SBC) { + ESP_LOGI(BT_AV_TAG, "sink codec capabilities: 0x%x-0x%x-0x%x-0x%x-0x%x-%d-%d", + sink_mcc->cie.sbc_info.samp_freq, + sink_mcc->cie.sbc_info.ch_mode, + sink_mcc->cie.sbc_info.block_len, + sink_mcc->cie.sbc_info.num_subbands, + sink_mcc->cie.sbc_info.alloc_mthd, + sink_mcc->cie.sbc_info.min_bitpool, + sink_mcc->cie.sbc_info.max_bitpool); + } else if (sink_mcc->type == ESP_A2D_MCT_M24) { + ESP_LOGI(BT_AV_TAG, "sink codec capabilities: 0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x-0x%x", + sink_mcc->cie.m24_info.obj_type, + sink_mcc->cie.m24_info.drc, + sink_mcc->cie.m24_info.samp_freq1, + sink_mcc->cie.m24_info.samp_freq2, + sink_mcc->cie.m24_info.ch, + sink_mcc->cie.m24_info.vbr, + sink_mcc->cie.m24_info.br1, + sink_mcc->cie.m24_info.br2, + sink_mcc->cie.m24_info.br3); + } + break; + } + default: { + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } + } +} + +static void bt_app_av_state_disconnecting_hdlr(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + + /* handle the events of interest in disconnecing state */ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp disconnected"); + bt_app_encode_stream_stop(); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + s_intv_cnt = 0; + s_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + case BT_APP_HEART_BEAT_EVT: + break; + case ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + ESP_LOGI(BT_AV_TAG, "%s, delay value: 0x%u * 1/10 ms", __func__, a2d->a2d_report_delay_value_stat.delay_value); + break; + } + default: { + ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); + break; + } + } +} + +/* callback function for AVRCP controller */ +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: + case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: + case ESP_AVRC_CT_PROF_STATE_EVT: { + bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL, NULL); + break; + } + default: { + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } + } +} + +static void bt_av_volume_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_VOLUME_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0); + } +} + +void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + /* when volume changed locally on target, this event comes */ + case ESP_AVRC_RN_VOLUME_CHANGE: { + ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume); + ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5); + esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5); + bt_av_volume_changed(); + break; + } + /* other */ + default: + break; + } +} + +/* AVRC controller event handler */ +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event); + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); + + switch (event) { + /* when connection state changed, this event comes */ + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state event: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); + } else { + s_avrc_peer_rn_cap.bits = 0; + } + break; + } + /* when passthrough responded, this event comes */ + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough response: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code, + rc->psth_rsp.key_state, rc->psth_rsp.rsp_code); + break; + } + /* when notification changed, this event comes */ + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + /* when indicate feature of remote device, this event comes */ + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + /* when get supported notification events capability of peer device, this event comes */ + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; + + bt_av_volume_changed(); + break; + } + /* when set absolute volume responded, this event comes */ + case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume response: volume %d", rc->set_volume_rsp.volume); + break; + } + /* when avrcp controller init or deinit completed, this event comes */ + case ESP_AVRC_CT_PROF_STATE_EVT: { + if (ESP_AVRC_INIT_SUCCESS == rc->avrc_ct_init_stat.state) { + ESP_LOGI(BT_RC_CT_TAG, "AVRCP CT STATE: Init Complete"); + } else if (ESP_AVRC_DEINIT_SUCCESS == rc->avrc_ct_init_stat.state) { + ESP_LOGI(BT_RC_CT_TAG, "AVRCP CT STATE: Deinit Complete"); + } else { + ESP_LOGE(BT_RC_CT_TAG, "AVRCP CT STATE error: %d", rc->avrc_ct_init_stat.state); + } + break; + } + /* other */ + default: { + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event); + break; + } + } +} + +/********************************* + * MAIN ENTRY POINT + ********************************/ + +void app_main(void) +{ + char bda_str[18] = {0}; + /* initialize NVS — it is used to store PHY calibration data */ + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + + /* + * This example only uses the functions of Classical Bluetooth. + * So release the controller memory for Bluetooth Low Energy. + */ + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize controller failed", __func__); + return; + } + if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable controller failed", __func__); + return; + } + + esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT(); +#if (CONFIG_EXAMPLE_SSP_ENABLED == false) + bluedroid_cfg.ssp_en = false; +#endif + if ((ret = esp_bluedroid_init_with_cfg(&bluedroid_cfg)) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(ret)); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed", __func__); + return; + } + +#if (CONFIG_EXAMPLE_SSP_ENABLED == true) + /* set default parameters for Secure Simple Pairing */ + esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE; + esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); +#endif + + /* + * Set default parameters for Legacy Pairing + * Use variable pin, input pin code when pairing + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE; + esp_bt_pin_code_t pin_code; + esp_bt_gap_set_pin(pin_type, 0, pin_code); + + ESP_LOGI(BT_AV_TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(), bda_str, sizeof(bda_str))); + bt_app_task_start_up(); + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_STACK_UP_EVT, NULL, 0, NULL, NULL); +} diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.ci.test b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.ci.test new file mode 100644 index 00000000000..800d21f529a --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.ci.test @@ -0,0 +1 @@ +CONFIG_EXAMPLE_PEER_DEVICE_NAME="${CI_PIPELINE_ID}_A2DP_SINK_AAC" diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults new file mode 100644 index 00000000000..e2a9f32eeaa --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults @@ -0,0 +1,13 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_BLE_ENABLED=n +CONFIG_BT_A2DP_USE_EXTERNAL_CODEC=y +CONFIG_BT_A2DP_SEP_NUM_MAX=2 +CONFIG_BT_A2DP_CODEC_AAC_ENABLED=y diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults.esp32s31 b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults.esp32s31 new file mode 100644 index 00000000000..1686559de40 --- /dev/null +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source_aac/sdkconfig.defaults.esp32s31 @@ -0,0 +1 @@ +CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y