diff --git a/components/bt/host/bluedroid/api/esp_a2dp_api.c b/components/bt/host/bluedroid/api/esp_a2dp_api.c index 6e7bb13fd6..05e61e5a67 100644 --- a/components/bt/host/bluedroid/api/esp_a2dp_api.c +++ b/components/bt/host/bluedroid/api/esp_a2dp_api.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -349,6 +349,35 @@ esp_err_t esp_a2d_source_init(void) return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; } +esp_err_t esp_a2d_source_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, const esp_a2d_mcc_t *pref_mcc) +{ + if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { + return ESP_ERR_INVALID_STATE; + } + + if (g_a2dp_on_deinit || g_a2dp_source_ongoing_deinit) { + return ESP_ERR_INVALID_STATE; + } + + if (conn_hdl == 0 || pref_mcc == NULL) { + return ESP_ERR_INVALID_ARG; + } + + btc_msg_t msg; + msg.sig = BTC_SIG_API_CALL; + msg.pid = BTC_PID_A2DP; + msg.act = BTC_AV_SRC_API_SET_PREF_MCC_EVT; + + btc_av_args_t arg; + memset(&arg, 0, sizeof(btc_av_args_t)); + arg.set_pref_mcc.conn_hdl = conn_hdl; + memcpy(&arg.set_pref_mcc.pref_mcc, pref_mcc, sizeof(esp_a2d_mcc_t)); + + /* Switch to BTC context */ + bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_av_args_t), NULL, NULL); + return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; +} + esp_err_t esp_a2d_source_register_stream_endpoint(uint8_t seid, const esp_a2d_mcc_t *mcc) { if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) { 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 5f1d801931..47ae116dd7 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -243,6 +243,7 @@ typedef enum { ESP_A2D_SNK_GET_DELAY_VALUE_EVT, /*!< indicate a2dp sink get delay report value complete, only used for A2DP SINK */ ESP_A2D_REPORT_SNK_DELAY_VALUE_EVT, /*!< report delay value, only used for A2DP SRC */ ESP_A2D_REPORT_SNK_CODEC_CAPS_EVT, /*!< report sink codec capabilities, only used for A2DP SRC */ + ESP_A2D_SRC_SET_PREF_MCC_EVT, /*!< indicate a2dp source set preferred media codec configuration status, only used for A2DP SRC */ } esp_a2d_cb_event_t; /** @@ -349,6 +350,16 @@ typedef union { esp_a2d_mcc_t mcc; /*!< A2DP sink media codec capability information */ } a2d_report_snk_codec_caps_stat; /*!< A2DP source received sink codec capabilities */ + /** + * @brief ESP_A2D_SRC_SET_PREF_MCC_EVT + */ + struct a2d_set_pref_mcc_param { + esp_bt_status_t set_status; /*!< set status. + @note Possible values: ESP_BT_STATUS_SUCCESS, ESP_BT_STATUS_FAIL, + ESP_BT_STATUS_NOT_READY, ESP_BT_STATUS_BUSY, ESP_BT_STATUS_UNSUPPORTED. */ + esp_a2d_conn_hdl_t conn_hdl; /*!< connection handle */ + } a2d_set_pref_mcc_stat; /*!< A2DP source set preferred media codec configuration */ + } esp_a2d_cb_param_t; /** @@ -570,6 +581,23 @@ esp_err_t esp_a2d_get_profile_status(esp_a2d_profile_status_t *profile_status); */ esp_err_t esp_a2d_source_init(void); +/** + * + * @brief Set the preferred A2DP source media codec configuration after establishing A2DP connection + * and before starting A2DP stream. + * + * @param[in] conn_hdl : connection handle + * @param[in] pref_mcc : preferred media codec configuration + * + * @return + * - ESP_OK: success + * - ESP_ERR_INVALID_STATE: if bluetooth stack is not yet enabled + * - ESP_ERR_INVALID_ARG: invalid parameter + * - ESP_FAIL: others + * + */ +esp_err_t esp_a2d_source_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, const esp_a2d_mcc_t *pref_mcc); + /** * @brief Register a a2dp source Stream Endpoint (SEP) with specific codec capability, shall register * SEP after a2dp source initializing and before a2dp connection establishing. Register the same 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 3764626fb6..4d57788533 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 @@ -121,6 +121,7 @@ static BOOLEAN bta_av_co_audio_peer_supports_codec(tBTA_AV_CO_PEER *p_peer, UINT static UINT8 bta_av_co_audio_media_supports_config(UINT8 codec_type, const UINT8 *p_codec_cfg); static UINT8 bta_av_co_audio_sink_supports_config(UINT8 codec_type, const UINT8 *p_codec_cfg); static BOOLEAN bta_av_co_audio_peer_src_supports_codec(tBTA_AV_CO_PEER *p_peer, UINT8 *p_src_index); +static BOOLEAN bta_av_co_audio_src_supports_pref_cfg(void); /******************************************************************************* @@ -1409,6 +1410,28 @@ static UINT8 bta_av_co_audio_media_supports_config(UINT8 codec_type, const UINT8 return status; } +/******************************************************************************* + ** + ** Function bta_av_co_audio_src_supports_pref_cfg + ** + ** Description Check if the media source codec capability supports the preferred configuration + ** + ** Returns TRUE if the media source supports preferred config, FALSE otherwise + ** + *******************************************************************************/ +static BOOLEAN bta_av_co_audio_src_supports_pref_cfg(void) +{ +#if (BTC_AV_EXT_CODEC == TRUE) + return (bta_av_co_cb.codec_pref_cfg.id == bta_av_co_cb.codec_caps.id && + bta_av_sbc_cfg_in_external_codec_cap((UINT8 *)&bta_av_co_cb.codec_pref_cfg.info, + (UINT8 *)&bta_av_co_cb.codec_caps.info) == A2D_SUCCESS); +#else + return (bta_av_co_cb.codec_pref_cfg.id == BTC_AV_CODEC_SBC && + bta_av_sbc_cfg_in_cap((UINT8 *)&bta_av_co_cb.codec_pref_cfg.info, + (tA2D_SBC_CIE *)&bta_av_co_sbc_caps) == A2D_SUCCESS); +#endif +} + /******************************************************************************* ** ** Function bta_av_co_audio_codec_supported @@ -1493,6 +1516,219 @@ BOOLEAN bta_av_co_audio_codec_supported(tBTC_AV_STATUS *p_status) return TRUE; } +/******************************************************************************* + ** + ** Function bta_av_co_audio_set_pref_mcc + ** + ** Description Set preferred codec configuration for a specific connection + ** and initiate reconfiguration if the connection is open. + ** + ** Note: This function returns TRUE if the preferred config is + ** supported. The actual success + ** or failure of reconfiguration will be reported asynchronously + ** via BTA_AV_RECONFIG_EVT. + ** + ** Returns TRUE if the preferred config is supported, FALSE otherwise + ** + *******************************************************************************/ +BOOLEAN bta_av_co_audio_set_pref_mcc(tBTA_AV_HNDL hndl, tBTC_AV_CODEC_INFO *pref_mcc) +{ +#if (BTC_AV_SRC_INCLUDED == TRUE) + tBTA_AV_CO_PEER *p_peer; + tBTA_AV_CO_SINK *p_sink; + UINT8 codec_cfg[AVDT_CODEC_SIZE]; + UINT8 num_protect = 0; + UINT8 snk_index; + BOOLEAN pref_cfg_supported = FALSE; +#if defined(BTA_AV_CO_CP_SCMS_T) && (BTA_AV_CO_CP_SCMS_T == TRUE) + BOOLEAN cp_active; +#endif + + if (pref_mcc) { + bta_av_co_cb.codec_pref_cfg.id = pref_mcc->id; + memcpy(bta_av_co_cb.codec_pref_cfg.info, pref_mcc->info, AVDT_CODEC_SIZE); + pref_cfg_supported = bta_av_co_audio_src_supports_pref_cfg(); + + if (pref_cfg_supported) { + /* Retrieve the peer info for the specified handle */ + p_peer = bta_av_co_get_peer(hndl); + if (p_peer == NULL) { + APPL_TRACE_ERROR("bta_av_co_audio_set_pref_mcc could not find peer entry for handle 0x%x", hndl); + return FALSE; + } + + if (!p_peer->opened) { + APPL_TRACE_WARNING("bta_av_co_audio_set_pref_mcc connection 0x%x is not opened", hndl); + return FALSE; + } + + /* Update the current codec configuration */ + bta_av_co_audio_codec_reset(); + + /* Protect access to bta_av_co_cb.codec_cfg */ + osi_mutex_global_lock(); + + /* Try to use the currently selected sink first. */ + if (p_peer->p_snk != NULL && + p_peer->p_snk->codec_type == bta_av_co_cb.codec_cfg.id && + bta_av_co_audio_codec_match(p_peer->p_snk->codec_caps)) { + /* Current sink supports the new preferred config, use it */ + p_sink = p_peer->p_snk; + } else if (bta_av_co_audio_peer_supports_codec(p_peer, &snk_index)) { + /* Current sink doesn't support, find another one that does */ + p_sink = &p_peer->snks[snk_index]; + /* Check that this sink is compatible with the CP */ + if (!bta_av_co_audio_sink_supports_cp(p_sink)) { + APPL_TRACE_DEBUG("bta_av_co_audio_set_pref_mcc connection 0x%x sink %d doesn't support cp", hndl, snk_index); + osi_mutex_global_unlock(); + bta_av_co_audio_clear_pref_mcc(hndl); + return FALSE; + } + } else { + /* No sink supports the new preferred config */ + APPL_TRACE_DEBUG("bta_av_co_audio_set_pref_mcc connection 0x%x has no sink supporting preferred config", hndl); + osi_mutex_global_unlock(); + bta_av_co_audio_clear_pref_mcc(hndl); + return FALSE; + } + + /* Build the codec configuration for this sink */ + if (bta_av_co_audio_codec_build_config(p_sink->codec_caps, codec_cfg)) { +#if defined(BTA_AV_CO_CP_SCMS_T) && (BTA_AV_CO_CP_SCMS_T == TRUE) + cp_active = bta_av_co_audio_sink_has_scmst(p_sink); +#endif + /* Check if configuration actually changed: + * 1. Sink changed + * 2. Codec configuration changed + * 3. CP state changed (if applicable) + */ + if (p_sink != p_peer->p_snk || memcmp(codec_cfg, p_peer->codec_cfg, AVDT_CODEC_SIZE) +#if defined(BTA_AV_CO_CP_SCMS_T) && (BTA_AV_CO_CP_SCMS_T == TRUE) + || (p_peer->cp_active != cp_active) +#endif + ) { + /* Update peer configuration with the new codec config based on preferred config. */ + p_peer->p_snk = p_sink; + memcpy(p_peer->codec_cfg, codec_cfg, AVDT_CODEC_SIZE); +#if defined(BTA_AV_CO_CP_SCMS_T) && (BTA_AV_CO_CP_SCMS_T == TRUE) + p_peer->cp_active = cp_active; + bta_av_co_cb.cp.active = cp_active; + if (cp_active) { + num_protect = BTA_AV_CP_INFO_LEN; + } +#endif + /* Reconfigure the connection to apply preferred codec configuration. + * This is an asynchronous operation. Success/failure will be reported + * via BTA_AV_RECONFIG_EVT. */ + APPL_TRACE_DEBUG("bta_av_co_audio_set_pref_mcc call BTA_AvReconfig(0x%x) to apply preferred config", hndl); + p_peer->pref_mcc_reconfig_initiated = TRUE; + BTA_AvReconfig(hndl, TRUE, p_sink->sep_info_idx, p_peer->codec_cfg, num_protect, (UINT8 *)bta_av_co_cp_scmst); + } else { + /* Configuration hasn't changed, no need to reconfigure. */ + APPL_TRACE_DEBUG("bta_av_co_audio_set_pref_mcc no change, hndl:0x%x", hndl); + p_peer->pref_mcc_reconfig_initiated = FALSE; + osi_mutex_global_unlock(); + /* Return TRUE to indicate preferred config is set. */ + return TRUE; + } + } else { + APPL_TRACE_ERROR("bta_av_co_audio_set_pref_mcc build cfg failed, hndl:0x%x", hndl); + osi_mutex_global_unlock(); + bta_av_co_audio_clear_pref_mcc(hndl); + return FALSE; + } + + osi_mutex_global_unlock(); + } + + return pref_cfg_supported; + } else { + APPL_TRACE_ERROR("bta_av_co_audio_set_pref_mcc pref_mcc NULL"); + return FALSE; + } +#else + UNUSED(hndl); + UNUSED(pref_mcc); + return FALSE; +#endif /* BTC_AV_SRC_INCLUDED == TRUE */ +} + +/******************************************************************************* + ** + ** Function bta_av_co_audio_clear_pref_mcc + ** + ** Description Clear the preferred codec configuration + ** + ** Returns void + ** + *******************************************************************************/ +void bta_av_co_audio_clear_pref_mcc(tBTA_AV_HNDL hndl) +{ + UNUSED(hndl); +#if (BTC_AV_SRC_INCLUDED == TRUE) + bta_av_co_cb.codec_pref_cfg.id = BTC_AV_CODEC_NONE; + memset(bta_av_co_cb.codec_pref_cfg.info, 0, AVDT_CODEC_SIZE); + /* reset codec configuration */ + bta_av_co_audio_codec_reset(); +#endif +} + +/******************************************************************************* + ** + ** Function bta_av_co_audio_pref_mcc_reconfig_initiated + ** + ** Description Check if a reconfig was actually initiated by the last call + ** to bta_av_co_audio_set_pref_mcc(). This is used to determine + ** if we need to wait for BTA_AV_RECONFIG_EVT or can notify + ** immediately (when config unchanged). + ** + ** Returns TRUE if reconfig was initiated, FALSE if config unchanged + ** + *******************************************************************************/ +BOOLEAN bta_av_co_audio_pref_mcc_reconfig_initiated(tBTA_AV_HNDL hndl) +{ +#if (BTC_AV_SRC_INCLUDED == TRUE) + tBTA_AV_CO_PEER *p_peer; + + /* Retrieve the peer info for the specified handle */ + p_peer = bta_av_co_get_peer(hndl); + if (p_peer == NULL || !p_peer->opened) { + return FALSE; + } + + return p_peer->pref_mcc_reconfig_initiated; +#else + UNUSED(hndl); + return FALSE; +#endif /* BTC_AV_SRC_INCLUDED == TRUE */ +} + +/******************************************************************************* + ** + ** Function bta_av_co_audio_pref_mcc_reconfig_clear + ** + ** Description Clear the pref_mcc_reconfig_initiated flag. This should be + ** called after processing a BTA_AV_RECONFIG_EVT for preferred + ** codec config change. + ** + ** Returns void + ** + *******************************************************************************/ +void bta_av_co_audio_pref_mcc_reconfig_clear(tBTA_AV_HNDL hndl) +{ +#if (BTC_AV_SRC_INCLUDED == TRUE) + tBTA_AV_CO_PEER *p_peer; + + /* Retrieve the peer info for the specified handle */ + p_peer = bta_av_co_get_peer(hndl); + if (p_peer != NULL) { + p_peer->pref_mcc_reconfig_initiated = FALSE; + } +#else + UNUSED(hndl); +#endif /* BTC_AV_SRC_INCLUDED == TRUE */ +} + /******************************************************************************* ** ** Function bta_av_co_audio_codec_reset @@ -1504,20 +1740,48 @@ BOOLEAN bta_av_co_audio_codec_supported(tBTC_AV_STATUS *p_status) *******************************************************************************/ void bta_av_co_audio_codec_reset(void) { + BOOLEAN pref_cfg_supported; + osi_mutex_global_lock(); FUNC_TRACE(); -#if (BTC_AV_EXT_CODEC == TRUE) - bta_av_co_cb.codec_cfg.id = bta_av_co_cb.codec_caps.id; - bta_av_build_src_cfg(bta_av_co_cb.codec_cfg.info, bta_av_co_cb.codec_caps.info); -#else - /* Reset the current configuration to SBC */ - bta_av_co_cb.codec_cfg.id = BTC_AV_CODEC_SBC; - if (A2D_BldSbcInfo(A2D_MEDIA_TYPE_AUDIO, (tA2D_SBC_CIE *)&btc_av_sbc_default_config, bta_av_co_cb.codec_cfg.info) != A2D_SUCCESS) { - APPL_TRACE_ERROR("bta_av_co_audio_codec_reset A2D_BldSbcInfo failed"); + pref_cfg_supported = FALSE; +#if (BTC_AV_SRC_INCLUDED == TRUE) + switch (bta_av_co_cb.codec_pref_cfg.id) { + case BTC_AV_CODEC_NONE: { + APPL_TRACE_DEBUG("bta_av_co_audio_codec_reset preferred codec configuration is not set"); + break; + } + case BTC_AV_CODEC_SBC: { + if (bta_av_co_audio_src_supports_pref_cfg()) { + pref_cfg_supported = TRUE; + } else { + APPL_TRACE_ERROR("bta_av_co_audio_codec_reset unsupported pref cfg"); + } + break; + } + + default: + APPL_TRACE_ERROR("bta_av_co_audio_codec_reset bad pref cfg id %d", bta_av_co_cb.codec_pref_cfg.id); + break; } #endif + if (pref_cfg_supported) { + bta_av_co_cb.codec_cfg = bta_av_co_cb.codec_pref_cfg; + } else { +#if (BTC_AV_EXT_CODEC == TRUE) + bta_av_co_cb.codec_cfg.id = bta_av_co_cb.codec_caps.id; + bta_av_build_src_cfg(bta_av_co_cb.codec_cfg.info, bta_av_co_cb.codec_caps.info); +#else + /* Reset the current configuration to SBC */ + bta_av_co_cb.codec_cfg.id = BTC_AV_CODEC_SBC; + if (A2D_BldSbcInfo(A2D_MEDIA_TYPE_AUDIO, (tA2D_SBC_CIE *)&btc_av_sbc_default_config, bta_av_co_cb.codec_cfg.info) != A2D_SUCCESS) { + APPL_TRACE_ERROR("bta_av_co_audio_codec_reset A2D_BldSbcInfo failed"); + } +#endif + } + osi_mutex_global_unlock(); } @@ -1549,7 +1813,15 @@ BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_ case BTC_AV_CODEC_PCM: new_cfg.id = BTC_AV_CODEC_SBC; - sbc_config = btc_av_sbc_default_config; + if (bta_av_co_audio_src_supports_pref_cfg()) { + if (A2D_ParsSbcInfo((tA2D_SBC_CIE *)&sbc_config, bta_av_co_cb.codec_pref_cfg.info, FALSE) != A2D_SUCCESS) { + APPL_TRACE_ERROR("bta_av_co_audio_set_codec A2D_ParsSbcInfo failed"); + return FALSE; + } + } else { + sbc_config = btc_av_sbc_default_config; + } + if ((p_feeding->cfg.pcm.num_channel != 1) && (p_feeding->cfg.pcm.num_channel != 2)) { APPL_TRACE_ERROR("bta_av_co_audio_set_codec PCM channel number unsupported"); @@ -1561,11 +1833,16 @@ BOOLEAN bta_av_co_audio_set_codec(const tBTC_AV_MEDIA_FEEDINGS *p_feeding, tBTC_ return FALSE; } switch (p_feeding->cfg.pcm.sampling_freq) { + /* SBC supports 16/32/44.1/48kHz */ + case 16000: + sbc_config.samp_freq = A2D_SBC_IE_SAMP_FREQ_16; + break; + case 32000: + sbc_config.samp_freq = A2D_SBC_IE_SAMP_FREQ_32; + break; case 8000: case 12000: - case 16000: case 24000: - case 32000: case 48000: sbc_config.samp_freq = A2D_SBC_IE_SAMP_FREQ_48; break; @@ -1708,6 +1985,7 @@ void bta_av_co_init(tBTC_AV_CODEC_INFO *codec_caps) if (codec_caps) { memcpy(&bta_av_co_cb.codec_caps, codec_caps, sizeof(tBTC_AV_CODEC_INFO)); } + bta_av_co_cb.codec_pref_cfg.id = BTC_AV_CODEC_NONE; bta_av_co_cb.codec_cfg_setconfig.id = BTC_AV_CODEC_NONE; #if defined(BTA_AV_CO_CP_SCMS_T) && (BTA_AV_CO_CP_SCMS_T == TRUE) diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c index 07dd7953b2..1da50c343f 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -430,17 +430,44 @@ void btc_a2dp_source_setup_codec(void) { tBTC_AV_MEDIA_FEEDINGS media_feeding; tBTC_AV_STATUS status; + UINT16 minmtu; + tA2D_SBC_CIE sbc_config; APPL_TRACE_EVENT("## A2DP SETUP CODEC ##\n"); osi_mutex_global_lock(); - /* for now hardcode 44.1 khz 16 bit stereo PCM format */ - media_feeding.cfg.pcm.sampling_freq = 44100; + bta_av_co_audio_get_sbc_config(&sbc_config, &minmtu); + + /* derive PCM feeding from SBC configuration */ + switch (sbc_config.samp_freq) { + case A2D_SBC_IE_SAMP_FREQ_48: + media_feeding.cfg.pcm.sampling_freq = 48000; + break; + case A2D_SBC_IE_SAMP_FREQ_32: + media_feeding.cfg.pcm.sampling_freq = 32000; + break; + case A2D_SBC_IE_SAMP_FREQ_16: + media_feeding.cfg.pcm.sampling_freq = 16000; + break; + case A2D_SBC_IE_SAMP_FREQ_44: + media_feeding.cfg.pcm.sampling_freq = 44100; + break; + default: + media_feeding.cfg.pcm.sampling_freq = 44100; + APPL_TRACE_WARNING("btc_a2dp_source_setup_codec unsupported SBC sampling frequency: %d", sbc_config.samp_freq); + break; + } + + media_feeding.cfg.pcm.num_channel = (sbc_config.ch_mode == A2D_SBC_IE_CH_MD_MONO) ? 1 : 2; media_feeding.cfg.pcm.bit_per_sample = 16; - media_feeding.cfg.pcm.num_channel = 2; media_feeding.format = BTC_AV_CODEC_PCM; + APPL_TRACE_DEBUG("btc_a2dp_source_setup_codec PCM feeding derived from SBC: freq=%d, bits=%d, ch=%d", + media_feeding.cfg.pcm.sampling_freq, + media_feeding.cfg.pcm.bit_per_sample, + media_feeding.cfg.pcm.num_channel); + if (bta_av_co_audio_set_codec(&media_feeding, &status)) { tBTC_MEDIA_INIT_AUDIO_FEEDING mfeed; @@ -970,11 +997,29 @@ static void btc_a2dp_source_pcm2sbc_init(tBTC_MEDIA_INIT_AUDIO_FEEDING *p_feedin /* Check the PCM feeding sampling_freq */ switch (p_feeding->feeding.cfg.pcm.sampling_freq) { + case 16000: + /* For this sampling_freq the AV connection must be 16000 */ + if (a2dp_source_local_param.btc_aa_src_cb.encoder.s16SamplingFreq != SBC_sf16000) { + /* Reconfiguration needed at 16000 */ + APPL_TRACE_DEBUG("SBC Reconfiguration needed at 16000"); + a2dp_source_local_param.btc_aa_src_cb.encoder.s16SamplingFreq = SBC_sf16000; + reconfig_needed = TRUE; + } + break; + + case 32000: + /* For this sampling_freq the AV connection must be 32000 */ + if (a2dp_source_local_param.btc_aa_src_cb.encoder.s16SamplingFreq != SBC_sf32000) { + /* Reconfiguration needed at 32000 */ + APPL_TRACE_DEBUG("SBC Reconfiguration needed at 32000"); + a2dp_source_local_param.btc_aa_src_cb.encoder.s16SamplingFreq = SBC_sf32000; + reconfig_needed = TRUE; + } + break; + case 8000: case 12000: - case 16000: case 24000: - case 32000: case 48000: /* For these sampling_freq the AV connection must be 48000 */ if (a2dp_source_local_param.btc_aa_src_cb.encoder.s16SamplingFreq != SBC_sf48000) { @@ -1001,13 +1046,6 @@ static void btc_a2dp_source_pcm2sbc_init(tBTC_MEDIA_INIT_AUDIO_FEEDING *p_feedin break; } - /* Some AV Headsets do not support Mono => always ask for Stereo */ - if (a2dp_source_local_param.btc_aa_src_cb.encoder.s16ChannelMode == SBC_MONO) { - APPL_TRACE_DEBUG("SBC Reconfiguration needed in Stereo"); - a2dp_source_local_param.btc_aa_src_cb.encoder.s16ChannelMode = SBC_JOINT_STEREO; - reconfig_needed = TRUE; - } - if (reconfig_needed != FALSE) { APPL_TRACE_DEBUG("%s :: mtu %d", __FUNCTION__, a2dp_source_local_param.btc_aa_src_cb.TxAaMtuSize); APPL_TRACE_DEBUG("ch mode %d, nbsubd %d, nb %d, alloc %d, rate %d, freq %d", @@ -1642,4 +1680,18 @@ static void btc_a2dp_source_thread_cleanup(UNUSED_ATTR void *context) a2dp_source_local_param.btc_aa_src_cb.poll_data = NULL; } +/******************************************************************************* + ** + ** Function btc_a2dp_source_set_pref_mcc + ** + ** Description + ** + ** Returns TRUE if the preferred config is supported, FALSE otherwise + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_set_pref_mcc(tBTA_AV_HNDL hndl, tBTC_AV_CODEC_INFO *pref_mcc) +{ + return bta_av_co_audio_set_pref_mcc(hndl, pref_mcc); +} + #endif /* (BTC_AV_SRC_INCLUDED == TRUE) && (BTC_AV_EXT_CODEC == FALSE) */ diff --git a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source_ext_codec.c b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source_ext_codec.c index f6a71add9b..dad8a05eab 100644 --- a/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source_ext_codec.c +++ b/components/bt/host/bluedroid/btc/profile/std/a2dp/btc_a2dp_source_ext_codec.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: Apache-2.0 */ @@ -232,4 +232,18 @@ void btc_a2dp_source_shutdown(void) #endif } +/******************************************************************************* + ** + ** Function btc_a2dp_source_set_pref_mcc + ** + ** Description + ** + ** Returns TRUE if the preferred config is supported, FALSE otherwise + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_set_pref_mcc(tBTA_AV_HNDL hndl, tBTC_AV_CODEC_INFO *pref_mcc) +{ + return bta_av_co_audio_set_pref_mcc(hndl, pref_mcc); +} + #endif /* (BTC_AV_SRC_INCLUDED == TRUE) && (BTC_AV_EXT_CODEC == TRUE) */ 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 75b9c41317..9a8e29fb59 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 @@ -96,6 +96,7 @@ typedef struct { UINT8 peer_sep; /* sep type of peer device */ tBTC_AV_CODEC_INFO codec_caps; #if BTC_AV_SRC_INCLUDED + tBTC_AV_CODEC_INFO pref_mcc; /* preferred media codec configuration */ osi_alarm_t *tle_av_open_on_rc; #endif /* BTC_AV_SRC_INCLUDED */ } btc_av_cb_t; @@ -152,6 +153,7 @@ static BOOLEAN btc_a2d_deinit_if_ongoing(void); #if BTC_AV_SRC_INCLUDED static bt_status_t btc_a2d_src_init(void); +static void btc_a2d_src_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_mcc_t *pref_mcc); static bt_status_t btc_a2d_src_connect(bt_bdaddr_t *remote_bda); static void btc_a2d_src_deinit(void); #endif /* BTC_AV_SRC_INCLUDED */ @@ -837,6 +839,15 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *p_data) /* pending start flag will be cleared when exit current state */ } + /* Check if this connection has a pending preferred config change */ + if (bta_av_co_audio_pref_mcc_reconfig_initiated(btc_av_cb.bta_handle)) { + bta_av_co_audio_pref_mcc_reconfig_clear(btc_av_cb.bta_handle); + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_FAIL; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + BTC_TRACE_DEBUG("pref cfg pending, closed, h:0x%x", btc_av_cb.bta_handle); + } + /* change state to idle, send acknowledgement if start is pending */ btc_sm_change_state(btc_av_cb.sm_handle, BTC_AV_STATE_IDLE); break; @@ -851,6 +862,24 @@ static BOOLEAN btc_av_state_opened_handler(btc_sm_event_t event, void *p_data) btc_av_cb.flags &= ~BTC_AV_FLAG_PENDING_START; btc_a2dp_control_command_ack(ESP_A2D_MEDIA_CTRL_ACK_FAILURE); } + + /* Handle user-initiated preferred codec config change. + * Check if this reconfig was triggered by preferred codec config change */ + if (bta_av_co_audio_pref_mcc_reconfig_initiated(p_av->reconfig.hndl)) { + bta_av_co_audio_pref_mcc_reconfig_clear(p_av->reconfig.hndl); + + if (p_av->reconfig.status == BTA_AV_SUCCESS) { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_SUCCESS; + BTC_TRACE_DEBUG("pref cfg reconfig ok, h:0x%x", p_av->reconfig.hndl); + } else { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_FAIL; + BTC_TRACE_DEBUG("pref cfg reconfig fail, h:0x%x st:%d", + p_av->reconfig.hndl, p_av->reconfig.status); + bta_av_co_audio_clear_pref_mcc(p_av->reconfig.hndl); + } + param.a2d_set_pref_mcc_stat.conn_hdl = p_av->reconfig.hndl; + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + } break; case BTC_AV_CONNECT_REQ_EVT: @@ -1745,6 +1774,10 @@ void btc_a2dp_call_handler(btc_msg_t *msg) btc_a2d_src_init(); break; } + case BTC_AV_SRC_API_SET_PREF_MCC_EVT: { + btc_a2d_src_set_pref_mcc(arg->set_pref_mcc.conn_hdl, &arg->set_pref_mcc.pref_mcc); + break; + } #if (BTC_AV_EXT_CODEC == TRUE) case BTC_AV_SRC_API_REG_SEP_EVT: { btc_av_reg_sep(AVDT_TSEP_SRC, arg->reg_sep.seid, &arg->reg_sep.mcc); @@ -1923,6 +1956,86 @@ static void btc_a2d_src_deinit(void) } } +static void btc_a2d_src_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, esp_a2d_mcc_t *pref_mcc) +{ + UNUSED(conn_hdl); + UNUSED(pref_mcc); +#if A2D_DYNAMIC_MEMORY == TRUE + if (btc_av_cb_ptr) +#endif + { + esp_a2d_cb_param_t param; + + if (btc_av_cb.sm_handle == NULL || btc_sm_get_state(btc_av_cb.sm_handle) != BTC_AV_STATE_OPENED) { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_NOT_READY; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc not ready"); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + return; + } + + if (conn_hdl != btc_av_cb.bta_handle) { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_UNSUPPORTED; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc bad hndl %d", conn_hdl); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + return; + } + + if (bta_av_co_audio_pref_mcc_reconfig_initiated(conn_hdl)) { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_BUSY; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc reconfig busy"); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + return; + } + + switch (pref_mcc->type) { + case ESP_A2D_MCT_SBC: { + btc_av_cb.pref_mcc.id = BTC_AV_CODEC_SBC; + if (A2D_BldSbcInfo(A2D_MEDIA_TYPE_AUDIO, (tA2D_SBC_CIE *)&btc_av_sbc_default_config, btc_av_cb.pref_mcc.info) == A2D_SUCCESS) { + /* overwrite sbc cie */ + memcpy(btc_av_cb.pref_mcc.info + A2D_SBC_CIE_OFF, &pref_mcc->cie, A2D_SBC_CIE_LEN); + + /* Note: Return value only indicates if the config is supported and reconfig is initiated. + * Actual success/failure will be reported via ESP_A2D_SRC_SET_PREF_MCC_EVT + * when BTA_AV_RECONFIG_EVT is received. */ + if (!btc_a2dp_source_set_pref_mcc(conn_hdl, &btc_av_cb.pref_mcc)) { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_UNSUPPORTED; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc bad params"); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + } else { + /* Check if reconfig was actually initiated */ + if (!bta_av_co_audio_pref_mcc_reconfig_initiated(conn_hdl)) { + /* Configuration unchanged, no reconfig needed. + * Preferred config is already in use, notify success immediately. */ + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_SUCCESS; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc already active"); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + } + } + } else { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_FAIL; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc build SBC info failed"); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + } + break; + } + + default: { + param.a2d_set_pref_mcc_stat.set_status = ESP_BT_STATUS_UNSUPPORTED; + param.a2d_set_pref_mcc_stat.conn_hdl = btc_av_cb.bta_handle; + BTC_TRACE_DEBUG("btc_a2d_src_set_pref_mcc bad codec type %d", pref_mcc->type); + btc_a2d_cb_to_app(ESP_A2D_SRC_SET_PREF_MCC_EVT, ¶m); + break; + } + } + } +} + static bt_status_t btc_a2d_src_connect(bt_bdaddr_t *remote_bda) { BTC_TRACE_DEBUG("%s\n", __FUNCTION__); 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 80bf92b7ec..2400fd302e 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -54,6 +54,7 @@ typedef struct { UINT16 mtu; /* maximum transmit unit size */ UINT16 uuid_to_connect; /* uuid of peer device */ BOOLEAN got_disc_res; /* got the results of initiating discovery */ + BOOLEAN pref_mcc_reconfig_initiated; /* TRUE if last bta_av_co_audio_set_pref_mcc() initiated reconfig for this peer */ } tBTA_AV_CO_PEER; typedef struct { @@ -67,6 +68,7 @@ typedef struct { tBTC_AV_CODEC_INFO codec_caps; /* Current codec configuration - access to this variable must be protected */ tBTC_AV_CODEC_INFO codec_cfg; + tBTC_AV_CODEC_INFO codec_pref_cfg; /* preferred media codec configuration for source */ tBTC_AV_CODEC_INFO codec_cfg_setconfig; /* remote peer setconfig preference */ tBTA_AV_CO_CP cp; @@ -122,6 +124,61 @@ UINT8 bta_av_co_cp_get_flag(void); *******************************************************************************/ BOOLEAN bta_av_co_cp_set_flag(UINT8 cp_flag); +/******************************************************************************* + ** + ** Function bta_av_co_audio_set_pref_mcc + ** + ** Description Set preferred codec configuration for a specific connection + ** and initiate reconfiguration if the connection is open. + ** + ** Note: This function returns TRUE if the preferred config is + ** supported. The actual success + ** or failure of reconfiguration will be reported asynchronously + ** via BTA_AV_RECONFIG_EVT. + ** + ** Returns TRUE if the preferred config is supported, FALSE otherwise + ** + *******************************************************************************/ +BOOLEAN bta_av_co_audio_set_pref_mcc(tBTA_AV_HNDL hndl, tBTC_AV_CODEC_INFO *pref_mcc); + +/******************************************************************************* + ** + ** Function bta_av_co_audio_clear_pref_mcc + ** + ** Description Clear the preferred codec configuration + ** + ** Returns void + ** + *******************************************************************************/ +void bta_av_co_audio_clear_pref_mcc(tBTA_AV_HNDL hndl); + +/******************************************************************************* + ** + ** Function bta_av_co_audio_pref_mcc_reconfig_initiated + ** + ** Description Check if a reconfig was actually initiated by the last call + ** to bta_av_co_audio_set_pref_mcc(). This is used to determine + ** if we need to wait for BTA_AV_RECONFIG_EVT or can notify + ** immediately (when config unchanged). + ** + ** Returns TRUE if reconfig was initiated, FALSE if config unchanged + ** + *******************************************************************************/ +BOOLEAN bta_av_co_audio_pref_mcc_reconfig_initiated(tBTA_AV_HNDL hndl); + +/******************************************************************************* + ** + ** Function bta_av_co_audio_pref_mcc_reconfig_clear + ** + ** Description Clear the pref_mcc_reconfig_initiated flag for a specific + ** connection. This should be called after processing a + ** BTA_AV_RECONFIG_EVT for preferred codec config change. + ** + ** Returns void + ** + *******************************************************************************/ +void bta_av_co_audio_pref_mcc_reconfig_clear(tBTA_AV_HNDL hndl); + /******************************************************************************* ** ** Function bta_av_co_audio_codec_reset diff --git a/components/bt/host/bluedroid/btc/profile/std/include/btc_a2dp_source.h b/components/bt/host/bluedroid/btc/profile/std/include/btc_a2dp_source.h index 3f0e79f8db..e1a9cd3e2b 100644 --- a/components/bt/host/bluedroid/btc/profile/std/include/btc_a2dp_source.h +++ b/components/bt/host/bluedroid/btc/profile/std/include/btc_a2dp_source.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -150,6 +150,17 @@ void btc_a2dp_source_on_suspended(tBTA_AV_SUSPEND *p_av); *******************************************************************************/ void btc_a2dp_source_set_tx_flush(BOOLEAN enable); +/******************************************************************************* + ** + ** Function btc_a2dp_source_set_pref_mcc + ** + ** Description set preferred codec configuration + ** + ** Returns TRUE if the preferred config is supported, FALSE otherwise + ** + *******************************************************************************/ +BOOLEAN btc_a2dp_source_set_pref_mcc(tBTA_AV_HNDL hndl, tBTC_AV_CODEC_INFO *pref_mcc); + #if (BTC_AV_EXT_CODEC == FALSE) /******************************************************************************* 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 f23ff67044..ecad1a9a7b 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -78,6 +78,7 @@ typedef enum { #endif /* BTC_AV_SINK_INCLUDED */ #if BTC_AV_SRC_INCLUDED BTC_AV_SRC_API_INIT_EVT, + BTC_AV_SRC_API_SET_PREF_MCC_EVT, BTC_AV_SRC_API_REG_SEP_EVT, BTC_AV_SRC_API_DEINIT_EVT, BTC_AV_SRC_API_CONNECT_EVT, @@ -108,6 +109,11 @@ typedef union { bt_bdaddr_t src_connect; // BTC_AV_SRC_API_DISCONNECT_EVT bt_bdaddr_t src_disconn; + // BTC_AV_SRC_API_SET_PREF_MCC_EVT + struct { + esp_a2d_conn_hdl_t conn_hdl; + esp_a2d_mcc_t pref_mcc; + } set_pref_mcc; #endif /* BTC_AV_SRC_INCLUDED */ // BTC_AV_CONFIG_EVT esp_a2d_mcc_t mcc; diff --git a/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c b/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c index ac4ec101e6..54bba3d680 100644 --- a/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c +++ b/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -91,6 +91,12 @@ 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); +/* check preferred codec configuration against sink capabilities */ +static bool check_pref_mcc_against_sink_caps(const esp_a2d_mcc_t *sink_caps, const esp_a2d_mcc_t *pref_mcc); + +/* set preferred codec configuration */ +static void bt_app_a2d_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, const esp_a2d_mcc_t *sink_caps); + /* 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); @@ -404,6 +410,77 @@ static void bt_app_av_sm_hdlr(uint16_t event, void *param) } } +static bool is_one_bit_set_u8(uint8_t v) +{ + return (v != 0) && ((v & (uint8_t)(v - 1)) == 0); +} + +static bool check_pref_mcc_against_sink_caps(const esp_a2d_mcc_t *sink_caps, const esp_a2d_mcc_t *pref_mcc) +{ + const esp_a2d_cie_sbc_t *caps; + const esp_a2d_cie_sbc_t *cfg; + + if (sink_caps == NULL || pref_mcc == NULL) { + return false; + } + if (sink_caps->type != pref_mcc->type) { + return false; + } + + if (pref_mcc->type != ESP_A2D_MCT_SBC) { + return false; + } + + caps = &sink_caps->cie.sbc_info; + cfg = &pref_mcc->cie.sbc_info; + + /* For preferred configuration, each field should select a single value (one bit) */ + if (!is_one_bit_set_u8(cfg->samp_freq) || ((cfg->samp_freq & caps->samp_freq) != cfg->samp_freq)) { + return false; + } + if (!is_one_bit_set_u8(cfg->ch_mode) || ((cfg->ch_mode & caps->ch_mode) != cfg->ch_mode)) { + return false; + } + if (!is_one_bit_set_u8(cfg->block_len) || ((cfg->block_len & caps->block_len) != cfg->block_len)) { + return false; + } + if (!is_one_bit_set_u8(cfg->num_subbands) || ((cfg->num_subbands & caps->num_subbands) != cfg->num_subbands)) { + return false; + } + if (!is_one_bit_set_u8(cfg->alloc_mthd) || ((cfg->alloc_mthd & caps->alloc_mthd) != cfg->alloc_mthd)) { + return false; + } + + if (cfg->min_bitpool < caps->min_bitpool || cfg->max_bitpool > caps->max_bitpool || cfg->min_bitpool > cfg->max_bitpool) { + return false; + } + + return true; +} + +static void bt_app_a2d_set_pref_mcc(esp_a2d_conn_hdl_t conn_hdl, const esp_a2d_mcc_t *sink_caps) +{ + esp_a2d_mcc_t pref_mcc; + + memset(&pref_mcc, 0, sizeof(pref_mcc)); + pref_mcc.type = ESP_A2D_MCT_SBC; + pref_mcc.cie.sbc_info.samp_freq = ESP_A2D_SBC_CIE_SF_44K; + pref_mcc.cie.sbc_info.ch_mode = ESP_A2D_SBC_CIE_CH_MODE_MONO; // Joint Stereo --> Mono + pref_mcc.cie.sbc_info.block_len = ESP_A2D_SBC_CIE_BLOCK_LEN_16; + pref_mcc.cie.sbc_info.num_subbands = ESP_A2D_SBC_CIE_NUM_SUBBANDS_8; + pref_mcc.cie.sbc_info.alloc_mthd = ESP_A2D_SBC_CIE_ALLOC_MTHD_LOUDNESS; + pref_mcc.cie.sbc_info.min_bitpool = 2; + pref_mcc.cie.sbc_info.max_bitpool = 35; // 53 --> 35 + + if (!check_pref_mcc_against_sink_caps(sink_caps, &pref_mcc)) { + ESP_LOGW(BT_AV_TAG, "pref_mcc not supported by sink"); + return; + } + + esp_err_t ret = esp_a2d_source_set_pref_mcc(conn_hdl, &pref_mcc); + ESP_LOGD(BT_AV_TAG, "Set pref_mcc result: %s", esp_err_to_name(ret)); +} + static void bt_app_av_state_unconnected_hdlr(uint16_t event, void *param) { esp_a2d_cb_param_t *a2d = NULL; @@ -581,6 +658,30 @@ static void bt_app_av_state_connected_hdlr(uint16_t event, void *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); + /* for now only SBC stream is supported */ + 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); + } + bt_app_a2d_set_pref_mcc(a2d->a2d_report_snk_codec_caps_stat.conn_hdl, sink_mcc); + break; + } + case ESP_A2D_SRC_SET_PREF_MCC_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + ESP_LOGI(BT_AV_TAG, "Set preferred media codec config result: conn_hdl: %d, set_status: %d", + a2d->a2d_set_pref_mcc_stat.conn_hdl, a2d->a2d_set_pref_mcc_stat.set_status); + break; + } default: { ESP_LOGE(BT_AV_TAG, "%s unhandled event: %d", __func__, event); break; @@ -629,7 +730,8 @@ static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t 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_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); break; } @@ -722,6 +824,17 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) 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);