blob: b52fcbf10e711e91e870ed4bb7b68fb124e3bc67 [file] [log] [blame]
/******************************************************************************
*
* Copyright (C) 2004-2012 Broadcom Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
/******************************************************************************
*
* This is the advanced audio/video call-out function implementation for
* BTIF.
*
******************************************************************************/
#include "bta_av_co.h"
#include <assert.h>
#include <string.h>
#include "a2dp_api.h"
#include "bt_target.h"
#include "bta_av_api.h"
#include "bta_av_ci.h"
#include "bta_sys.h"
#include "btif_av_co.h"
#include "btif_util.h"
#include "osi/include/mutex.h"
#include "osi/include/osi.h"
/*****************************************************************************
** Constants
*****************************************************************************/
/* Macro to retrieve the number of elements in a statically allocated array */
#define BTA_AV_CO_NUM_ELEMENTS(__a) (sizeof(__a) / sizeof((__a)[0]))
/* MIN and MAX macros */
#define BTA_AV_CO_MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#define BTA_AV_CO_MAX(X, Y) ((X) > (Y) ? (X) : (Y))
/* Macro to convert audio handle to index and vice versa */
#define BTA_AV_CO_AUDIO_HNDL_TO_INDX(hndl) (((hndl) & (~BTA_AV_CHNL_MSK)) - 1)
#define BTA_AV_CO_AUDIO_INDX_TO_HNDL(indx) (((indx) + 1) | BTA_AV_CHNL_AUDIO)
/* SCMS-T protect info */
const uint8_t bta_av_co_cp_scmst[AVDT_CP_INFO_LEN] = {0x02, 0x02, 0x00};
/*****************************************************************************
* Local data
****************************************************************************/
typedef struct {
uint8_t sep_info_idx; /* local SEP index (in BTA tables) */
uint8_t seid; /* peer SEP index (in peer tables) */
uint8_t codec_caps[AVDT_CODEC_SIZE]; /* peer SEP codec capabilities */
uint8_t num_protect; /* peer SEP number of CP elements */
uint8_t protect_info[AVDT_CP_INFO_LEN]; /* peer SEP content protection info */
} tBTA_AV_CO_SINK;
typedef struct {
BD_ADDR addr; /* address of audio/video peer */
tBTA_AV_CO_SINK
sinks[A2DP_CODEC_SEP_INDEX_MAX]; /* array of supported sinks */
tBTA_AV_CO_SINK srcs[A2DP_CODEC_SEP_INDEX_MAX]; /* array of supported srcs */
uint8_t num_sinks; /* total number of sinks at peer */
uint8_t num_srcs; /* total number of srcs at peer */
uint8_t num_seps; /* total number of seids at peer */
uint8_t num_rx_sinks; /* number of received sinks */
uint8_t num_rx_srcs; /* number of received srcs */
uint8_t num_sup_sinks; /* number of supported sinks in the sinks array */
uint8_t num_sup_srcs; /* number of supported srcs in the srcs array */
const tBTA_AV_CO_SINK* p_sink; /* currently selected sink */
const tBTA_AV_CO_SINK* p_src; /* currently selected src */
uint8_t codec_config[AVDT_CODEC_SIZE]; /* current codec configuration */
bool cp_active; /* current CP configuration */
bool acp; /* acceptor */
bool reconfig_needed; /* reconfiguration is needed */
bool opened; /* opened */
uint16_t mtu; /* maximum transmit unit size */
uint16_t uuid_to_connect; /* uuid of peer device */
} tBTA_AV_CO_PEER;
typedef struct {
bool active;
uint8_t flag;
} tBTA_AV_CO_CP;
typedef struct {
/* Connected peer information */
tBTA_AV_CO_PEER peers[BTA_AV_NUM_STRS];
/* Current codec configuration - access to this variable must be protected */
uint8_t codec_config[AVDT_CODEC_SIZE];
uint8_t codec_config_setconfig[AVDT_CODEC_SIZE]; /* remote peer setconfig
* preference */
tBTA_AV_CO_CP cp;
} tBTA_AV_CO_CB;
/* Control block instance */
static tBTA_AV_CO_CB bta_av_co_cb;
static bool bta_av_co_cp_is_scmst(const uint8_t* p_protectinfo);
static bool bta_av_co_audio_sink_has_scmst(const tBTA_AV_CO_SINK* p_sink);
static const tBTA_AV_CO_SINK* bta_av_co_find_peer_sink_supports_codec(
const uint8_t* codec_config, const tBTA_AV_CO_PEER* p_peer);
static const tBTA_AV_CO_SINK* bta_av_co_find_peer_src_supports_codec(
const tBTA_AV_CO_PEER* p_peer);
static bool bta_av_co_audio_codec_selected(const uint8_t* codec_config);
/*******************************************************************************
**
** Function bta_av_co_cp_get_flag
**
** Description Get content protection flag
** AVDT_CP_SCMS_COPY_NEVER
** AVDT_CP_SCMS_COPY_ONCE
** AVDT_CP_SCMS_COPY_FREE
**
** Returns The current flag value
**
*******************************************************************************/
static uint8_t bta_av_co_cp_get_flag(void) { return bta_av_co_cb.cp.flag; }
/*******************************************************************************
**
** Function bta_av_co_cp_set_flag
**
** Description Set content protection flag
** AVDT_CP_SCMS_COPY_NEVER
** AVDT_CP_SCMS_COPY_ONCE
** AVDT_CP_SCMS_COPY_FREE
**
** Returns true if setting the SCMS flag is supported else false
**
*******************************************************************************/
static bool bta_av_co_cp_set_flag(uint8_t cp_flag) {
APPL_TRACE_DEBUG("%s: cp_flag = %d", __func__, cp_flag);
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
#else
if (cp_flag != AVDT_CP_SCMS_COPY_FREE) {
return false;
}
#endif
bta_av_co_cb.cp.flag = cp_flag;
return true;
}
/*******************************************************************************
**
** Function bta_av_co_get_peer
**
** Description find the peer entry for a given handle
**
** Returns the control block
**
*******************************************************************************/
static tBTA_AV_CO_PEER* bta_av_co_get_peer(tBTA_AV_HNDL hndl) {
uint8_t index;
index = BTA_AV_CO_AUDIO_HNDL_TO_INDX(hndl);
APPL_TRACE_DEBUG("%s: handle = %d index = %d", __func__, hndl, index);
/* Sanity check */
if (index >= BTA_AV_CO_NUM_ELEMENTS(bta_av_co_cb.peers)) {
APPL_TRACE_ERROR("%s: peer index out of bounds: %d", __func__, index);
return NULL;
}
return &bta_av_co_cb.peers[index];
}
/*******************************************************************************
**
** Function bta_av_co_audio_init
**
** Description This callout function is executed by AV when it is
** started by calling BTA_AvRegister(). This function can be
** used by the phone to initialize audio paths or for other
** initialization purposes.
**
**
** Returns Stream codec and content protection capabilities info.
**
*******************************************************************************/
bool bta_av_co_audio_init(tA2DP_CODEC_SEP_INDEX codec_sep_index,
tAVDT_CFG* p_cfg) {
/* reset remote preference through setconfig */
memset(bta_av_co_cb.codec_config_setconfig, 0,
sizeof(bta_av_co_cb.codec_config_setconfig));
return A2DP_InitCodecConfig(codec_sep_index, p_cfg);
}
/*******************************************************************************
**
** Function bta_av_co_audio_disc_res
**
** Description This callout function is executed by AV to report the
** number of stream end points (SEP) were found during the
** AVDT stream discovery process.
**
**
** Returns void.
**
*******************************************************************************/
void bta_av_co_audio_disc_res(tBTA_AV_HNDL hndl, uint8_t num_seps,
uint8_t num_sink, uint8_t num_src, BD_ADDR addr,
uint16_t uuid_local) {
tBTA_AV_CO_PEER* p_peer;
APPL_TRACE_DEBUG("%s: h:x%x num_seps:%d num_sink:%d num_src:%d", __func__,
hndl, num_seps, num_sink, num_src);
/* Find the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer == NULL) {
APPL_TRACE_ERROR("%s: could not find peer entry", __func__);
return;
}
/* Sanity check : this should never happen */
if (p_peer->opened) {
APPL_TRACE_ERROR("%s: peer already opened", __func__);
}
/* Copy the discovery results */
bdcpy(p_peer->addr, addr);
p_peer->num_sinks = num_sink;
p_peer->num_srcs = num_src;
p_peer->num_seps = num_seps;
p_peer->num_rx_sinks = 0;
p_peer->num_rx_srcs = 0;
p_peer->num_sup_sinks = 0;
if (uuid_local == UUID_SERVCLASS_AUDIO_SINK)
p_peer->uuid_to_connect = UUID_SERVCLASS_AUDIO_SOURCE;
else if (uuid_local == UUID_SERVCLASS_AUDIO_SOURCE)
p_peer->uuid_to_connect = UUID_SERVCLASS_AUDIO_SINK;
}
/*******************************************************************************
**
** Function bta_av_audio_sink_getconfig
**
** Description This callout function is executed by AV to retrieve the
** desired codec and content protection configuration for the
** A2DP Sink audio stream in Initiator.
**
**
** Returns Pass or Fail for current getconfig.
**
*******************************************************************************/
static tA2DP_STATUS bta_av_audio_sink_getconfig(
tBTA_AV_HNDL hndl, uint8_t* p_codec_info, uint8_t* p_sep_info_idx,
uint8_t seid, uint8_t* p_num_protect, uint8_t* p_protect_info) {
tA2DP_STATUS result = A2DP_FAIL;
tBTA_AV_CO_PEER* p_peer;
uint8_t pref_config[AVDT_CODEC_SIZE];
APPL_TRACE_DEBUG("%s: handle:0x%x codec:%s seid:%d", __func__, hndl,
A2DP_CodecName(p_codec_info), seid);
APPL_TRACE_DEBUG("%s: num_protect:0x%02x protect_info:0x%02x%02x%02x",
__func__, *p_num_protect, p_protect_info[0],
p_protect_info[1], p_protect_info[2]);
/* Retrieve the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer == NULL) {
APPL_TRACE_ERROR("%s: could not find peer entry", __func__);
return A2DP_FAIL;
}
APPL_TRACE_DEBUG("%s: peer(o=%d,n_sinks=%d,n_rx_sinks=%d,n_sup_sinks=%d)",
__func__, p_peer->opened, p_peer->num_srcs,
p_peer->num_rx_srcs, p_peer->num_sup_srcs);
p_peer->num_rx_srcs++;
/* Check the peer's SOURCE codec */
if (A2DP_IsPeerSourceCodecValid(p_codec_info)) {
/* If there is room for a new one */
if (p_peer->num_sup_srcs < BTA_AV_CO_NUM_ELEMENTS(p_peer->srcs)) {
tBTA_AV_CO_SINK* p_src = &p_peer->srcs[p_peer->num_sup_srcs++];
APPL_TRACE_DEBUG("%s: saved caps[%x:%x:%x:%x:%x:%x]", __func__,
p_codec_info[1], p_codec_info[2], p_codec_info[3],
p_codec_info[4], p_codec_info[5], p_codec_info[6]);
memcpy(p_src->codec_caps, p_codec_info, AVDT_CODEC_SIZE);
p_src->sep_info_idx = *p_sep_info_idx;
p_src->seid = seid;
p_src->num_protect = *p_num_protect;
memcpy(p_src->protect_info, p_protect_info, AVDT_CP_INFO_LEN);
} else {
APPL_TRACE_ERROR("%s: no more room for SRC info", __func__);
}
}
/* If last SINK get capabilities or all supported codec caps retrieved */
if ((p_peer->num_rx_srcs == p_peer->num_srcs) ||
(p_peer->num_sup_srcs == BTA_AV_CO_NUM_ELEMENTS(p_peer->srcs))) {
APPL_TRACE_DEBUG("%s: last SRC reached", __func__);
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
/* Find a src that matches the codec config */
const tBTA_AV_CO_SINK* p_src =
bta_av_co_find_peer_src_supports_codec(p_peer);
if (p_src != NULL) {
APPL_TRACE_DEBUG("%s: codec supported", __func__);
/* Build the codec configuration for this sink */
{
/* Save the new configuration */
p_peer->p_src = p_src;
/* get preferred config from src_caps */
if (A2DP_BuildSrc2SinkConfig(p_src->codec_caps, pref_config) !=
A2DP_SUCCESS) {
mutex_global_unlock();
return A2DP_FAIL;
}
memcpy(p_peer->codec_config, pref_config, AVDT_CODEC_SIZE);
APPL_TRACE_DEBUG("%s: p_codec_info[%x:%x:%x:%x:%x:%x]", __func__,
p_peer->codec_config[1], p_peer->codec_config[2],
p_peer->codec_config[3], p_peer->codec_config[4],
p_peer->codec_config[5], p_peer->codec_config[6]);
/* By default, no content protection */
*p_num_protect = 0;
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
p_peer->cp_active = false;
bta_av_co_cb.cp.active = false;
#endif
*p_sep_info_idx = p_src->sep_info_idx;
memcpy(p_codec_info, p_peer->codec_config, AVDT_CODEC_SIZE);
result = A2DP_SUCCESS;
}
}
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_unlock();
}
return result;
}
/*******************************************************************************
**
** Function bta_av_co_audio_getconfig
**
** Description This callout function is executed by AV to retrieve the
** desired codec and content protection configuration for the
** audio stream.
**
**
** Returns Stream codec and content protection configuration info.
**
*******************************************************************************/
tA2DP_STATUS bta_av_co_audio_getconfig(tBTA_AV_HNDL hndl, uint8_t* p_codec_info,
uint8_t* p_sep_info_idx, uint8_t seid,
uint8_t* p_num_protect,
uint8_t* p_protect_info) {
tA2DP_STATUS result = A2DP_FAIL;
tBTA_AV_CO_PEER* p_peer;
uint8_t codec_config[AVDT_CODEC_SIZE];
APPL_TRACE_DEBUG("%s", __func__);
/* Retrieve the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer == NULL) {
APPL_TRACE_ERROR("%s: could not find peer entry", __func__);
return A2DP_FAIL;
}
if (p_peer->uuid_to_connect == UUID_SERVCLASS_AUDIO_SOURCE) {
result = bta_av_audio_sink_getconfig(hndl, p_codec_info, p_sep_info_idx,
seid, p_num_protect, p_protect_info);
return result;
}
APPL_TRACE_DEBUG("%s: handle:0x%x codec:%s seid:%d", __func__, hndl,
A2DP_CodecName(p_codec_info), seid);
APPL_TRACE_DEBUG("%s: num_protect:0x%02x protect_info:0x%02x%02x%02x",
__func__, *p_num_protect, p_protect_info[0],
p_protect_info[1], p_protect_info[2]);
APPL_TRACE_DEBUG("%s: peer(o=%d, n_sinks=%d, n_rx_sinks=%d, n_sup_sinks=%d)",
__func__, p_peer->opened, p_peer->num_sinks,
p_peer->num_rx_sinks, p_peer->num_sup_sinks);
p_peer->num_rx_sinks++;
/* Check the peer's SINK codec */
if (A2DP_IsPeerSinkCodecValid(p_codec_info)) {
/* If there is room for a new one */
if (p_peer->num_sup_sinks < BTA_AV_CO_NUM_ELEMENTS(p_peer->sinks)) {
tBTA_AV_CO_SINK* p_sink = &p_peer->sinks[p_peer->num_sup_sinks++];
APPL_TRACE_DEBUG("%s: saved caps[%x:%x:%x:%x:%x:%x]", __func__,
p_codec_info[1], p_codec_info[2], p_codec_info[3],
p_codec_info[4], p_codec_info[5], p_codec_info[6]);
memcpy(p_sink->codec_caps, p_codec_info, AVDT_CODEC_SIZE);
p_sink->sep_info_idx = *p_sep_info_idx;
p_sink->seid = seid;
p_sink->num_protect = *p_num_protect;
memcpy(p_sink->protect_info, p_protect_info, AVDT_CP_INFO_LEN);
} else {
APPL_TRACE_ERROR("%s: no more room for SINK info", __func__);
}
}
/* If last SINK get capabilities or all supported codec capa retrieved */
if ((p_peer->num_rx_sinks == p_peer->num_sinks) ||
(p_peer->num_sup_sinks == BTA_AV_CO_NUM_ELEMENTS(p_peer->sinks))) {
APPL_TRACE_DEBUG("%s: last sink reached", __func__);
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
/* Find a sink that matches the codec config */
const tBTA_AV_CO_SINK* p_sink = NULL;
// Initial strawman codec selection mechanism: largest codec SEP index
// first.
// TODO: Replace this mechanism with a better one, and abstract it
// in a separate function.
for (int i = A2DP_CODEC_SEP_INDEX_SOURCE_MAX - 1;
i >= A2DP_CODEC_SEP_INDEX_SOURCE_MIN; i--) {
tA2DP_CODEC_SEP_INDEX source_codec_sep_index =
static_cast<tA2DP_CODEC_SEP_INDEX>(i);
APPL_TRACE_DEBUG("%s: trying codec %s with sep_index %d", __func__,
A2DP_CodecSepIndexStr(source_codec_sep_index), i);
tAVDT_CFG avdt_cfg;
if (!A2DP_InitCodecConfig(source_codec_sep_index, &avdt_cfg)) {
APPL_TRACE_DEBUG("%s: cannot setup source codec %s", __func__,
A2DP_CodecSepIndexStr(source_codec_sep_index));
continue;
}
p_sink = bta_av_co_find_peer_sink_supports_codec(avdt_cfg.codec_info,
p_peer);
if (p_sink == NULL)
continue;
// Found a preferred codec
APPL_TRACE_DEBUG("%s: selected codec %s", __func__,
A2DP_CodecName(avdt_cfg.codec_info));
memcpy(bta_av_co_cb.codec_config, avdt_cfg.codec_info, AVDT_CODEC_SIZE);
break;
}
if (p_sink == NULL) {
APPL_TRACE_ERROR("%s: cannot find peer SINK for this codec config",
__func__);
} else {
/* stop fetching caps once we retrieved a supported codec */
if (p_peer->acp) {
APPL_TRACE_EVENT("%s: no need to fetch more SEPs", __func__);
*p_sep_info_idx = p_peer->num_seps;
}
/* Build the codec configuration for this sink */
memset(codec_config, 0, AVDT_CODEC_SIZE);
if (A2DP_BuildSinkConfig(bta_av_co_cb.codec_config, p_sink->codec_caps,
codec_config) == A2DP_SUCCESS) {
APPL_TRACE_DEBUG("%s: reconfig codec_config[%x:%x:%x:%x:%x:%x]",
__func__, codec_config[1], codec_config[2],
codec_config[3], codec_config[4], codec_config[5],
codec_config[6]);
for (int i = 0; i < AVDT_CODEC_SIZE; i++) {
APPL_TRACE_DEBUG("%s: p_codec_info[%d]: %x", __func__, i,
p_codec_info[i]);
}
/* Save the new configuration */
p_peer->p_sink = p_sink;
memcpy(p_peer->codec_config, codec_config, AVDT_CODEC_SIZE);
/* By default, no content protection */
*p_num_protect = 0;
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
/* Check if this sink supports SCMS */
p_peer->cp_active = bta_av_co_audio_sink_has_scmst(p_sink);
bta_av_co_cb.cp.active = p_peer->cp_active;
if (p_peer->cp_active) {
*p_num_protect = AVDT_CP_INFO_LEN;
memcpy(p_protect_info, bta_av_co_cp_scmst, AVDT_CP_INFO_LEN);
}
#endif
/* If acceptor -> reconfig otherwise reply for configuration */
if (p_peer->acp) {
if (p_peer->reconfig_needed) {
APPL_TRACE_DEBUG("%s: call BTA_AvReconfig(x%x)", __func__, hndl);
BTA_AvReconfig(hndl, true, p_sink->sep_info_idx,
p_peer->codec_config, *p_num_protect,
bta_av_co_cp_scmst);
}
} else {
*p_sep_info_idx = p_sink->sep_info_idx;
memcpy(p_codec_info, p_peer->codec_config, AVDT_CODEC_SIZE);
}
result = A2DP_SUCCESS;
}
}
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_unlock();
}
return result;
}
/*******************************************************************************
**
** Function bta_av_co_audio_setconfig
**
** Description This callout function is executed by AV to set the codec
** and content protection configuration of the audio stream.
**
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_setconfig(tBTA_AV_HNDL hndl, const uint8_t* p_codec_info,
UNUSED_ATTR uint8_t seid,
UNUSED_ATTR BD_ADDR addr, uint8_t num_protect,
uint8_t* p_protect_info, uint8_t t_local_sep,
uint8_t avdt_handle) {
tBTA_AV_CO_PEER* p_peer;
tA2DP_STATUS status = A2DP_SUCCESS;
uint8_t category = A2DP_SUCCESS;
bool reconfig_needed = false;
APPL_TRACE_DEBUG("%s: p_codec_info[%x:%x:%x:%x:%x:%x]", __func__,
p_codec_info[1], p_codec_info[2], p_codec_info[3],
p_codec_info[4], p_codec_info[5], p_codec_info[6]);
APPL_TRACE_DEBUG("num_protect:0x%02x protect_info:0x%02x%02x%02x",
num_protect, p_protect_info[0], p_protect_info[1],
p_protect_info[2]);
/* Retrieve the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer == NULL) {
APPL_TRACE_ERROR("%s: could not find peer entry", __func__);
/* Call call-in rejecting the configuration */
bta_av_ci_setconfig(hndl, A2DP_BUSY, AVDT_ASC_CODEC, 0, NULL, false,
avdt_handle);
return;
}
APPL_TRACE_DEBUG("%s: peer(o=%d, n_sinks=%d, n_rx_sinks=%d, n_sup_sinks=%d)",
__func__, p_peer->opened, p_peer->num_sinks,
p_peer->num_rx_sinks, p_peer->num_sup_sinks);
/* Sanity check: should not be opened at this point */
if (p_peer->opened) {
APPL_TRACE_ERROR("%s: peer already in use", __func__);
}
if (num_protect != 0) {
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
/* If CP is supported */
if ((num_protect != 1) ||
(bta_av_co_cp_is_scmst(p_protect_info) == false)) {
APPL_TRACE_ERROR("%s: wrong CP configuration", __func__);
status = A2DP_BAD_CP_TYPE;
category = AVDT_ASC_PROTECT;
}
#else
/* Do not support content protection for the time being */
APPL_TRACE_ERROR("%s: wrong CP configuration", __func__);
status = A2DP_BAD_CP_TYPE;
category = AVDT_ASC_PROTECT;
#endif
}
if (status == A2DP_SUCCESS) {
bool codec_config_supported = false;
if (t_local_sep == AVDT_TSEP_SNK) {
APPL_TRACE_DEBUG("%s: peer is A2DP SRC", __func__);
codec_config_supported = A2DP_IsSinkCodecSupported(p_codec_info);
}
if (t_local_sep == AVDT_TSEP_SRC) {
APPL_TRACE_DEBUG("%s: peer is A2DP SINK", __func__);
codec_config_supported = A2DP_IsSourceCodecSupported(p_codec_info);
}
/* Check if codec configuration is supported */
if (codec_config_supported) {
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
/* Check if the configuration matches the current codec config */
if (A2DP_CodecRequiresReconfig(p_codec_info, bta_av_co_cb.codec_config)) {
reconfig_needed = true;
} else if ((num_protect == 1) && (!bta_av_co_cb.cp.active)) {
reconfig_needed = true;
}
memcpy(bta_av_co_cb.codec_config_setconfig, p_codec_info, AVDT_CODEC_SIZE);
if (t_local_sep == AVDT_TSEP_SNK) {
/*
* If Peer is SRC, and our config subset matches with what is
* requested by peer, then just accept what peer wants.
*/
memcpy(bta_av_co_cb.codec_config, p_codec_info, AVDT_CODEC_SIZE);
reconfig_needed = false;
}
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_unlock();
} else {
category = AVDT_ASC_CODEC;
status = A2DP_WRONG_CODEC;
}
}
if (status != A2DP_SUCCESS) {
APPL_TRACE_DEBUG("%s: reject s=%d c=%d", __func__, status, category);
/* Call call-in rejecting the configuration */
bta_av_ci_setconfig(hndl, status, category, 0, NULL, false, avdt_handle);
return;
}
/* Mark that this is an acceptor peer */
p_peer->acp = true;
p_peer->reconfig_needed = reconfig_needed;
APPL_TRACE_DEBUG("%s: accept reconf=%d", __func__, reconfig_needed);
/* Call call-in accepting the configuration */
bta_av_ci_setconfig(hndl, A2DP_SUCCESS, A2DP_SUCCESS, 0, NULL,
reconfig_needed, avdt_handle);
}
/*******************************************************************************
**
** Function bta_av_co_audio_open
**
** Description This function is called by AV when the audio stream
** connection is opened.
**
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_open(tBTA_AV_HNDL hndl, uint8_t* p_codec_info,
uint16_t mtu) {
tBTA_AV_CO_PEER* p_peer;
APPL_TRACE_DEBUG("%s: mtu:%d codec:%s", __func__, mtu,
A2DP_CodecName(p_codec_info));
/* Retrieve the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer == NULL) {
APPL_TRACE_ERROR("%s: could not find peer entry", __func__);
} else {
p_peer->opened = true;
p_peer->mtu = mtu;
}
}
/*******************************************************************************
**
** Function bta_av_co_audio_close
**
** Description This function is called by AV when the audio stream
** connection is closed.
**
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_close(tBTA_AV_HNDL hndl, UNUSED_ATTR uint16_t mtu)
{
tBTA_AV_CO_PEER* p_peer;
APPL_TRACE_DEBUG("%s", __func__);
/* Retrieve the peer info */
p_peer = bta_av_co_get_peer(hndl);
if (p_peer) {
/* Mark the peer closed and clean the peer info */
memset(p_peer, 0, sizeof(*p_peer));
} else {
APPL_TRACE_ERROR("%s: could not find peer entry", __func__);
}
/* reset remote preference through setconfig */
memset(bta_av_co_cb.codec_config_setconfig, 0,
sizeof(bta_av_co_cb.codec_config_setconfig));
}
/*******************************************************************************
**
** Function bta_av_co_audio_start
**
** Description This function is called by AV when the audio streaming data
** transfer is started.
**
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_start(UNUSED_ATTR tBTA_AV_HNDL hndl,
UNUSED_ATTR uint8_t* p_codec_info,
UNUSED_ATTR bool* p_no_rtp_hdr) {
APPL_TRACE_DEBUG("%s", __func__);
}
/*******************************************************************************
**
** Function bta_av_co_audio_stop
**
** Description This function is called by AV when the audio streaming data
** transfer is stopped.
**
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_stop(UNUSED_ATTR tBTA_AV_HNDL hndl) {
APPL_TRACE_DEBUG("%s", __func__);
}
/*******************************************************************************
**
** Function bta_av_co_audio_src_data_path
**
** Description This function is called to manage data transfer from
** the audio codec to AVDTP.
**
** Returns Pointer to the GKI buffer to send, NULL if no buffer to
** send
**
*******************************************************************************/
void* bta_av_co_audio_src_data_path(const uint8_t* p_codec_info,
uint32_t* p_timestamp) {
BT_HDR* p_buf;
APPL_TRACE_DEBUG("%s: codec: %s", __func__, A2DP_CodecName(p_codec_info));
p_buf = btif_a2dp_source_audio_readbuf();
if (p_buf == NULL) return NULL;
/*
* Retrieve the timestamp information from the media packet,
* and set up the packet header.
*
* In media packet, the following information is available:
* p_buf->layer_specific : number of audio frames in the packet
* p_buf->word[0] : timestamp
*/
if (!A2DP_GetPacketTimestamp(p_codec_info, (const uint8_t*)(p_buf + 1),
p_timestamp) ||
!A2DP_BuildCodecHeader(p_codec_info, p_buf, p_buf->layer_specific)) {
APPL_TRACE_ERROR("%s: unsupported codec type (%d)", __func__,
A2DP_GetCodecType(p_codec_info));
}
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
if (bta_av_co_cb.cp.active) {
p_buf->len++;
p_buf->offset--;
uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset;
*p = bta_av_co_cp_get_flag();
}
#endif
return p_buf;
}
/*******************************************************************************
**
** Function bta_av_co_audio_drop
**
** Description An Audio packet is dropped. .
** It's very likely that the connected headset with this
** handle is moved far away. The implementation may want to
** reduce the encoder bit rate setting to reduce the packet
** size.
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_drop(tBTA_AV_HNDL hndl) {
APPL_TRACE_ERROR("%s: dropped audio packet on handle 0x%x", __func__, hndl);
}
/*******************************************************************************
**
** Function bta_av_co_audio_delay
**
** Description This function is called by AV when the audio stream
** connection needs to send the initial delay report to the
** connected SRC.
**
**
** Returns void
**
*******************************************************************************/
void bta_av_co_audio_delay(tBTA_AV_HNDL hndl, uint16_t delay) {
APPL_TRACE_ERROR("%s: handle: x%x, delay:0x%x", __func__, hndl, delay);
}
/*******************************************************************************
**
** Function bta_av_co_cp_is_scmst
**
** Description Check if a content protection service is SCMS-T
**
** Returns true if this CP is SCMS-T, false otherwise
**
*******************************************************************************/
static bool bta_av_co_cp_is_scmst(const uint8_t* p_protectinfo) {
APPL_TRACE_DEBUG("%s", __func__);
if (*p_protectinfo >= AVDT_CP_LOSC) {
uint16_t cp_id;
p_protectinfo++;
STREAM_TO_UINT16(cp_id, p_protectinfo);
if (cp_id == AVDT_CP_SCMS_T_ID) {
APPL_TRACE_DEBUG("%s: SCMS-T found", __func__);
return true;
}
}
return false;
}
/*******************************************************************************
**
** Function bta_av_co_audio_sink_has_scmst
**
** Description Check if a sink supports SCMS-T
**
** Returns true if the sink supports this CP, false otherwise
**
*******************************************************************************/
static bool bta_av_co_audio_sink_has_scmst(const tBTA_AV_CO_SINK* p_sink) {
uint8_t index;
const uint8_t* p;
APPL_TRACE_DEBUG("%s", __func__);
/* Check if sink supports SCMS-T */
index = p_sink->num_protect;
p = &p_sink->protect_info[0];
while (index) {
if (bta_av_co_cp_is_scmst(p)) return true;
/* Move to the next SC */
p += *p + 1;
/* Decrement the SC counter */
index--;
}
APPL_TRACE_DEBUG("%s: SCMS-T not found", __func__);
return false;
}
/*******************************************************************************
**
** Function bta_av_co_audio_sink_supports_cp
**
** Description Check if a sink supports the current content protection
**
** Returns true if the sink supports this CP, false otherwise
**
*******************************************************************************/
static bool bta_av_co_audio_sink_supports_cp(const tBTA_AV_CO_SINK* p_sink) {
APPL_TRACE_DEBUG("%s", __func__);
/* Check if content protection is enabled for this stream */
if (bta_av_co_cp_get_flag() != AVDT_CP_SCMS_COPY_FREE)
return bta_av_co_audio_sink_has_scmst(p_sink);
APPL_TRACE_DEBUG("%s: not required", __func__);
return true;
}
/*******************************************************************************
**
** Function bta_av_co_find_peer_sink_supports_codec
**
** Description Find a peer acting as a sink that suppors codec config
**
** Returns The peer sink that supports the codec, otherwise NULL.
**
*******************************************************************************/
static const tBTA_AV_CO_SINK* bta_av_co_find_peer_sink_supports_codec(
const uint8_t* codec_config, const tBTA_AV_CO_PEER* p_peer) {
APPL_TRACE_DEBUG("%s: peer num_sup_sinks = %d", __func__,
p_peer->num_sup_sinks);
for (size_t index = 0; index < p_peer->num_sup_sinks; index++) {
if (A2DP_CodecConfigMatchesCapabilities(codec_config,
p_peer->sinks[index].codec_caps)) {
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
if (!bta_av_co_audio_sink_has_scmst(&p_peer->sinks[index]))
continue;
#endif
return &p_peer->sinks[index];
}
}
return NULL;
}
/*******************************************************************************
**
** Function bta_av_co_find_peer_src_supports_codec
**
** Description Find a peer acting as src that supports codec config
**
** Returns The peer source that supports the codec, otherwise NULL.
**
*******************************************************************************/
static const tBTA_AV_CO_SINK* bta_av_co_find_peer_src_supports_codec(
const tBTA_AV_CO_PEER* p_peer) {
APPL_TRACE_DEBUG("%s: peer num_sup_srcs = %d", __func__,
p_peer->num_sup_srcs);
for (size_t index = 0; index < p_peer->num_sup_srcs; index++) {
const uint8_t* p_codec_caps = p_peer->srcs[index].codec_caps;
if (A2DP_CodecTypeEquals(bta_av_co_cb.codec_config, p_codec_caps) &&
A2DP_IsPeerSourceCodecSupported(p_codec_caps)) {
return &p_peer->srcs[index];
}
}
return NULL;
}
/*******************************************************************************
**
** Function bta_av_co_audio_set_codec
**
** Description Set the current codec configuration from the feeding type.
** This function is starting to modify the configuration, it
** should be protected.
**
** Returns true if successful, false otherwise
**
*******************************************************************************/
bool bta_av_co_audio_set_codec(const tA2DP_FEEDING_PARAMS* p_feeding_params) {
uint8_t new_config[AVDT_CODEC_SIZE];
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
// Initial strawman codec selection mechanism: largest codec SEP index first.
// TODO: Replace this mechanism with a better one.
for (int i = A2DP_CODEC_SEP_INDEX_SOURCE_MAX - 1;
i >= A2DP_CODEC_SEP_INDEX_SOURCE_MIN; i--) {
tA2DP_CODEC_SEP_INDEX source_codec_sep_index =
static_cast<tA2DP_CODEC_SEP_INDEX>(i);
APPL_TRACE_DEBUG("%s: trying codec %s with sep_index %d", __func__,
A2DP_CodecSepIndexStr(source_codec_sep_index), i);
if (!A2DP_SetSourceCodec(source_codec_sep_index, p_feeding_params,
new_config)) {
APPL_TRACE_DEBUG("%s: cannot setup source codec %s", __func__,
A2DP_CodecSepIndexStr(source_codec_sep_index));
continue;
}
/* Try to select an open device for the codec */
if (bta_av_co_audio_codec_selected(new_config)) {
APPL_TRACE_DEBUG("%s: selected codec %s with sep_index %d", __func__,
A2DP_CodecSepIndexStr(source_codec_sep_index), i);
mutex_global_unlock();
return true;
}
APPL_TRACE_DEBUG("%s: cannot select source codec %s", __func__,
A2DP_CodecSepIndexStr(source_codec_sep_index));
}
mutex_global_unlock();
return false;
}
// Select an open device for the given codec info.
// Return true if an open device was selected, otherwise false.
static bool bta_av_co_audio_codec_selected(const uint8_t* codec_config) {
APPL_TRACE_DEBUG("%s", __func__);
/* Check AV feeding is supported */
for (uint8_t index = 0; index < BTA_AV_CO_NUM_ELEMENTS(bta_av_co_cb.peers);
index++) {
uint8_t peer_codec_config[AVDT_CODEC_SIZE];
tBTA_AV_CO_PEER* p_peer = &bta_av_co_cb.peers[index];
if (!p_peer->opened) continue;
const tBTA_AV_CO_SINK* p_sink =
bta_av_co_find_peer_sink_supports_codec(codec_config, p_peer);
if (p_sink == NULL) {
APPL_TRACE_DEBUG("%s: index %d doesn't support codec", __func__, index);
continue;
}
/* Check that this sink is compatible with the CP */
if (!bta_av_co_audio_sink_supports_cp(p_sink)) {
APPL_TRACE_DEBUG("%s: sink of peer %d doesn't support cp", __func__,
index);
continue;
}
/* Build the codec configuration for this sink */
memset(peer_codec_config, 0, AVDT_CODEC_SIZE);
if (A2DP_BuildSinkConfig(codec_config, p_sink->codec_caps,
peer_codec_config) != A2DP_SUCCESS) {
continue;
}
/* The new config was correctly built and selected */
memcpy(bta_av_co_cb.codec_config, codec_config,
sizeof(bta_av_co_cb.codec_config));
/* Save the new configuration */
uint8_t num_protect = 0;
p_peer->p_sink = p_sink;
memcpy(p_peer->codec_config, peer_codec_config, AVDT_CODEC_SIZE);
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
/* Check if this sink supports SCMS */
bool cp_active = bta_av_co_audio_sink_has_scmst(p_sink);
bta_av_co_cb.cp.active = cp_active;
p_peer->cp_active = cp_active;
if (p_peer->cp_active) num_protect = AVDT_CP_INFO_LEN;
#endif
APPL_TRACE_DEBUG("%s: call BTA_AvReconfig(0x%x)", __func__,
BTA_AV_CO_AUDIO_INDX_TO_HNDL(index));
BTA_AvReconfig(BTA_AV_CO_AUDIO_INDX_TO_HNDL(index), true,
p_sink->sep_info_idx, p_peer->codec_config, num_protect,
bta_av_co_cp_scmst);
return true;
}
return false;
}
/*******************************************************************************
**
** Function bta_av_co_audio_codec_reset
**
** Description Reset the current codec configuration
**
** Returns void
**
*******************************************************************************/
static void bta_av_co_audio_codec_reset(void) {
APPL_TRACE_DEBUG("%s", __func__);
mutex_global_lock();
/* Reset the current configuration to the default codec */
A2DP_InitDefaultCodec(bta_av_co_cb.codec_config);
mutex_global_unlock();
}
void bta_av_co_audio_encoder_init(tA2DP_ENCODER_INIT_PARAMS* p_init_params) {
uint16_t min_mtu = 0xFFFF;
APPL_TRACE_DEBUG("%s", __func__);
assert(p_init_params != nullptr);
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
/* Compute the MTU */
for (size_t i = 0; i < BTA_AV_CO_NUM_ELEMENTS(bta_av_co_cb.peers); i++) {
const tBTA_AV_CO_PEER* p_peer = &bta_av_co_cb.peers[i];
if (!p_peer->opened) continue;
if (p_peer->mtu < min_mtu) min_mtu = p_peer->mtu;
}
const uint8_t* p_codec_info = bta_av_co_cb.codec_config;
p_init_params->NumOfSubBands = A2DP_GetNumberOfSubbands(p_codec_info);
p_init_params->NumOfBlocks = A2DP_GetNumberOfBlocks(p_codec_info);
p_init_params->AllocationMethod = A2DP_GetAllocationMethodCode(p_codec_info);
p_init_params->ChannelMode = A2DP_GetChannelModeCode(p_codec_info);
p_init_params->SamplingFreq = A2DP_GetSamplingFrequencyCode(p_codec_info);
p_init_params->MtuSize = min_mtu;
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_unlock();
}
void bta_av_co_audio_encoder_update(
tA2DP_ENCODER_UPDATE_PARAMS* p_update_params) {
uint16_t min_mtu = 0xFFFF;
APPL_TRACE_DEBUG("%s", __func__);
assert(p_update_params != nullptr);
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
const uint8_t* p_codec_info = bta_av_co_cb.codec_config;
int min_bitpool = A2DP_GetMinBitpool(p_codec_info);
int max_bitpool = A2DP_GetMaxBitpool(p_codec_info);
if ((min_bitpool < 0) || (max_bitpool < 0)) {
APPL_TRACE_ERROR("%s: Invalid min/max bitpool: [%d, %d]", __func__,
min_bitpool, max_bitpool);
mutex_global_unlock();
return;
}
for (size_t i = 0; i < BTA_AV_CO_NUM_ELEMENTS(bta_av_co_cb.peers); i++) {
const tBTA_AV_CO_PEER* p_peer = &bta_av_co_cb.peers[i];
if (!p_peer->opened) continue;
if (p_peer->mtu < min_mtu) min_mtu = p_peer->mtu;
for (int j = 0; j < p_peer->num_sup_sinks; j++) {
const tBTA_AV_CO_SINK* p_sink = &p_peer->sinks[j];
if (!A2DP_CodecTypeEquals(p_codec_info, p_sink->codec_caps)) continue;
/* Update the bitpool boundaries of the current config */
int peer_min_bitpool = A2DP_GetMinBitpool(p_sink->codec_caps);
int peer_max_bitpool = A2DP_GetMaxBitpool(p_sink->codec_caps);
if (peer_min_bitpool >= 0)
min_bitpool = BTA_AV_CO_MAX(min_bitpool, peer_min_bitpool);
if (peer_max_bitpool >= 0)
max_bitpool = BTA_AV_CO_MIN(max_bitpool, peer_max_bitpool);
APPL_TRACE_EVENT("%s: sink bitpool min %d, max %d", __func__, min_bitpool,
max_bitpool);
break;
}
}
/*
* Check if the remote Sink has a preferred bitpool range.
* Adjust our preferred bitpool with the remote preference if within
* our capable range.
*/
if (A2DP_IsSourceCodecValid(bta_av_co_cb.codec_config_setconfig) &&
A2DP_CodecTypeEquals(p_codec_info, bta_av_co_cb.codec_config_setconfig)) {
int setconfig_min_bitpool =
A2DP_GetMinBitpool(bta_av_co_cb.codec_config_setconfig);
int setconfig_max_bitpool =
A2DP_GetMaxBitpool(bta_av_co_cb.codec_config_setconfig);
if (setconfig_min_bitpool >= 0)
min_bitpool = BTA_AV_CO_MAX(min_bitpool, setconfig_min_bitpool);
if (setconfig_max_bitpool >= 0)
max_bitpool = BTA_AV_CO_MIN(max_bitpool, setconfig_max_bitpool);
APPL_TRACE_EVENT("%s: sink adjusted bitpool min %d, max %d", __func__,
min_bitpool, max_bitpool);
}
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_unlock();
if (min_bitpool > max_bitpool) {
APPL_TRACE_ERROR("%s: Irrational min/max bitpool: [%d, %d]", __func__,
min_bitpool, max_bitpool);
return;
}
p_update_params->MinMtuSize = min_mtu;
p_update_params->MinBitPool = min_bitpool;
p_update_params->MaxBitPool = max_bitpool;
}
const tA2DP_ENCODER_INTERFACE* bta_av_co_get_encoder_interface(void) {
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_lock();
const tA2DP_ENCODER_INTERFACE* encoder_interface =
A2DP_GetEncoderInterface(bta_av_co_cb.codec_config);
/* Protect access to bta_av_co_cb.codec_config */
mutex_global_unlock();
return encoder_interface;
}
/*******************************************************************************
**
** Function bta_av_co_init
**
** Description Initialization
**
** Returns Nothing
**
*******************************************************************************/
void bta_av_co_init(void) {
APPL_TRACE_DEBUG("%s", __func__);
/* Reset the control block */
memset(&bta_av_co_cb, 0, sizeof(bta_av_co_cb));
#if (BTA_AV_CO_CP_SCMS_T == TRUE)
bta_av_co_cp_set_flag(AVDT_CP_SCMS_COPY_NEVER);
#else
bta_av_co_cp_set_flag(AVDT_CP_SCMS_COPY_FREE);
#endif
/* Reset the current config */
bta_av_co_audio_codec_reset();
}