Merge branch 'bugfix/a2dp_aac_br_v6.1' into 'release/v6.1'

fix(bt): Fix some bugs of A2DP AAC (v6.1)

See merge request espressif/esp-idf!52186
This commit is contained in:
Wang Meng Yang
2026-09-02 17:45:20 +08:00
9 changed files with 420 additions and 44 deletions

View File

@@ -257,6 +257,7 @@ typedef enum {
ESP_A2D_SEP_REG_FAIL, /*!< A2DP stream endpoint register generic fail */
ESP_A2D_SEP_REG_UNSUPPORTED, /*!< A2DP stream endpoint register fail, unsupported codec type or param */
ESP_A2D_SEP_REG_INVALID_STATE, /*!< A2DP stream endpoint register fail, invalid state */
ESP_A2D_SEP_REG_SBC_REQUIRED, /*!< A2DP stream endpoint register fail, at least one SBC SEP must remain */
} esp_a2d_sep_reg_state_t;
/**
@@ -511,7 +512,11 @@ esp_err_t esp_a2d_sink_init(void);
* SEP index repeatedly will overwrite the old one.
* It is necessary to set BT_A2DP_USE_EXTERNAL_CODEC to y.
*
* @note The SEID determines the priority of negotiating the configuration with the peer for initiator.
* @note After A2DP sink init, every SEID is pre-registered with the mandatory default SBC SEP.
* Calling this API overwrites that SEID. A2DP requires at least one SBC SEP; registering a
* non-SBC codec that would overwrite the last SBC SEP fails with ESP_A2D_SEP_REG_SBC_REQUIRED.
* Therefore using AAC (or other non-SBC codecs) requires ESP_A2D_MAX_SEPS >= 2.
* The SEID determines the priority of negotiating the configuration with the peer for initiator.
* The lower the SEID, the higher the priority of the codec capability.
*
* @param[in] seid: local SEP identifier, start from 0, less than ESP_A2D_MAX_SEPS
@@ -667,7 +672,11 @@ esp_err_t esp_a2d_source_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, const esp_a2d
* SEP index repeatedly will overwrite the old one.
* It is necessary to set BT_A2DP_USE_EXTERNAL_CODEC to y.
*
* @note The SEID determines the priority of negotiating the configuration with the peer for initiator.
* @note After A2DP source init, every SEID is pre-registered with the mandatory default SBC SEP.
* Calling this API overwrites that SEID. A2DP requires at least one SBC SEP; registering a
* non-SBC codec that would overwrite the last SBC SEP fails with ESP_A2D_SEP_REG_SBC_REQUIRED.
* Therefore using AAC (or other non-SBC codecs) requires ESP_A2D_MAX_SEPS >= 2.
* The SEID determines the priority of negotiating the configuration with the peer for initiator.
* The lower the SEID, the higher the priority of the codec capability.
*
* @param[in] seid: local SEP identifier, start from 0, less than ESP_A2D_MAX_SEPS

View File

@@ -53,6 +53,18 @@ void bta_av_m24_set_br_info(UINT8 *codec_info, UINT32 bit_rate)
codec_info[BTA_AV_M24_BR3_OFF] = (UINT8)(bit_rate & A2D_M24_IE_BR3_MSK);
}
UINT32 bta_av_m24_br_min(UINT32 br1, UINT32 br2)
{
/* A2DP MPEG-2/4 AAC: bit rate 0 means "not known", not an upper bound of 0. */
if (br1 == 0) {
return br2;
}
if (br2 == 0) {
return br1;
}
return (br1 < br2) ? br1 : br2;
}
UINT8 bta_av_m24_cap_matches_cap(UINT8 *p_cap1, UINT8 *p_cap2)
{
tA2D_M24_CIE cap1;
@@ -62,8 +74,6 @@ UINT8 bta_av_m24_cap_matches_cap(UINT8 *p_cap1, UINT8 *p_cap2)
UINT8 cap2_sf1;
UINT8 cap1_sf2;
UINT8 cap2_sf2;
UINT32 cap1_br;
UINT32 cap2_br;
st = A2D_ParsM24Info(&cap1, p_cap1, TRUE);
if (st != A2D_SUCCESS) {
@@ -90,13 +100,6 @@ UINT8 bta_av_m24_cap_matches_cap(UINT8 *p_cap1, UINT8 *p_cap2)
return A2D_NS_CHANNEL;
}
cap1_br = bta_av_m24_br(&cap1);
cap2_br = bta_av_m24_br(&cap2);
if (!cap1_br || !cap2_br) {
return A2D_NS_BIT_RATE;
}
return A2D_SUCCESS;
}
@@ -147,7 +150,8 @@ UINT8 bta_av_m24_cfg_in_external_codec_cap(UINT8 *p_cfg, UINT8 *p_cap)
cfg_br = bta_av_m24_br(&cfg);
cap_br = bta_av_m24_br(&cap);
if (cfg_br > cap_br) {
/* Cap 0 = unknown/unlimited; cfg 0 = bit rate not known in SetConfiguration. */
if (cap_br != 0 && cfg_br > cap_br) {
return A2D_NS_BIT_RATE;
}
@@ -158,7 +162,6 @@ tA2D_STATUS bta_av_m24_pick_pref_from_src_cap(const tA2D_M24_CIE *src_cap, tA2D_
{
UINT8 i;
UINT32 src_br_max;
UINT32 p_pref_br;
static const UINT8 sf2_order[] = {
A2D_M24_IE_SAMP_FREQ2_96,
A2D_M24_IE_SAMP_FREQ2_88,
@@ -191,12 +194,13 @@ tA2D_STATUS bta_av_m24_pick_pref_from_src_cap(const tA2D_M24_CIE *src_cap, tA2D_
}
}
if ((p_pref->drc & A2D_M24_IE_DRC_MSK) && !(src_cap->drc & A2D_M24_IE_DRC_MSK)) {
p_pref->drc = A2D_M24_IE_DRC_NS;
}
/* DRC: follow registered SEP capability (MPEG-2 AAC LC has no DRC) */
if ((p_pref->obj_type & A2D_M24_IE_OBJ_TYPE_MSK) == A2D_M24_IE_OBJ_TYPE_2_AAC_LC) {
p_pref->drc = A2D_M24_IE_DRC_NS;
} else if (src_cap->drc & A2D_M24_IE_DRC_MSK) {
p_pref->drc = A2D_M24_IE_DRC_SUPPORT;
} else {
p_pref->drc = A2D_M24_IE_DRC_NS;
}
if (!((p_pref->samp_freq1 & src_cap->samp_freq1) & A2D_M24_IE_SAMP_FREQ1_MSK) &&
@@ -242,12 +246,7 @@ got_sf:
}
src_br_max = bta_av_m24_br(src_cap);
p_pref_br = bta_av_m24_br(p_pref);
if (p_pref_br > src_br_max) {
p_pref_br = src_br_max;
}
bta_av_m24_set_br(p_pref, p_pref_br, (UINT8)(p_pref->vbr & A2D_M24_IE_VBR_MSK));
bta_av_m24_set_br(p_pref, src_br_max, (UINT8)(p_pref->vbr & A2D_M24_IE_VBR_MSK));
return A2D_SUCCESS;
}

View File

@@ -542,10 +542,8 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data)
tAVDT_CS cs;
char *p_service_name;
tBTA_UTL_COD cod;
#if (BTA_AV_EXT_CODEC == FALSE)
tBTA_AV_CODEC codec_type;
UINT8 index = 0;
#endif
char p_avk_service_name[BTA_SERVICE_NAME_LEN + 1];
BCM_STRLCPY_S(p_avk_service_name, BTIF_AVK_SERVICE_NAME, BTA_SERVICE_NAME_LEN + 1);
@@ -667,10 +665,25 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data)
/* keep the configuration in the stream control block */
memcpy(&p_scb->cfg, &cs.cfg, sizeof(tAVDT_CFG));
#if (BTA_AV_EXT_CODEC == FALSE)
while (index < BTA_AV_MAX_SEPS &&
(p_scb->p_cos->init)(index, &codec_type, cs.cfg.codec_info,
&cs.cfg.num_protect, cs.cfg.protect_info, p_data->api_reg.tsep) == TRUE) {
/*
* Create local SEPs:
* - Internal codec: init() supplies SBC (typically one SEP).
* - External codec: A2DP mandates SBC, so pre-fill every SEID with
* the default SBC SEP; app register_stream_endpoint() may overwrite.
*/
while (index < BTA_AV_MAX_SEPS) {
#if (BTA_AV_EXT_CODEC == TRUE)
if (bta_av_co_audio_build_sbc_default(p_data->api_reg.tsep, &codec_type,
cs.cfg.codec_info) != TRUE) {
APPL_TRACE_ERROR("failed to build default SBC SEP for seid %d", index);
break;
}
#endif
if ((p_scb->p_cos->init)(index, &codec_type, cs.cfg.codec_info,
&cs.cfg.num_protect, cs.cfg.protect_info,
p_data->api_reg.tsep) != TRUE) {
break;
}
#if (BTA_AV_SINK_INCLUDED == TRUE)
if (p_data->api_reg.tsep == AVDT_TSEP_SNK) {
@@ -698,7 +711,6 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data)
break;
}
}
#endif
if (!bta_av_cb.reg_audio) {
if (p_data->api_reg.tsep == AVDT_TSEP_SRC) {

View File

@@ -87,6 +87,19 @@ typedef struct {
extern BOOLEAN bta_av_co_audio_init(UINT8 seid, UINT8 *p_codec_type, UINT8 *p_codec_info,
UINT8 *p_num_protect, UINT8 *p_protect_info, UINT8 tsep);
/*******************************************************************************
**
** Function bta_av_co_audio_build_sbc_default
**
** Description Build the mandatory default SBC codec capability for the
** given SEP type (source or sink).
**
** Returns TRUE if built successfully, FALSE otherwise.
**
*******************************************************************************/
extern BOOLEAN bta_av_co_audio_build_sbc_default(UINT8 tsep, UINT8 *p_codec_type,
UINT8 *p_codec_info);
/*******************************************************************************
**
** Function bta_av_co_audio_disc_res

View File

@@ -41,6 +41,19 @@ extern UINT32 bta_av_m24_br_info(const UINT8 *codec_info);
*******************************************************************************/
extern void bta_av_m24_set_br_info(UINT8 *codec_info, UINT32 bit_rate);
/*******************************************************************************
**
** Function bta_av_m24_br_min
**
** Description Select the negotiated AAC bit rate from two values.
** Per A2DP, bit rate 0 means "not known" (no upper bound),
** so it is ignored when the other value is non-zero.
**
** Returns Negotiated bit rate (0 only if both inputs are 0).
**
*******************************************************************************/
extern UINT32 bta_av_m24_br_min(UINT32 br1, UINT32 br2);
/*******************************************************************************
**
** Function bta_av_m24_cap_matches_cap

View File

@@ -298,6 +298,37 @@ static void bta_av_co_report_peer_all_snk_codec_caps(tBTA_AV_HNDL hndl)
}
#endif /* BTC_AV_SRC_INCLUDED */
/*******************************************************************************
**
** Function bta_av_co_audio_build_sbc_default
**
** Description Build the mandatory default SBC codec capability for the
** given SEP type (source or sink).
**
** Returns TRUE if built successfully, FALSE otherwise.
**
*******************************************************************************/
BOOLEAN bta_av_co_audio_build_sbc_default(UINT8 tsep, UINT8 *p_codec_type, UINT8 *p_codec_info)
{
FUNC_TRACE();
if (p_codec_type == NULL || p_codec_info == NULL) {
return FALSE;
}
*p_codec_type = BTA_AV_CODEC_SBC;
if (tsep == AVDT_TSEP_SRC) {
return (A2D_BldSbcInfo(AVDT_MEDIA_AUDIO, (tA2D_SBC_CIE *)&bta_av_co_sbc_caps,
p_codec_info) == A2D_SUCCESS);
} else if (tsep == AVDT_TSEP_SNK) {
return (A2D_BldSbcInfo(AVDT_MEDIA_AUDIO, (tA2D_SBC_CIE *)&bta_av_co_sbc_sink_caps,
p_codec_info) == A2D_SUCCESS);
}
APPL_TRACE_WARNING("bta_av_co_audio_build_sbc_default invalid SEP type %d", tsep);
return FALSE;
}
/*******************************************************************************
**
** Function bta_av_co_audio_init
@@ -604,6 +635,133 @@ void bta_av_build_src_cfg (UINT8 *p_pref_cfg, UINT8 *p_src_cap)
}
}
/*******************************************************************************
**
** Function bta_av_co_audio_isect_src_caps
**
** Description Intersect the codec capabilities of the peer source with
** the capabilities of the currently selected local sink SEP.
** The result is a capability byte sequence that both sides
** support, so a configuration built from it never exceeds
** the local sink capabilities.
**
** Returns TRUE if a non-empty intersection was built, FALSE otherwise.
**
*******************************************************************************/
static BOOLEAN bta_av_co_audio_isect_src_caps(UINT8 *p_src_caps, UINT8 *p_isect_caps)
{
UINT8 *p_snk_caps;
UINT8 codec_type;
#if (BTC_AV_EXT_CODEC == FALSE)
UINT8 snk_caps_info[AVDT_CODEC_SIZE];
#endif
FUNC_TRACE();
if (p_src_caps == NULL || p_isect_caps == NULL) {
return FALSE;
}
#if (BTC_AV_EXT_CODEC == TRUE)
if (bta_av_co_cb.cur_seid >= BTA_AV_MAX_SEPS) {
APPL_TRACE_ERROR("isect_src_caps OOB cur_seid: %d", bta_av_co_cb.cur_seid);
return FALSE;
}
p_snk_caps = bta_av_co_cb.codec_caps[bta_av_co_cb.cur_seid].info;
#else
if (A2D_BldSbcInfo(AVDT_MEDIA_AUDIO, (tA2D_SBC_CIE *)&bta_av_co_sbc_sink_caps, snk_caps_info) != A2D_SUCCESS) {
APPL_TRACE_ERROR("isect_src_caps A2D_BldSbcInfo failed");
return FALSE;
}
p_snk_caps = snk_caps_info;
#endif
codec_type = p_src_caps[BTA_AV_CODEC_TYPE_IDX];
if (codec_type != p_snk_caps[BTA_AV_CODEC_TYPE_IDX]) {
APPL_TRACE_ERROR("isect_src_caps codec mismatch src: %d snk: %d",
codec_type, p_snk_caps[BTA_AV_CODEC_TYPE_IDX]);
return FALSE;
}
switch (codec_type) {
case A2D_MEDIA_CT_SBC: {
tA2D_SBC_CIE src_cie;
tA2D_SBC_CIE snk_cie;
tA2D_SBC_CIE isect_cie;
if ((A2D_ParsSbcInfo(&src_cie, p_src_caps, TRUE) != A2D_SUCCESS) ||
(A2D_ParsSbcInfo(&snk_cie, p_snk_caps, TRUE) != A2D_SUCCESS)) {
APPL_TRACE_ERROR("isect_src_caps A2D_ParsSbcInfo failed");
return FALSE;
}
memset(&isect_cie, 0, sizeof(tA2D_SBC_CIE));
isect_cie.samp_freq = src_cie.samp_freq & snk_cie.samp_freq & A2D_SBC_IE_SAMP_FREQ_MSK;
isect_cie.ch_mode = src_cie.ch_mode & snk_cie.ch_mode & A2D_SBC_IE_CH_MD_MSK;
isect_cie.block_len = src_cie.block_len & snk_cie.block_len & A2D_SBC_IE_BLOCKS_MSK;
isect_cie.num_subbands = src_cie.num_subbands & snk_cie.num_subbands & A2D_SBC_IE_SUBBAND_MSK;
isect_cie.alloc_mthd = src_cie.alloc_mthd & snk_cie.alloc_mthd & A2D_SBC_IE_ALLOC_MD_MSK;
if (!isect_cie.samp_freq || !isect_cie.ch_mode || !isect_cie.block_len ||
!isect_cie.num_subbands || !isect_cie.alloc_mthd) {
APPL_TRACE_ERROR("isect_src_caps empty SBC intersection");
return FALSE;
}
/* bitpool: the tightest boundaries accepted by both sides */
isect_cie.min_bitpool = BTA_AV_CO_MAX(src_cie.min_bitpool, snk_cie.min_bitpool);
isect_cie.max_bitpool = BTA_AV_CO_MIN(src_cie.max_bitpool, snk_cie.max_bitpool);
if (isect_cie.min_bitpool > isect_cie.max_bitpool) {
APPL_TRACE_ERROR("isect_src_caps SBC bitpool mismatch min: %d max: %d",
isect_cie.min_bitpool, isect_cie.max_bitpool);
return FALSE;
}
return (A2D_BldSbcInfo(AVDT_MEDIA_AUDIO, &isect_cie, p_isect_caps) == A2D_SUCCESS);
}
#if (BTC_AV_CODEC_AAC_INCLUDED == TRUE)
case A2D_MEDIA_CT_M24: {
tA2D_M24_CIE src_cie;
tA2D_M24_CIE snk_cie;
tA2D_M24_CIE isect_cie;
UINT32 isect_br;
if ((A2D_ParsM24Info(&src_cie, p_src_caps, TRUE) != A2D_SUCCESS) ||
(A2D_ParsM24Info(&snk_cie, p_snk_caps, TRUE) != A2D_SUCCESS)) {
APPL_TRACE_ERROR("isect_src_caps A2D_ParsM24Info failed");
return FALSE;
}
memset(&isect_cie, 0, sizeof(tA2D_M24_CIE));
isect_cie.obj_type = src_cie.obj_type & snk_cie.obj_type & A2D_M24_IE_OBJ_TYPE_MSK;
isect_cie.drc = src_cie.drc & snk_cie.drc & A2D_M24_IE_DRC_MSK;
isect_cie.samp_freq1 = src_cie.samp_freq1 & snk_cie.samp_freq1 & A2D_M24_IE_SAMP_FREQ1_MSK;
isect_cie.samp_freq2 = src_cie.samp_freq2 & snk_cie.samp_freq2 & A2D_M24_IE_SAMP_FREQ2_MSK;
isect_cie.ch = src_cie.ch & snk_cie.ch & A2D_M24_IE_CH_MSK;
isect_cie.vbr = src_cie.vbr & snk_cie.vbr & A2D_M24_IE_VBR_MSK;
if (!isect_cie.obj_type || !isect_cie.ch ||
(!isect_cie.samp_freq1 && !isect_cie.samp_freq2)) {
APPL_TRACE_ERROR("isect_src_caps empty M24 intersection");
return FALSE;
}
/* bit rate in capabilities is an upper bound, 0 means "not known" */
isect_br = bta_av_m24_br_min(bta_av_m24_br_info(p_src_caps), bta_av_m24_br_info(p_snk_caps));
isect_cie.br1 = (UINT8)((isect_br >> 16) & A2D_M24_IE_BR1_MSK);
isect_cie.br2 = (UINT8)((isect_br >> 8) & A2D_M24_IE_BR2_MSK);
isect_cie.br3 = (UINT8)(isect_br & A2D_M24_IE_BR3_MSK);
return (A2D_BldM24Info(AVDT_MEDIA_AUDIO, &isect_cie, p_isect_caps) == A2D_SUCCESS);
}
#endif
default:
APPL_TRACE_ERROR("isect_src_caps unsup_codec type: %d", codec_type);
return FALSE;
}
}
/*******************************************************************************
**
** Function bta_av_audio_sink_getconfig
@@ -626,11 +784,12 @@ UINT8 bta_av_audio_sink_getconfig(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type,
tBTA_AV_CO_PEER *p_peer;
tBTA_AV_CO_SINK *p_src;
UINT8 pref_cfg[AVDT_CODEC_SIZE];
UINT8 isect_caps[AVDT_CODEC_SIZE];
UINT8 index;
FUNC_TRACE();
APPL_TRACE_DEBUG("bta_av_audio_sink_getconfig handle:0x%x codec_type:%d seid:%d",
APPL_TRACE_DEBUG("sink_getconfig handle:0x%x codec_type:%d seid:%d",
hndl, codec_type, seid);
APPL_TRACE_DEBUG("num_protect:0x%02x protect_info:0x%02x%02x%02x",
*p_num_protect, p_protect_info[0], p_protect_info[1], p_protect_info[2]);
@@ -638,11 +797,11 @@ UINT8 bta_av_audio_sink_getconfig(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type,
/* Retrieve the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer == NULL) {
APPL_TRACE_ERROR("bta_av_audio_sink_getconfig could not find peer entry");
APPL_TRACE_ERROR("sink_getconfig could not find peer entry");
return A2D_FAIL;
}
APPL_TRACE_DEBUG("bta_av_audio_sink_getconfig peer(o=%d,n_snks=%d,n_rx_snks=%d,n_sup_snks=%d)",
APPL_TRACE_DEBUG("sink_getconfig peer(o=%d,n_snks=%d,n_rx_snks=%d,n_sup_snks=%d)",
p_peer->opened, p_peer->num_srcs, p_peer->num_rx_srcs, p_peer->num_sup_srcs);
p_peer->num_rx_srcs++;
@@ -666,7 +825,7 @@ UINT8 bta_av_audio_sink_getconfig(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type,
if (p_peer->num_sup_srcs < BTA_AV_CO_NUM_ELEMENTS(p_peer->srcs)) {
p_src = &p_peer->srcs[p_peer->num_sup_srcs++];
APPL_TRACE_DEBUG("bta_av_audio_sink_getconfig saved caps[%x:%x:%x:%x:%x:%x:%x:%x]",
APPL_TRACE_DEBUG("sink_getconfig saved caps[%x:%x:%x:%x:%x:%x:%x:%x]",
p_codec_info[1], p_codec_info[2], p_codec_info[3],
p_codec_info[4], p_codec_info[5], p_codec_info[6],
p_codec_info[7], p_codec_info[8]);
@@ -678,14 +837,14 @@ UINT8 bta_av_audio_sink_getconfig(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type,
p_src->num_protect = *p_num_protect;
memcpy(p_src->protect_info, p_protect_info, BTA_AV_CP_INFO_LEN);
} else {
APPL_TRACE_ERROR("bta_av_audio_sink_getconfig no more room for SRC info");
APPL_TRACE_ERROR("sink_getconfig no more room for SRC info");
}
}
/* If last SNK get capabilities or all supported codec caps retrieved */
if ((p_peer->num_rx_srcs == p_peer->num_srcs) ||
(p_peer->num_sup_srcs == BTA_AV_CO_NUM_ELEMENTS(p_peer->srcs))) {
APPL_TRACE_DEBUG("bta_av_audio_sink_getconfig last SRC reached");
APPL_TRACE_DEBUG("sink_getconfig last SRC reached");
/* Protect access to bta_av_co_cb.codec_cfg */
osi_mutex_global_lock();
@@ -696,14 +855,14 @@ UINT8 bta_av_audio_sink_getconfig(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type,
p_src = &p_peer->srcs[index];
/* Build the codec configuration for this sink */
{
if (bta_av_co_audio_isect_src_caps(p_src->codec_caps, isect_caps)) {
/* Save the new configuration */
p_peer->p_src = p_src;
/* get preferred config from src_caps */
bta_av_build_src_cfg(pref_cfg, p_src->codec_caps);
/* get preferred config from the caps supported by both peer src and local sink */
bta_av_build_src_cfg(pref_cfg, isect_caps);
memcpy(p_peer->codec_cfg, pref_cfg, AVDT_CODEC_SIZE);
APPL_TRACE_DEBUG("bta_av_audio_sink_getconfig p_codec_info[%x:%x:%x:%x:%x:%x:%x:%x]",
APPL_TRACE_DEBUG("sink_getconfig p_codec_info[%x:%x:%x:%x:%x:%x:%x:%x]",
p_peer->codec_cfg[1], p_peer->codec_cfg[2], p_peer->codec_cfg[3],
p_peer->codec_cfg[4], p_peer->codec_cfg[5], p_peer->codec_cfg[6],
p_peer->codec_cfg[7], p_peer->codec_cfg[8]);
@@ -718,6 +877,8 @@ UINT8 bta_av_audio_sink_getconfig(tBTA_AV_HNDL hndl, tBTA_AV_CODEC codec_type,
*p_sep_info_idx = p_src->sep_info_idx;
memcpy(p_codec_info, p_peer->codec_cfg, AVDT_CODEC_SIZE);
result = A2D_SUCCESS;
} else {
APPL_TRACE_ERROR("sink_getconfig no common caps with peer src");
}
}
/* Protect access to bta_av_co_cb.codec_cfg */
@@ -1286,17 +1447,21 @@ static BOOLEAN bta_av_co_audio_codec_build_config(const UINT8 *p_codec_caps, UIN
case BTC_AV_CODEC_M24: {
UINT32 cfg_bit_rate;
UINT32 cap_bit_rate;
UINT32 nego_bit_rate;
/* LOSC(1) + media(1) + codec(1) + CIE(6) = 9 octets */
memcpy(p_codec_cfg, bta_av_co_cb.codec_cfg.info, BTA_AV_CO_M24_INFO_LEN);
cfg_bit_rate = bta_av_m24_br_info(p_codec_cfg);
cap_bit_rate = bta_av_m24_br_info(p_codec_caps);
/* Bit rate 0 means "not known" per A2DP; do not treat it as min=0. */
nego_bit_rate = bta_av_m24_br_min(cfg_bit_rate, cap_bit_rate);
bta_av_m24_set_br_info(p_codec_cfg, BTA_AV_CO_MIN(cfg_bit_rate, cap_bit_rate));
bta_av_m24_set_br_info(p_codec_cfg, nego_bit_rate);
p_codec_cfg[BTA_AV_CO_M24_VBR_BR1_OFF] &= (p_codec_caps[BTA_AV_CO_M24_VBR_BR1_OFF] & A2D_M24_IE_VBR_MSK) | ~A2D_M24_IE_VBR_MSK;
APPL_TRACE_EVENT("bta_av_co_audio_codec_build_config bit rate: 0x%u(cfg_br: %u/cap_br: %u, min: %u)", bta_av_m24_br_info(p_codec_cfg), cfg_bit_rate, cap_bit_rate, BTA_AV_CO_MIN(cfg_bit_rate, cap_bit_rate));
APPL_TRACE_EVENT("bta_av_co_audio_codec_build_config bit rate: %u(cfg_br: %u/cap_br: %u, nego: %u)",
bta_av_m24_br_info(p_codec_cfg), cfg_bit_rate, cap_bit_rate, nego_bit_rate);
break;
}
#endif

View File

@@ -85,6 +85,24 @@ typedef enum {
#define BTC_AV_M24_CIE_OFFSET 3
#define BTC_AV_M24_CIE_LEN 6
#define BTC_AV_SBC_SNK_HQ_BITPOOL 53
#define BTC_AV_SBC_CH_MODE_ALL (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)
#define BTC_AV_SBC_CH_MODE_SRC_C1 (ESP_A2D_SBC_CIE_CH_MODE_DUAL_CHANNEL | \
ESP_A2D_SBC_CIE_CH_MODE_STEREO | \
ESP_A2D_SBC_CIE_CH_MODE_JOINT_STEREO)
#define BTC_AV_SBC_BLOCK_LEN_ALL (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)
#define BTC_AV_SBC_NUM_SUBBANDS_ALL (ESP_A2D_SBC_CIE_NUM_SUBBANDS_4 | \
ESP_A2D_SBC_CIE_NUM_SUBBANDS_8)
#define BTC_AV_SBC_ALLOC_MTHD_ALL (ESP_A2D_SBC_CIE_ALLOC_MTHD_SNR | \
ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS)
#define BTC_AV_M24_CH_1_2 (ESP_A2D_M24_CIE_CH_1 | ESP_A2D_M24_CIE_CH_2)
/*****************************************************************************
** Local type definitions
******************************************************************************/
@@ -1242,9 +1260,15 @@ static bt_status_t btc_av_init(int service_id)
#if (BTC_AV_EXT_CODEC == TRUE)
UINT8 index;
UINT8 tsep = (service_id == BTA_A2DP_SINK_SERVICE_ID) ? AVDT_TSEP_SNK : AVDT_TSEP_SRC;
for (index = 0; index < BTA_AV_MAX_SEPS; index++) {
btc_av_cb.codec_caps[index].id = BTC_AV_CODEC_NONE;
memset(btc_av_cb.codec_caps[index].info, 0, AVDT_CODEC_SIZE);
/* A2DP mandates SBC: fill every SEID with the default SBC capability */
if (bta_av_co_audio_build_sbc_default(tsep, &btc_av_cb.codec_caps[index].id,
btc_av_cb.codec_caps[index].info) != TRUE) {
BTC_TRACE_ERROR("%s: failed to build default SBC for seid %d", __func__, index);
btc_av_cb.codec_caps[index].id = BTC_AV_CODEC_NONE;
memset(btc_av_cb.codec_caps[index].info, 0, AVDT_CODEC_SIZE);
}
}
#endif
@@ -1624,11 +1648,73 @@ tBTC_AV_CODEC_INFO *btc_av_codec_cap_get(void)
return btc_av_cb.codec_caps;
}
static BOOLEAN btc_av_sep_mcc_caps_valid(BOOLEAN is_sink, const esp_a2d_mcc_t *mcc)
{
/* Mandatory bits per A2DP v1.4.1 Get All Capabilities (Tables 4.24.7, 4.144.20). */
if (mcc->type == ESP_A2D_MCT_SBC) {
const esp_a2d_cie_sbc_t *sbc = &mcc->cie.sbc_info;
if (!sbc->samp_freq || !sbc->ch_mode || !sbc->block_len ||
!sbc->num_subbands || !sbc->alloc_mthd ||
sbc->min_bitpool < A2D_SBC_IE_MIN_BITPOOL || sbc->max_bitpool > A2D_SBC_IE_MAX_BITPOOL ||
sbc->max_bitpool < sbc->min_bitpool) {
return FALSE;
}
/* Table 4.4: block length 4/8/12/16 mandatory for SRC and SNK. */
if ((sbc->block_len & BTC_AV_SBC_BLOCK_LEN_ALL) != BTC_AV_SBC_BLOCK_LEN_ALL) {
return FALSE;
}
if (is_sink) {
/* Tables 4.2, 4.3, 4.5, 4.6, 4.7: 44.1+48, all channel modes,
* both subbands, SNR+Loudness, min bitpool 2, max >= HQ JS 44.1. */
return ((sbc->samp_freq & (ESP_A2D_SBC_CIE_SF_44K | ESP_A2D_SBC_CIE_SF_48K)) ==
(ESP_A2D_SBC_CIE_SF_44K | ESP_A2D_SBC_CIE_SF_48K)) &&
((sbc->ch_mode & BTC_AV_SBC_CH_MODE_ALL) == BTC_AV_SBC_CH_MODE_ALL) &&
((sbc->num_subbands & BTC_AV_SBC_NUM_SUBBANDS_ALL) == BTC_AV_SBC_NUM_SUBBANDS_ALL) &&
((sbc->alloc_mthd & BTC_AV_SBC_ALLOC_MTHD_ALL) == BTC_AV_SBC_ALLOC_MTHD_ALL) &&
(sbc->min_bitpool == A2D_SBC_IE_MIN_BITPOOL) &&
(sbc->max_bitpool >= BTC_AV_SBC_SNK_HQ_BITPOOL);
}
/* SRC: Table 4.2 C1 (44.1 or 48), Table 4.3 Mono + C1 stereo family,
* Table 4.5 subbands 8, Table 4.6 Loudness. */
return ((sbc->samp_freq & (ESP_A2D_SBC_CIE_SF_44K | ESP_A2D_SBC_CIE_SF_48K)) != 0) &&
(sbc->ch_mode & ESP_A2D_SBC_CIE_CH_MODE_MONO) &&
(sbc->ch_mode & BTC_AV_SBC_CH_MODE_SRC_C1) &&
(sbc->num_subbands & ESP_A2D_SBC_CIE_NUM_SUBBANDS_8) &&
(sbc->alloc_mthd & ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS);
}
#if (BTC_AV_CODEC_AAC_INCLUDED == TRUE)
if (mcc->type == ESP_A2D_MCT_M24) {
const esp_a2d_cie_m24_t *m24 = &mcc->cie.m24_info;
/* Table 4.14: MPEG-2 AAC LC mandatory. Table 4.16: DRC not with MPEG-2 AAC LC only. */
if (!(m24->obj_type & ESP_A2D_M24_CIE_OBJ_TYPE_2_AAC_LC) ||
!(m24->samp_freq1 | m24->samp_freq2) || !m24->ch) {
return FALSE;
}
if (m24->obj_type == ESP_A2D_M24_CIE_OBJ_TYPE_2_AAC_LC && m24->drc) {
return FALSE;
}
if (is_sink) {
/* Tables 4.17, 4.18, 4.20: 44.1+48, ch 1+2, VBR. */
return (m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) &&
(m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_48K) &&
((m24->ch & BTC_AV_M24_CH_1_2) == BTC_AV_M24_CH_1_2) &&
m24->vbr;
}
/* SRC: Table 4.17 C1 (44.1 or 48), Table 4.18 C1 (1 or 2 ch). */
return ((m24->samp_freq1 & ESP_A2D_M24_CIE_SF1_44K) ||
(m24->samp_freq2 & ESP_A2D_M24_CIE_SF2_48K)) &&
(m24->ch & BTC_AV_M24_CH_1_2);
}
#endif
return FALSE;
}
static void btc_av_reg_sep(uint8_t tsep, uint8_t seid, esp_a2d_mcc_t *mcc)
{
tBTA_AV_DATA_CBACK *p_data_cback = NULL;
esp_a2d_cb_param_t param;
tBTA_AV_CODEC codec_type = ESP_A2D_MCT_NON_A2DP;
UINT8 index;
param.a2d_sep_reg_stat.seid = seid;
if (btc_av_cb.sm_handle == NULL || btc_sm_get_state(btc_av_cb.sm_handle) != BTC_AV_STATE_IDLE) {
@@ -1638,6 +1724,35 @@ static void btc_av_reg_sep(uint8_t tsep, uint8_t seid, esp_a2d_mcc_t *mcc)
return;
}
if (!btc_av_sep_mcc_caps_valid((tsep == AVDT_TSEP_SNK), mcc)) {
param.a2d_sep_reg_stat.reg_state = ESP_A2D_SEP_REG_UNSUPPORTED;
btc_a2d_cb_to_app(ESP_A2D_SEP_REG_STATE_EVT, &param);
BTC_TRACE_WARNING("%s: refuse seid %d codec 0x%02x, A2DP capability check failed",
__func__, seid, mcc->type);
return;
}
/*
* A2DP requires at least one SBC SEP. Reject a non-SBC registration that
* would overwrite the last remaining SBC capability.
*/
if (mcc->type != ESP_A2D_MCT_SBC) {
BOOLEAN has_other_sbc = FALSE;
for (index = 0; index < BTA_AV_MAX_SEPS; index++) {
if (index != seid && btc_av_cb.codec_caps[index].id == BTC_AV_CODEC_SBC) {
has_other_sbc = TRUE;
break;
}
}
if (!has_other_sbc) {
param.a2d_sep_reg_stat.reg_state = ESP_A2D_SEP_REG_SBC_REQUIRED;
btc_a2d_cb_to_app(ESP_A2D_SEP_REG_STATE_EVT, &param);
BTC_TRACE_WARNING("%s: refuse seid %d codec 0x%02x, at least one SBC SEP is required",
__func__, seid, mcc->type);
return;
}
}
if (tsep == AVDT_TSEP_SNK) {
p_data_cback = bte_av_media_sink_callback;
}

View File

@@ -57,6 +57,31 @@ 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.
### Stream Endpoint (SEP) registration
This example registers codec capabilities with `esp_a2d_sink_register_stream_endpoint()` after A2DP sink init and before connecting. A2DP requires **at least one SBC SEP**; using AAC therefore needs `ESP_A2D_MAX_SEPS >= 2`. Capabilities that do not meet A2DP Profile (SNK) fail with `ESP_A2D_SEP_REG_UNSUPPORTED` in `ESP_A2D_SEP_REG_STATE_EVT`. Overwriting the last SBC SEP fails with `ESP_A2D_SEP_REG_SBC_REQUIRED`.
**SBC (SNK)**
| Field | Mandatory |
| :---- | :-------- |
| Sampling frequency | 44.1 kHz and 48 kHz |
| Channel mode | Mono, Dual Channel, Stereo, and Joint Stereo |
| Block length | 4, 8, 12, and 16 |
| Subbands | 4 and 8 |
| Allocation method | SNR and Loudness |
| Bitpool | min = 2; max ≥ 53 (High Quality Joint Stereo 44.1 kHz, A2DP Table 4.7); max ≤ 250 |
**AAC (SNK)**
| Field | Mandatory |
| :---- | :-------- |
| Object type | MPEG-2 AAC LC |
| MPEG-D DRC | Must be 0 if only MPEG-2 AAC LC is advertised |
| Sampling frequency | 44.1 kHz and 48 kHz |
| Channels | 1 and 2 |
| VBR | Supported |
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output.

View File

@@ -28,6 +28,31 @@ idf.py menuconfig
* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable
### Stream Endpoint (SEP) registration
This example registers codec capabilities with `esp_a2d_source_register_stream_endpoint()` after A2DP source init and before connecting. A2DP requires **at least one SBC SEP**; using AAC therefore needs `ESP_A2D_MAX_SEPS >= 2`. Capabilities that do not meet A2DP Profile (SRC) fail with `ESP_A2D_SEP_REG_UNSUPPORTED` in `ESP_A2D_SEP_REG_STATE_EVT`. Overwriting the last SBC SEP fails with `ESP_A2D_SEP_REG_SBC_REQUIRED`.
**SBC (SRC)**
| Field | Mandatory |
| :---- | :-------- |
| Sampling frequency | At least one of 44.1 kHz and 48 kHz |
| Channel mode | Mono, and at least one of Dual Channel / Stereo / Joint Stereo |
| Block length | 4, 8, 12, and 16 |
| Subbands | 8 |
| Allocation method | Loudness |
| Bitpool | min 2250, max 2250, min ≤ max |
**AAC (SRC)**
| Field | Mandatory |
| :---- | :-------- |
| Object type | MPEG-2 AAC LC |
| MPEG-D DRC | Must be 0 if only MPEG-2 AAC LC is advertised |
| Sampling frequency | At least one of 44.1 kHz and 48 kHz |
| Channels | At least one of 1 and 2 |
| VBR | Optional |
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output.