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;