mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(bt): A2DP source supports AAC codec
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 <stddef.h>
|
||||
#include <string.h>
|
||||
#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) */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user