Merge branch 'feature/support_bt_avrcp_cover_art' into 'master'

feat(bt/bluedroid): Support BT AVRCP Cover Art

Closes BT-3231

See merge request espressif/esp-idf!32538
This commit is contained in:
Wang Meng Yang 2024-09-14 14:37:19 +08:00
commit cda2846558
43 changed files with 5220 additions and 71 deletions

View File

@ -1,4 +1,4 @@
[codespell] [codespell]
skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,components/wpa_supplicant/*,components/esp_wifi/*,*.pem skip = build,*.yuv,components/fatfs/src/*,alice.txt,*.rgb,components/wpa_supplicant/*,components/esp_wifi/*,*.pem
ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn ignore-words-list = ser,dout,rsource,fram,inout,shs,ans,aci,unstall,unstalling,hart,wheight,wel,ot,fane,assertIn,registr
write-changes = true write-changes = true

View File

@ -156,6 +156,8 @@ if(CONFIG_BT_ENABLED)
host/bluedroid/stack/avdt/include host/bluedroid/stack/avdt/include
host/bluedroid/stack/a2dp/include host/bluedroid/stack/a2dp/include
host/bluedroid/stack/rfcomm/include host/bluedroid/stack/rfcomm/include
host/bluedroid/stack/obex/include
host/bluedroid/stack/goep/include
host/bluedroid/stack/include host/bluedroid/stack/include
host/bluedroid/common/include host/bluedroid/common/include
host/bluedroid/config/include) host/bluedroid/config/include)
@ -183,6 +185,8 @@ if(CONFIG_BT_ENABLED)
"host/bluedroid/bta/av/bta_av_aact.c" "host/bluedroid/bta/av/bta_av_aact.c"
"host/bluedroid/bta/av/bta_av_act.c" "host/bluedroid/bta/av/bta_av_act.c"
"host/bluedroid/bta/av/bta_av_api.c" "host/bluedroid/bta/av/bta_av_api.c"
"host/bluedroid/bta/av/bta_av_ca_act.c"
"host/bluedroid/bta/av/bta_av_ca_sm.c"
"host/bluedroid/bta/av/bta_av_cfg.c" "host/bluedroid/bta/av/bta_av_cfg.c"
"host/bluedroid/bta/av/bta_av_ci.c" "host/bluedroid/bta/av/bta_av_ci.c"
"host/bluedroid/bta/av/bta_av_main.c" "host/bluedroid/bta/av/bta_av_main.c"
@ -378,6 +382,8 @@ if(CONFIG_BT_ENABLED)
"host/bluedroid/stack/gatt/gatt_sr.c" "host/bluedroid/stack/gatt/gatt_sr.c"
"host/bluedroid/stack/gatt/gatt_sr_hash.c" "host/bluedroid/stack/gatt/gatt_sr_hash.c"
"host/bluedroid/stack/gatt/gatt_utils.c" "host/bluedroid/stack/gatt/gatt_utils.c"
"host/bluedroid/stack/goep/goepc_api.c"
"host/bluedroid/stack/goep/goepc_main.c"
"host/bluedroid/stack/hcic/hciblecmds.c" "host/bluedroid/stack/hcic/hciblecmds.c"
"host/bluedroid/stack/hcic/hcicmds.c" "host/bluedroid/stack/hcic/hcicmds.c"
"host/bluedroid/stack/l2cap/l2c_api.c" "host/bluedroid/stack/l2cap/l2c_api.c"
@ -389,6 +395,9 @@ if(CONFIG_BT_ENABLED)
"host/bluedroid/stack/l2cap/l2c_ucd.c" "host/bluedroid/stack/l2cap/l2c_ucd.c"
"host/bluedroid/stack/l2cap/l2c_utils.c" "host/bluedroid/stack/l2cap/l2c_utils.c"
"host/bluedroid/stack/l2cap/l2cap_client.c" "host/bluedroid/stack/l2cap/l2cap_client.c"
"host/bluedroid/stack/obex/obex_api.c"
"host/bluedroid/stack/obex/obex_main.c"
"host/bluedroid/stack/obex/obex_tl_l2cap.c"
"host/bluedroid/stack/rfcomm/port_api.c" "host/bluedroid/stack/rfcomm/port_api.c"
"host/bluedroid/stack/rfcomm/port_rfc.c" "host/bluedroid/stack/rfcomm/port_rfc.c"
"host/bluedroid/stack/rfcomm/port_utils.c" "host/bluedroid/stack/rfcomm/port_utils.c"

View File

@ -84,9 +84,30 @@ config BT_A2DP_ENABLE
bool "A2DP" bool "A2DP"
depends on BT_CLASSIC_ENABLED depends on BT_CLASSIC_ENABLED
default n default n
select BT_AVRCP_ENABLED
help help
Advanced Audio Distribution Profile Advanced Audio Distribution Profile
config BT_AVRCP_ENABLED
bool
depends on BT_A2DP_ENABLE
default y
help
Audio/Video Remote Control Profile, AVRCP and A2DP are coupled in Bluedroid,
AVRCP still controlled by A2DP option, this is a dummy option currently
menu "AVRCP Features"
depends on BT_AVRCP_ENABLED
config BT_AVRCP_CT_COVER_ART_ENABLED
bool "AVRCP CT Cover Art"
default y
select BT_GOEPC_ENABLED
help
This enable Cover Art feature of AVRCP CT role
endmenu
config BT_SPP_ENABLED config BT_SPP_ENABLED
bool "SPP" bool "SPP"
depends on BT_CLASSIC_ENABLED depends on BT_CLASSIC_ENABLED
@ -143,13 +164,12 @@ endchoice
config BT_HFP_WBS_ENABLE config BT_HFP_WBS_ENABLE
bool "Wide Band Speech" bool "Wide Band Speech"
depends on BT_HFP_AUDIO_DATA_PATH_HCI depends on BT_HFP_ENABLE && BT_HFP_AUDIO_DATA_PATH_HCI
default y default y
help help
This enables Wide Band Speech. Should disable it when SCO data path is PCM. This enables Wide Band Speech. Should disable it when SCO data path is PCM.
Otherwise there will be no data transmitted via GPIOs. Otherwise there will be no data transmitted via GPIOs.
menuconfig BT_HID_ENABLED menuconfig BT_HID_ENABLED
bool "Classic BT HID" bool "Classic BT HID"
depends on BT_CLASSIC_ENABLED depends on BT_CLASSIC_ENABLED
@ -170,6 +190,13 @@ config BT_HID_DEVICE_ENABLED
help help
This enables the BT HID Device This enables the BT HID Device
config BT_GOEPC_ENABLED
bool
depends on BT_CLASSIC_ENABLED
default n
help
This enables the BT GOEP Profile Client role
config BT_BLE_ENABLED config BT_BLE_ENABLED
bool "Bluetooth Low Energy" bool "Bluetooth Low Energy"
depends on BT_BLUEDROID_ENABLED depends on BT_BLUEDROID_ENABLED

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -232,6 +232,149 @@ esp_err_t esp_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code, uint8_t
return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL; return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
} }
#if BTC_AV_CA_INCLUDED
esp_err_t esp_avrc_ct_cover_art_connect(uint16_t mtu)
{
if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) ||
(!btc_avrc_ct_connected_p())) {
return ESP_ERR_INVALID_STATE;
}
if (!btc_avrc_ct_check_cover_art_support()) {
return ESP_ERR_NOT_SUPPORTED;
}
if (mtu > ESP_AVRC_CA_MTU_MAX || mtu < ESP_AVRC_CA_MTU_MIN) {
mtu = ESP_AVRC_CA_MTU_MAX;
}
btc_msg_t msg;
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_AVRC_CT;
msg.act = BTC_AVRC_CT_API_COVER_ART_CONNECT_EVT;
btc_avrc_args_t arg;
memset(&arg, 0, sizeof(btc_avrc_args_t));
arg.ca_conn.mtu = mtu;
/* Switch to BTC context */
bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL, NULL);
return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_avrc_ct_cover_art_disconnect(void)
{
if (esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) {
return ESP_ERR_INVALID_STATE;
}
if (!btc_avrc_ct_check_cover_art_support()) {
return ESP_ERR_NOT_SUPPORTED;
}
btc_msg_t msg;
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_AVRC_CT;
msg.act = BTC_AVRC_CT_API_COVER_ART_DISCONNECT_EVT;
/* Switch to BTC context */
bt_status_t stat = btc_transfer_context(&msg, NULL, 0, NULL, NULL);
return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_avrc_ct_cover_art_get_image_properties(uint8_t *image_handle)
{
if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) ||
(!btc_avrc_ct_connected_p())) {
return ESP_ERR_INVALID_STATE;
}
if (!btc_avrc_ct_check_cover_art_support()) {
return ESP_ERR_NOT_SUPPORTED;
}
if (image_handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
btc_msg_t msg;
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_AVRC_CT;
msg.act = BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_PROPERTIES_EVT;
btc_avrc_args_t arg;
memset(&arg, 0, sizeof(btc_avrc_args_t));
memcpy(arg.ca_get_img_prop.image_handle, image_handle, ESP_AVRC_CA_IMAGE_HANDLE_LEN);
/* Switch to BTC context */
bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL, NULL);
return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_avrc_ct_cover_art_get_image(uint8_t *image_handle, uint8_t *image_descriptor, uint16_t image_descriptor_len)
{
if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) ||
(!btc_avrc_ct_connected_p())) {
return ESP_ERR_INVALID_STATE;
}
if (!btc_avrc_ct_check_cover_art_support()) {
return ESP_ERR_NOT_SUPPORTED;
}
if (image_handle == NULL || image_descriptor == NULL || image_descriptor_len == 0) {
return ESP_ERR_INVALID_ARG;
}
btc_msg_t msg;
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_AVRC_CT;
msg.act = BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT;
btc_avrc_args_t arg;
memset(&arg, 0, sizeof(btc_avrc_args_t));
memcpy(arg.ca_get_img.image_handle, image_handle, ESP_AVRC_CA_IMAGE_HANDLE_LEN);
arg.ca_get_img.image_descriptor_len = image_descriptor_len;
arg.ca_get_img.image_descriptor = image_descriptor;
/* Switch to BTC context */
bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), btc_avrc_arg_deep_copy, btc_avrc_arg_deep_free);
return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
}
esp_err_t esp_avrc_ct_cover_art_get_linked_thumbnail(uint8_t *image_handle)
{
if ((esp_bluedroid_get_status() != ESP_BLUEDROID_STATUS_ENABLED) ||
(!btc_avrc_ct_connected_p())) {
return ESP_ERR_INVALID_STATE;
}
if (!btc_avrc_ct_check_cover_art_support()) {
return ESP_ERR_NOT_SUPPORTED;
}
if (image_handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
btc_msg_t msg;
msg.sig = BTC_SIG_API_CALL;
msg.pid = BTC_PID_AVRC_CT;
msg.act = BTC_AVRC_CT_API_COVER_ART_GET_LINKED_THUMBNAIL_EVT;
btc_avrc_args_t arg;
memset(&arg, 0, sizeof(btc_avrc_args_t));
memcpy(arg.ca_get_lk_thn.image_handle, image_handle, ESP_AVRC_CA_IMAGE_HANDLE_LEN);
/* Switch to BTC context */
bt_status_t stat = btc_transfer_context(&msg, &arg, sizeof(btc_avrc_args_t), NULL, NULL);
return (stat == BT_STATUS_SUCCESS) ? ESP_OK : ESP_FAIL;
}
#endif /* #if BTC_AV_CA_INCLUDED */
/*********************************************************************************************/ /*********************************************************************************************/
/** following is the API of AVRCP target role **/ /** following is the API of AVRCP target role **/
/*********************************************************************************************/ /*********************************************************************************************/

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -18,6 +18,10 @@ extern "C" {
#define ESP_AVRC_TRANS_LABEL_MAX 15 /*!< max transaction label */ #define ESP_AVRC_TRANS_LABEL_MAX 15 /*!< max transaction label */
#define ESP_AVRC_CA_IMAGE_HANDLE_LEN 7 /* The image handle length is fixed to 7, specified by Basic Image Profile */
#define ESP_AVRC_CA_MTU_MIN 255 /* Minimal MTU can be used in Cover Art OBEX connection */
#define ESP_AVRC_CA_MTU_MAX 1691 /* Maximum MTU can be used in Cover Art OBEX connection */
/// AVRC feature bit mask /// AVRC feature bit mask
typedef enum { typedef enum {
ESP_AVRC_FEAT_RCTG = 0x0001, /*!< remote control target */ ESP_AVRC_FEAT_RCTG = 0x0001, /*!< remote control target */
@ -30,14 +34,18 @@ typedef enum {
/// AVRC supported features flag retrieved in SDP record /// AVRC supported features flag retrieved in SDP record
typedef enum { typedef enum {
/* CT and TG common features flag */
ESP_AVRC_FEAT_FLAG_CAT1 = 0x0001, /*!< category 1 */ ESP_AVRC_FEAT_FLAG_CAT1 = 0x0001, /*!< category 1 */
ESP_AVRC_FEAT_FLAG_CAT2 = 0x0002, /*!< category 2 */ ESP_AVRC_FEAT_FLAG_CAT2 = 0x0002, /*!< category 2 */
ESP_AVRC_FEAT_FLAG_CAT3 = 0x0004, /*!< category 3 */ ESP_AVRC_FEAT_FLAG_CAT3 = 0x0004, /*!< category 3 */
ESP_AVRC_FEAT_FLAG_CAT4 = 0x0008, /*!< category 4 */ ESP_AVRC_FEAT_FLAG_CAT4 = 0x0008, /*!< category 4 */
ESP_AVRC_FEAT_FLAG_BROWSING = 0x0040, /*!< browsing */ ESP_AVRC_FEAT_FLAG_BROWSING = 0x0040, /*!< browsing */
ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE_PROP = 0x0080, /*!< Cover Art GetImageProperties */ /* CT only features flag */
ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE = 0x0100, /*!< Cover Art GetImage */ ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE_PROP = 0x0080, /*!< CT support Cover Art GetImageProperties */
ESP_AVRC_FEAT_FLAG_COVER_ART_GET_LINKED_THUMBNAIL = 0x0200, /*!< Cover Art GetLinkedThumbnail */ ESP_AVRC_FEAT_FLAG_COVER_ART_GET_IMAGE = 0x0100, /*!< CT support Cover Art GetImage */
ESP_AVRC_FEAT_FLAG_COVER_ART_GET_LINKED_THUMBNAIL = 0x0200, /*!< CT support Cover Art GetLinkedThumbnail */
/* TG only features flag */
ESP_AVRC_FEAT_FLAG_TG_COVER_ART = 0x0100, /*!< TG support Cover Art */
} esp_avrc_feature_flag_t; } esp_avrc_feature_flag_t;
/// AVRC passthrough command code /// AVRC passthrough command code
@ -133,8 +141,10 @@ typedef enum {
ESP_AVRC_CT_PLAY_STATUS_RSP_EVT = 3, /*!< play status response event */ ESP_AVRC_CT_PLAY_STATUS_RSP_EVT = 3, /*!< play status response event */
ESP_AVRC_CT_CHANGE_NOTIFY_EVT = 4, /*!< notification event */ ESP_AVRC_CT_CHANGE_NOTIFY_EVT = 4, /*!< notification event */
ESP_AVRC_CT_REMOTE_FEATURES_EVT = 5, /*!< feature of remote device indication event */ ESP_AVRC_CT_REMOTE_FEATURES_EVT = 5, /*!< feature of remote device indication event */
ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT = 6, /*!< supported notification events capability of peer device */ ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT = 6, /*!< supported notification events capability of peer device */
ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT = 7, /*!< set absolute volume response event */ ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT = 7, /*!< set absolute volume response event */
ESP_AVRC_CT_COVER_ART_STATE_EVT = 8, /*!< cover art client connection state changed event */
ESP_AVRC_CT_COVER_ART_DATA_EVT = 9, /*!< cover art client data event */
} esp_avrc_ct_cb_event_t; } esp_avrc_ct_cb_event_t;
/// AVRC Target callback events /// AVRC Target callback events
@ -155,7 +165,8 @@ typedef enum {
ESP_AVRC_MD_ATTR_TRACK_NUM = 0x8, /*!< track position on the album */ ESP_AVRC_MD_ATTR_TRACK_NUM = 0x8, /*!< track position on the album */
ESP_AVRC_MD_ATTR_NUM_TRACKS = 0x10, /*!< number of tracks on the album */ ESP_AVRC_MD_ATTR_NUM_TRACKS = 0x10, /*!< number of tracks on the album */
ESP_AVRC_MD_ATTR_GENRE = 0x20, /*!< track genre */ ESP_AVRC_MD_ATTR_GENRE = 0x20, /*!< track genre */
ESP_AVRC_MD_ATTR_PLAYING_TIME = 0x40 /*!< total album playing time in miliseconds */ ESP_AVRC_MD_ATTR_PLAYING_TIME = 0x40, /*!< total album playing time in milliseconds */
ESP_AVRC_MD_ATTR_COVER_ART = 0x80 /*!< cover art image handle */
} esp_avrc_md_attr_mask_t; } esp_avrc_md_attr_mask_t;
/// AVRC event notification ids /// AVRC event notification ids
@ -261,6 +272,12 @@ typedef enum {
ESP_AVRC_PLAYBACK_ERROR = 0xFF, /*!< error */ ESP_AVRC_PLAYBACK_ERROR = 0xFF, /*!< error */
} esp_avrc_playback_stat_t; } esp_avrc_playback_stat_t;
/// AVRC Cover Art connection error code
typedef enum {
ESP_AVRC_COVER_ART_DISCONNECTED, /*!< Cover Art connection disconnected or connection failed */
ESP_AVRC_COVER_ART_CONNECTED, /*!< Cover Art connection established */
} esp_avrc_cover_art_conn_state_t;
/// AVRCP notification parameters /// AVRCP notification parameters
typedef union typedef union
{ {
@ -337,6 +354,24 @@ typedef union {
struct avrc_ct_set_volume_rsp_param { struct avrc_ct_set_volume_rsp_param {
uint8_t volume; /*!< the volume which has actually been set, range is 0 to 0x7f, means 0% to 100% */ uint8_t volume; /*!< the volume which has actually been set, range is 0 to 0x7f, means 0% to 100% */
} set_volume_rsp; /*!< set absolute volume response event */ } set_volume_rsp; /*!< set absolute volume response event */
/**
* @brief ESP_AVRC_CT_COVER_ART_STATE_EVT
*/
struct avrc_ct_cover_art_state_param {
esp_avrc_cover_art_conn_state_t state; /*!< indicate the Cover Art connection status */
esp_bt_status_t reason; /*!< the disconnect reason of Cover Art connection */
} cover_art_state; /*!< AVRC Cover Art connection state change event */
/**
* @brief ESP_AVRC_CT_COVER_ART_DATA_EVT
*/
struct avrc_ct_cover_art_data_param {
esp_bt_status_t status; /*!< indicate whether the get operation is success, p_data is valid only when status is ESP_BT_STATUS_SUCCESS */
bool final; /*!< indicate whether this data event is the final one, true if we have received the entire object */
uint16_t data_len; /*!< the data length of this data event, in bytes */
uint8_t *p_data; /*!< pointer to data, should copy to other buff before event callback return */
} cover_art_data; /*!< AVRC Cover Art data event */
} esp_avrc_ct_cb_param_t; } esp_avrc_ct_cb_param_t;
/// AVRC target callback parameters /// AVRC target callback parameters
@ -656,11 +691,11 @@ bool esp_avrc_psth_bit_mask_operation(esp_avrc_bit_mask_op_t op, esp_avrc_psth_b
/** /**
* *
* @brief Get the requested event notification capabilies on local AVRC target. The capability is returned * @brief Get the requested event notification capabilities on local AVRC target. The capability is returned
* in a bit mask representation in evt_set. This function should be called after esp_avrc_tg_init(). * in a bit mask representation in evt_set. This function should be called after esp_avrc_tg_init().
* *
* For capability type "ESP_AVRC_RN_CAP_ALLOWED_EVT, the retrieved event set is constant and * For capability type "ESP_AVRC_RN_CAP_ALLOWED_EVT, the retrieved event set is constant and
* it covers all of the notifcation events that can possibly be supported with current * it covers all of the notification events that can possibly be supported with current
* implementation. * implementation.
* *
* For capability type ESP_AVRC_RN_CAP_SUPPORTED_EVT, the event set covers the notification * For capability type ESP_AVRC_RN_CAP_SUPPORTED_EVT, the event set covers the notification
@ -729,6 +764,92 @@ bool esp_avrc_rn_evt_bit_mask_operation(esp_avrc_bit_mask_op_t op, esp_avrc_rn_e
esp_err_t esp_avrc_tg_send_rn_rsp(esp_avrc_rn_event_ids_t event_id, esp_avrc_rn_rsp_t rsp, esp_err_t esp_avrc_tg_send_rn_rsp(esp_avrc_rn_event_ids_t event_id, esp_avrc_rn_rsp_t rsp,
esp_avrc_rn_param_t *param); esp_avrc_rn_param_t *param);
/**
*
* @brief Start the process to establish OBEX connection used in Cover Art Client. Once the operation done,
* ESP_AVRC_CT_COVER_ART_STATE_EVT will come, operation result can be found in event param. This API
* can be used only when AVRC Cover Art feature is enabled.
*
* @param[in] mtu: MTU used in lower level connection, should not smaller than ESP_AVRC_CA_MTU_MIN or larger than
* ESP_AVRC_CA_MTU_MAX, if value is not valid, will be reset to ESP_AVRC_CA_MTU_MAX. This can limit
* the max data length in cover_art_data event.
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized
* - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function
*
*/
esp_err_t esp_avrc_ct_cover_art_connect(uint16_t mtu);
/**
*
* @brief Start the process to release the OBEX connection used in Cover Art Client.Once the operation done,
* ESP_AVRC_CT_COVER_ART_STATE_EVT will come, operation result can be found in event param. This API
* can be used only when AVRC Cover Art feature is enabled.
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized
* - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function
*
*/
esp_err_t esp_avrc_ct_cover_art_disconnect(void);
/**
*
* @brief Start the process to get image properties from Cover Art server. This API can be used only when AVRC
* Cover Art feature is enabled.
*
* @param[in] image_handle: pointer to image handle with a length of ESP_AVRC_CA_IMAGE_HANDLE_LEN bytes, can be freed
* after this function return
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized
* - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function
*
*/
esp_err_t esp_avrc_ct_cover_art_get_image_properties(uint8_t *image_handle);
/**
*
* @brief Start the process to get image from Cover Art server. This API can be used only when AVRC Cover Art
* feature is enabled.
*
* @param[in] image_handle: pointer to image handle with a length of ESP_AVRC_CA_IMAGE_HANDLE_LEN bytes, can be freed
* after this function return
*
* @param[in] image_descriptor: pointer to image descriptor, will be cache internally by bluetooth stack, can be freed
* once this api return
*
* @param[in] image_descriptor_len: the length of image descriptor
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized
* - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function
*
*/
esp_err_t esp_avrc_ct_cover_art_get_image(uint8_t *image_handle, uint8_t *image_descriptor, uint16_t image_descriptor_len);
/**
*
* @brief Start the process to get linked thumbnail from Cover Art server. This API can be used only when AVRC
* Cover Art feature is enabled.
*
* @param[in] image_handle: pointer to image handle with a length of ESP_AVRC_CA_IMAGE_HANDLE_LEN bytes, can be freed
* after this function return
*
* @return
* - ESP_OK: success
* - ESP_ERR_INVALID_STATE: if bluetooth stack is not enabled or AVRC CT is not initialized
* - ESP_ERR_NOT_SUPPORTED: if peer device does not support Cover Art function
*
*/
esp_err_t esp_avrc_ct_cover_art_get_linked_thumbnail(uint8_t *image_handle);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -37,6 +37,10 @@
#if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE) #if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE)
#include "bta/bta_ar_api.h" #include "bta/bta_ar_api.h"
#endif #endif
#if BTA_AV_CA_INCLUDED
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#endif
#define LOG_TAG "bt_bta_av" #define LOG_TAG "bt_bta_av"
// #include "osi/include/log.h" // #include "osi/include/log.h"
@ -98,6 +102,10 @@ void bta_av_del_rc(tBTA_AV_RCB *p_rcb)
p_scb = NULL; p_scb = NULL;
if (p_rcb->handle != BTA_AV_RC_HANDLE_NONE) { if (p_rcb->handle != BTA_AV_RC_HANDLE_NONE) {
#if BTA_AV_CA_INCLUDED
/* reset cover art state */
bta_av_ca_reset(p_rcb);
#endif
if (p_rcb->shdl) { if (p_rcb->shdl) {
/* Validate array index*/ /* Validate array index*/
if ((p_rcb->shdl - 1) < BTA_AV_NUM_STRS) { if ((p_rcb->shdl - 1) < BTA_AV_NUM_STRS) {
@ -127,9 +135,6 @@ void bta_av_del_rc(tBTA_AV_RCB *p_rcb)
} }
/* else ACP && connected. do not clear the handle yet */ /* else ACP && connected. do not clear the handle yet */
AVRC_Close(rc_handle); AVRC_Close(rc_handle);
if (rc_handle == bta_av_cb.rc_acp_handle) {
bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE;
}
APPL_TRACE_EVENT("end del_rc handle: %d status=0x%x, rc_acp_handle:%d, lidx:%d", APPL_TRACE_EVENT("end del_rc handle: %d status=0x%x, rc_acp_handle:%d, lidx:%d",
p_rcb->handle, p_rcb->status, bta_av_cb.rc_acp_handle, p_rcb->lidx); p_rcb->handle, p_rcb->status, bta_av_cb.rc_acp_handle, p_rcb->lidx);
} }
@ -302,7 +307,7 @@ UINT8 bta_av_rc_create(tBTA_AV_CB *p_cb, UINT8 role, UINT8 shdl, UINT8 lidx)
bda = p_scb->peer_addr; bda = p_scb->peer_addr;
status = BTA_AV_RC_ROLE_INT; status = BTA_AV_RC_ROLE_INT;
} else { } else {
if ((p_rcb = bta_av_get_rcb_by_shdl(shdl)) != NULL ) { if (shdl != 0 && ((p_rcb = bta_av_get_rcb_by_shdl(shdl)) != NULL)) {
APPL_TRACE_ERROR("bta_av_rc_create ACP handle exist for shdl:%d", shdl); APPL_TRACE_ERROR("bta_av_rc_create ACP handle exist for shdl:%d", shdl);
return p_rcb->handle; return p_rcb->handle;
} }
@ -1143,7 +1148,7 @@ void bta_av_conn_chg(tBTA_AV_DATA *p_data)
p_data->conn_chg.peer_addr[5]); p_data->conn_chg.peer_addr[5]);
if (p_lcb_rc->conn_msk && bdcmp(p_lcb_rc->addr, p_data->conn_chg.peer_addr) == 0) { if (p_lcb_rc->conn_msk && bdcmp(p_lcb_rc->addr, p_data->conn_chg.peer_addr) == 0) {
/* AVRCP is already connected. /* AVRCP is already connected.
* need to update the association betwen SCB and RCB */ * need to update the association between SCB and RCB */
p_lcb_rc->conn_msk = 0; /* indicate RC ONLY is not connected */ p_lcb_rc->conn_msk = 0; /* indicate RC ONLY is not connected */
p_lcb_rc->lidx = 0; p_lcb_rc->lidx = 0;
p_scb->rc_handle = p_cb->rc_acp_handle; p_scb->rc_handle = p_cb->rc_acp_handle;
@ -1484,6 +1489,51 @@ static void bta_av_acp_sig_timer_cback (TIMER_LIST_ENT *p_tle)
} }
} }
#if BTA_AV_CA_INCLUDED
/*******************************************************************************
**
** Function bta_av_extra_tg_cover_art_l2cap_psm
**
** Description Extra the AVRC Cover Art L2CAP PSM of peer device from the
** SDP record
**
** Returns void
**
*******************************************************************************/
static UINT16 bta_av_extra_tg_cover_art_l2cap_psm(void)
{
tBTA_AV_CB *p_cb = &bta_av_cb;
tSDP_DISC_REC *p_rec = NULL;
tSDP_DISC_ATTR *p_add_prot_desc, *p_prot_desc;
tSDP_PROTOCOL_ELEM elem_l2cap, elem_obex;
UINT16 l2cap_psm = 0;
while (TRUE) {
/* get next record; if none found, we're done */
if ((p_rec = SDP_FindServiceInDb(p_cb->p_disc_db, UUID_SERVCLASS_AV_REM_CTRL_TARGET, p_rec)) == NULL) {
break;
}
p_add_prot_desc = SDP_FindAttributeInRec(p_rec, ATTR_ID_ADDITION_PROTO_DESC_LISTS);
if ((p_add_prot_desc != NULL) && (SDP_DISC_ATTR_TYPE(p_add_prot_desc->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE)) {
/* Walk through all protocol descriptor list */
for (p_prot_desc = p_add_prot_desc->attr_value.v.p_sub_attr; p_prot_desc; p_prot_desc = p_prot_desc->p_next_attr) {
if(SDP_FindProtocolListElem(p_prot_desc, UUID_PROTOCOL_L2CAP, &elem_l2cap)
&& SDP_FindProtocolListElem(p_prot_desc, UUID_PROTOCOL_OBEX, &elem_obex))
{
/* found */
l2cap_psm = elem_l2cap.params[0];
break;
}
}
}
}
return l2cap_psm;
}
#endif /* BTA_AV_CA_INCLUDED */
/******************************************************************************* /*******************************************************************************
** **
** Function bta_av_check_peer_rc_features ** Function bta_av_check_peer_rc_features
@ -1539,6 +1589,10 @@ tBTA_AV_FEAT bta_av_check_peer_rc_features (UINT16 service_uuid, UINT16 *rc_feat
if (categories & AVRC_SUPF_CT_BROWSE) { if (categories & AVRC_SUPF_CT_BROWSE) {
peer_features |= (BTA_AV_FEAT_BROWSE); peer_features |= (BTA_AV_FEAT_BROWSE);
} }
if ((service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET) && (categories & AVRC_SUPF_TG_COVER_ART)) {
/* remote target support cover art */
peer_features |= BTA_AV_FEAT_COVER_ART;
}
} }
} }
} }
@ -1573,6 +1627,9 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data)
tBTA_AV_FEAT peer_features; /* peer features mask */ tBTA_AV_FEAT peer_features; /* peer features mask */
UINT16 peer_ct_features; /* peer features mask as controller */ UINT16 peer_ct_features; /* peer features mask as controller */
UINT16 peer_tg_features; /* peer features mask as target */ UINT16 peer_tg_features; /* peer features mask as target */
#if BTA_AV_CA_INCLUDED
UINT16 obex_l2cap_psm = 0; /* target obex l2cap psm */
#endif
UNUSED(p_data); UNUSED(p_data);
APPL_TRACE_DEBUG("bta_av_rc_disc_done disc:x%x", p_cb->disc); APPL_TRACE_DEBUG("bta_av_rc_disc_done disc:x%x", p_cb->disc);
@ -1600,7 +1657,11 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data)
/* check peer version and whether support CT and TG role */ /* check peer version and whether support CT and TG role */
peer_features = bta_av_check_peer_rc_features (UUID_SERVCLASS_AV_REMOTE_CONTROL, &peer_ct_features); peer_features = bta_av_check_peer_rc_features (UUID_SERVCLASS_AV_REMOTE_CONTROL, &peer_ct_features);
peer_features |= bta_av_check_peer_rc_features (UUID_SERVCLASS_AV_REM_CTRL_TARGET, &peer_tg_features); peer_features |= bta_av_check_peer_rc_features (UUID_SERVCLASS_AV_REM_CTRL_TARGET, &peer_tg_features);
#if BTA_AV_CA_INCLUDED
if (peer_features & BTA_AV_FEAT_COVER_ART) {
obex_l2cap_psm = bta_av_extra_tg_cover_art_l2cap_psm();
}
#endif
p_cb->disc = 0; p_cb->disc = 0;
utl_freebuf((void **) &p_cb->p_disc_db); utl_freebuf((void **) &p_cb->p_disc_db);
@ -1618,6 +1679,9 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data)
p_cb->rcb[rc_handle].peer_features = peer_features; p_cb->rcb[rc_handle].peer_features = peer_features;
p_cb->rcb[rc_handle].peer_ct_features = peer_ct_features; p_cb->rcb[rc_handle].peer_ct_features = peer_ct_features;
p_cb->rcb[rc_handle].peer_tg_features = peer_tg_features; p_cb->rcb[rc_handle].peer_tg_features = peer_tg_features;
#if BTA_AV_CA_INCLUDED
p_cb->rcb[rc_handle].cover_art_l2cap_psm = obex_l2cap_psm;
#endif
} }
#if (BT_USE_TRACES == TRUE || BT_TRACE_APPL == TRUE) #if (BT_USE_TRACES == TRUE || BT_TRACE_APPL == TRUE)
else { else {
@ -1636,6 +1700,11 @@ void bta_av_rc_disc_done(tBTA_AV_DATA *p_data)
} }
} else { } else {
p_cb->rcb[rc_handle].peer_features = peer_features; p_cb->rcb[rc_handle].peer_features = peer_features;
p_cb->rcb[rc_handle].peer_ct_features = peer_ct_features;
p_cb->rcb[rc_handle].peer_tg_features = peer_tg_features;
#if BTA_AV_CA_INCLUDED
p_cb->rcb[rc_handle].cover_art_l2cap_psm = obex_l2cap_psm;
#endif
rc_feat.rc_handle = rc_handle; rc_feat.rc_handle = rc_handle;
rc_feat.peer_features = peer_features; rc_feat.peer_features = peer_features;
rc_feat.peer_ct_features = peer_ct_features; rc_feat.peer_ct_features = peer_ct_features;
@ -1676,6 +1745,10 @@ void bta_av_rc_closed(tBTA_AV_DATA *p_data)
p_rcb->peer_features = 0; p_rcb->peer_features = 0;
p_rcb->peer_ct_features = 0; p_rcb->peer_ct_features = 0;
p_rcb->peer_tg_features = 0; p_rcb->peer_tg_features = 0;
#if BTA_AV_CA_INCLUDED
/* reset cover art state */
bta_av_ca_reset(p_rcb);
#endif
APPL_TRACE_DEBUG(" shdl:%d, lidx:%d", p_rcb->shdl, p_rcb->lidx); APPL_TRACE_DEBUG(" shdl:%d, lidx:%d", p_rcb->shdl, p_rcb->lidx);
if (p_rcb->shdl) { if (p_rcb->shdl) {
if ((p_rcb->shdl - 1) < BTA_AV_NUM_STRS) { if ((p_rcb->shdl - 1) < BTA_AV_NUM_STRS) {
@ -1711,7 +1784,8 @@ void bta_av_rc_closed(tBTA_AV_DATA *p_data)
bta_av_del_rc(p_rcb); bta_av_del_rc(p_rcb);
/* if the AVRCP is no longer listening, create the listening channel */ /* if the AVRCP is no longer listening, create the listening channel */
if (bta_av_cb.rc_acp_handle == BTA_AV_RC_HANDLE_NONE && bta_av_cb.features & BTA_AV_FEAT_RCTG) { if (bta_av_cb.rc_acp_handle == p_msg->handle && bta_av_cb.features & BTA_AV_FEAT_RCTG) {
bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE;
bta_av_rc_create(&bta_av_cb, AVCT_ACP, 0, BTA_AV_NUM_LINKS + 1); bta_av_rc_create(&bta_av_cb, AVCT_ACP, 0, BTA_AV_NUM_LINKS + 1);
} }
} }
@ -1748,6 +1822,7 @@ void bta_av_rc_disc(UINT8 disc)
tAVRC_SDP_DB_PARAMS db_params; tAVRC_SDP_DB_PARAMS db_params;
UINT16 attr_list[] = {ATTR_ID_SERVICE_CLASS_ID_LIST, UINT16 attr_list[] = {ATTR_ID_SERVICE_CLASS_ID_LIST,
ATTR_ID_BT_PROFILE_DESC_LIST, ATTR_ID_BT_PROFILE_DESC_LIST,
ATTR_ID_ADDITION_PROTO_DESC_LISTS,
ATTR_ID_SUPPORTED_FEATURES ATTR_ID_SUPPORTED_FEATURES
}; };
UINT8 hdi; UINT8 hdi;
@ -1785,7 +1860,7 @@ void bta_av_rc_disc(UINT8 disc)
if (p_cb->p_disc_db) { if (p_cb->p_disc_db) {
/* set up parameters */ /* set up parameters */
db_params.db_len = BTA_AV_DISC_BUF_SIZE; db_params.db_len = BTA_AV_DISC_BUF_SIZE;
db_params.num_attr = 3; db_params.num_attr = 4;
db_params.p_db = p_cb->p_disc_db; db_params.p_db = p_cb->p_disc_db;
db_params.p_attrs = attr_list; db_params.p_attrs = attr_list;

View File

@ -613,4 +613,77 @@ void BTA_AvMetaCmd(UINT8 rc_handle, UINT8 label, tBTA_AV_CMD cmd_code, BT_HDR *p
} }
} }
#if BTA_AV_CA_INCLUDED
/*******************************************************************************
**
** Function BTA_AvCaOpen
**
** Description Open a Cover Art OBEX connection to peer device. This function
** can only be used if peer device TG support Cover Art feature and
** AV is enabled with feature BTA_AV_FEAT_METADATA.
**
** Returns void
**
*******************************************************************************/
void BTA_AvCaOpen(UINT8 rc_handle, UINT16 mtu)
{
tBTA_AV_API_CA_OPEN *p_buf;
if ((p_buf = (tBTA_AV_API_CA_OPEN *) osi_malloc(sizeof(tBTA_AV_API_CA_OPEN))) != NULL) {
p_buf->hdr.event = BTA_AV_API_CA_OPEN_EVT;
p_buf->hdr.layer_specific = rc_handle;
p_buf->mtu = mtu;
bta_sys_sendmsg(p_buf);
}
}
/*******************************************************************************
**
** Function BTA_AvCaClose
**
** Description Close a Cover Art OBEX connection.
**
** Returns void
**
*******************************************************************************/
void BTA_AvCaClose(UINT8 rc_handle)
{
tBTA_AV_API_CA_CLOSE *p_buf;
if ((p_buf = (tBTA_AV_API_CA_CLOSE *) osi_malloc(sizeof(tBTA_AV_API_CA_CLOSE))) != NULL) {
p_buf->hdr.event = BTA_AV_API_CA_CLOSE_EVT;
p_buf->hdr.layer_specific = rc_handle;
bta_sys_sendmsg(p_buf);
}
}
/*******************************************************************************
**
** Function BTA_AvCaGet
**
** Description Start the process to get image properties, get image or get
** linked thumbnail. This function can only be used if Cover Art
** OBEX connection is established.
**
** Returns void
**
*******************************************************************************/
void BTA_AvCaGet(UINT8 rc_handle, tBTA_AV_GET_TYPE type, UINT8 *image_handle, UINT8 *image_descriptor, UINT16 image_descriptor_len)
{
tBTA_AV_API_CA_GET *p_buf;
if ((p_buf = (tBTA_AV_API_CA_GET *) osi_malloc(sizeof(tBTA_AV_API_CA_GET))) != NULL) {
p_buf->hdr.event = BTA_AV_API_CA_GET_EVT;
p_buf->hdr.layer_specific = rc_handle;
p_buf->type = type;
memcpy(p_buf->image_handle, image_handle, BTA_AV_CA_IMG_HDL_LEN);
p_buf->image_descriptor = image_descriptor;
p_buf->image_descriptor_len = image_descriptor_len;
bta_sys_sendmsg(p_buf);
}
}
#endif /* BTA_AV_CA_INCLUDED */
#endif /* BTA_AV_INCLUDED */ #endif /* BTA_AV_INCLUDED */

View File

@ -0,0 +1,495 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common/bt_target.h"
#if BTA_AV_CA_INCLUDED
#include <string.h>
#include "bta/bta_av_api.h"
#include "bta_av_int.h"
#include "stack/avdt_api.h"
#include "bta/utl.h"
#include "stack/l2c_api.h"
#include "osi/allocator.h"
#include "osi/list.h"
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#include "stack/obex_api.h"
#include "common/bt_trace.h"
#define COVER_ART_HEADER_ID_IMG_HANDLE 0x30
#define COVER_ART_HEADER_ID_IMG_DESCRIPTOR 0x71
static const UINT8 cover_art_uuid[16] = {0x71, 0x63, 0xDD, 0x54, 0x4A, 0x7E, 0x11, 0xE2, 0xB4, 0x7C, 0x00, 0x50, 0xC2, 0x49, 0x00, 0x48};
static const char *cover_art_img_type_img = "x-bt/img-img";
static const char *cover_art_img_type_thm = "x-bt/img-thm";
static const char *cover_art_img_type_prop = "x-bt/img-properties";
#define COVER_ART_IMG_TYPE_IMG_LEN 13
#define COVER_ART_IMG_TYPE_THM_LEN 13
#define COVER_ART_IMG_TYPE_PROP_LEN 20
static BOOLEAN find_rcb_idx_by_goep_handle(UINT16 handle, UINT16 *out_idx)
{
for (UINT16 i = 0; i < BTA_AV_NUM_RCB; ++i) {
if (bta_av_cb.rcb[i].handle != BTA_AV_RC_HANDLE_NONE && bta_av_cb.rcb[i].cover_art_goep_hdl == handle) {
*out_idx = i;
return TRUE;
}
}
return FALSE;
}
static UINT8 get_rcb_idx(tBTA_AV_RCB *p_rcb)
{
return (p_rcb - &bta_av_cb.rcb[0]);
}
static void get_peer_bd_addr(tBTA_AV_RCB *p_rcb, BD_ADDR out_addr)
{
/* check if this rcb is related to a scb */
if (p_rcb->shdl && p_rcb->shdl <= BTA_AV_NUM_STRS) {
tBTA_AV_SCB *p_scb = bta_av_cb.p_scb[p_rcb->shdl - 1];
bdcpy(out_addr, p_scb->peer_addr);
}
/* else, try get peer addr from lcb */
else if (p_rcb->lidx && p_rcb->lidx <= BTA_AV_NUM_LINKS + 1)
{
bdcpy(out_addr, bta_av_cb.lcb[p_rcb->lidx-1].addr);
}
}
static void report_data_event(BT_HDR *pkt, UINT8 *p_data, UINT16 data_len, BOOLEAN final)
{
tBTA_AV_CA_DATA ca_data;
ca_data.status = BT_STATUS_SUCCESS;
ca_data.final = final;
ca_data.data_len = data_len;
ca_data.p_data = p_data;
ca_data.p_hdr = pkt;
(*bta_av_cb.p_cback)(BTA_AV_CA_DATA_EVT, (tBTA_AV *) &ca_data);
}
static void report_error_data_event(UINT16 status)
{
tBTA_AV_CA_DATA ca_data;
ca_data.status = status;
ca_data.final = TRUE;
ca_data.data_len = 0;
ca_data.p_data = NULL;
ca_data.p_hdr = NULL;
(*bta_av_cb.p_cback)(BTA_AV_CA_DATA_EVT, (tBTA_AV *) &ca_data);
}
static void build_and_send_connect_req(tBTA_AV_RCB *p_rcb)
{
tOBEX_PARSE_INFO info = {0};
info.opcode = OBEX_OPCODE_CONNECT;
info.obex_version_number = 0x15;
info.max_packet_length = p_rcb->cover_art_max_rx;
/* before OBEX connect response, we dont know cover_art_max_tx, use BT_SMALL_BUFFER_SIZE as tx buff size */
if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, BT_SMALL_BUFFER_SIZE) == GOEP_SUCCESS) {
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TARGET, (UINT8 *)cover_art_uuid, 16);
GOEPC_SendRequest(p_rcb->cover_art_goep_hdl);
}
}
static void build_and_send_disconnect_req(tBTA_AV_RCB *p_rcb)
{
tOBEX_PARSE_INFO info = {0};
info.opcode = OBEX_OPCODE_DISCONNECT;
if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, BT_SMALL_BUFFER_SIZE) == GOEP_SUCCESS) {
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_rcb->cover_art_cid), 4);
GOEPC_SendRequest(p_rcb->cover_art_goep_hdl);
}
}
static void build_and_send_empty_get_req(tBTA_AV_RCB *p_rcb)
{
tOBEX_PARSE_INFO info = {0};
info.opcode = OBEX_OPCODE_GET_FINAL;
/* empty get request, use a small buff size */
UINT16 tx_buff_size = BT_SMALL_BUFFER_SIZE < p_rcb->cover_art_max_tx ? BT_SMALL_BUFFER_SIZE : p_rcb->cover_art_max_tx;
if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, tx_buff_size) == GOEP_SUCCESS) {
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_rcb->cover_art_cid), 4);
GOEPC_SendRequest(p_rcb->cover_art_goep_hdl);
}
}
static void close_goepc_and_disconnect(tBTA_AV_RCB *p_rcb)
{
if (p_rcb->cover_art_goep_hdl) {
GOEPC_Close(p_rcb->cover_art_goep_hdl);
}
p_rcb->cover_art_goep_hdl = 0;
p_rcb->cover_art_cid = 0;
p_rcb->cover_art_max_tx = 0;
p_rcb->cover_art_max_rx = 0;
tBTA_AV_DATA *p_data = (tBTA_AV_DATA *) osi_malloc(sizeof(tBTA_AV_DATA));
if (p_data == NULL) {
assert(0);
}
p_data->hdr.event = BTA_AV_CA_GOEP_DISCONNECT_EVT;
p_data->hdr.layer_specific = get_rcb_idx(p_rcb);
p_data->ca_disconnect.reason = BT_STATUS_FAIL;
bta_sys_sendmsg(p_data);
}
static void image_handle_to_utf16(const UINT8 *image_handle, UINT8 *buffer)
{
UINT8 pos = 0;
for (int i = 0 ; i < BTA_AV_CA_IMG_HDL_LEN ; i++){
buffer[pos++] = 0;
buffer[pos++] = image_handle[i];
}
buffer[pos++] = 0;
buffer[pos++] = 0;
}
void bta_av_ca_goep_event_handler(UINT16 handle, UINT8 event, tGOEPC_MSG *p_msg)
{
tBTA_AV_DATA *p_data = NULL;
UINT16 rcb_idx;
if (!find_rcb_idx_by_goep_handle(handle, &rcb_idx)) {
/* can not find a rcb, go error */
goto error;
}
if (event == GOEPC_RESPONSE_EVT || event == GOEPC_OPENED_EVT || event == GOEPC_CLOSED_EVT) {
p_data = (tBTA_AV_DATA *) osi_malloc(sizeof(tBTA_AV_DATA));
assert(p_data != NULL);
}
switch (event)
{
case GOEPC_OPENED_EVT:
p_data->hdr.layer_specific = rcb_idx;
p_data->hdr.event = BTA_AV_CA_GOEP_CONNECT_EVT;
p_data->ca_connect.max_rx = p_msg->opened.our_mtu;
break;
case GOEPC_CLOSED_EVT:
p_data->hdr.layer_specific = rcb_idx;
p_data->hdr.event = BTA_AV_CA_GOEP_DISCONNECT_EVT;
p_data->ca_disconnect.reason = BT_STATUS_FAIL;
break;
case GOEPC_RESPONSE_EVT:
p_data->hdr.layer_specific = rcb_idx;
p_data->ca_response.pkt = p_msg->response.pkt;
p_data->ca_response.opcode = p_msg->response.opcode;
p_data->ca_response.srm_en = p_msg->response.srm_en;
p_data->ca_response.srm_wait = p_msg->response.srm_wait;
if (p_msg->response.final) {
p_data->hdr.event = BTA_AV_CA_RESPONSE_FINAL_EVT;
}
else {
p_data->hdr.event = BTA_AV_CA_RESPONSE_EVT;
}
break;
case GOEPC_MTU_CHANGED_EVT:
case GOEPC_CONGEST_EVT:
case GOEPC_UNCONGEST_EVT:
/* ignore these event */
break;
default:
goto error;
break;
}
if (p_data) {
bta_sys_sendmsg(p_data);
}
return;
error:
/* can not find rcb, just free resource and disconnect */
if (p_data != NULL) {
osi_free(p_data);
}
if (event == GOEPC_RESPONSE_EVT && p_msg->response.pkt != NULL) {
osi_free(p_msg->response.pkt);
}
if (event != GOEPC_CLOSED_EVT) {
GOEPC_Close(handle);
}
}
void bta_av_ca_api_open(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
tOBEX_SVR_INFO svr = {0};
svr.tl = OBEX_OVER_L2CAP;
svr.l2cap.psm = p_rcb->cover_art_l2cap_psm;
svr.l2cap.pref_mtu = p_data->api_ca_open.mtu;
/* reuse the security mask store in bta_av_cb, when support multi connection, this may need change */
svr.l2cap.sec_mask = bta_av_cb.sec_mask;
p_rcb->cover_art_max_rx = p_data->api_ca_open.mtu;
get_peer_bd_addr(p_rcb, svr.l2cap.addr);
if (GOEPC_Open(&svr, bta_av_ca_goep_event_handler, &p_rcb->cover_art_goep_hdl) != GOEP_SUCCESS) {
/* open failed */
if ((p_data = (tBTA_AV_DATA *) osi_malloc(sizeof(tBTA_AV_DATA))) == NULL) {
assert(0);
}
p_data->hdr.event = BTA_AV_CA_GOEP_DISCONNECT_EVT;
p_data->hdr.layer_specific = get_rcb_idx(p_rcb);
p_data->ca_disconnect.reason = BT_STATUS_FAIL;
bta_sys_sendmsg(p_data);
}
}
void bta_av_ca_api_close(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
/* this is a normal disconnect, just build and send OBEX disconnect request */
build_and_send_disconnect_req(p_rcb);
}
void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
tOBEX_PARSE_INFO info = {0};
UINT8 image_handle_utf16[BTA_AV_CA_IMG_HDL_UTF16_LEN];
info.opcode = OBEX_OPCODE_GET_FINAL;
/* limit the tx buff size to BT_DEFAULT_BUFFER_SIZE */
UINT16 tx_buff_size = BT_DEFAULT_BUFFER_SIZE < p_rcb->cover_art_max_tx ? BT_DEFAULT_BUFFER_SIZE : p_rcb->cover_art_max_tx;
if (GOEPC_PrepareRequest(p_rcb->cover_art_goep_hdl, &info, tx_buff_size) != GOEP_SUCCESS) {
/* something error */
goto error;
}
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_CONNECTION_ID, (UINT8 *)(&p_rcb->cover_art_cid), 4);
switch (p_data->api_ca_get.type)
{
case BTA_AV_CA_GET_IMAGE_PROPERTIES:
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TYPE, (const UINT8 *)cover_art_img_type_prop, COVER_ART_IMG_TYPE_PROP_LEN);
break;
case BTA_AV_CA_GET_IMAGE:
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TYPE, (const UINT8 *)cover_art_img_type_img, COVER_ART_IMG_TYPE_IMG_LEN);
break;
case BTA_AV_CA_GET_LINKED_THUMBNAIL:
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, OBEX_HEADER_ID_TYPE, (const UINT8 *)cover_art_img_type_thm, COVER_ART_IMG_TYPE_THM_LEN);
break;
default:
/* should not go here */
assert(0);
break;
}
image_handle_to_utf16(p_data->api_ca_get.image_handle, image_handle_utf16);
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_HANDLE, (UINT8 *)image_handle_utf16, BTA_AV_CA_IMG_HDL_UTF16_LEN);
if (p_data->api_ca_get.type == BTA_AV_CA_GET_IMAGE) {
GOEPC_RequestAddHeader(p_rcb->cover_art_goep_hdl, COVER_ART_HEADER_ID_IMG_DESCRIPTOR, (UINT8 *)p_data->api_ca_get.image_descriptor, p_data->api_ca_get.image_descriptor_len);
}
/* always request to enable srm */
GOEPC_RequestSetSRM(p_rcb->cover_art_goep_hdl, TRUE, FALSE);
if (GOEPC_SendRequest(p_rcb->cover_art_goep_hdl) != GOEP_SUCCESS) {
goto error;
}
return;
error:
close_goepc_and_disconnect(p_rcb);
}
void bta_av_ca_response(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
tOBEX_PARSE_INFO info;
OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info);
/* we always use a final get */
if (p_data->ca_response.opcode == OBEX_OPCODE_GET_FINAL
&& (info.response_code == OBEX_RESPONSE_CODE_CONTINUE || info.response_code == (OBEX_RESPONSE_CODE_CONTINUE | OBEX_FINAL_BIT_MASK)))
{
UINT8 *header = NULL;
UINT8 *body_data = NULL;
UINT16 body_data_len = 0;
while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) {
switch (*header)
{
case OBEX_HEADER_ID_BODY:
/* actually,END_OF_BODY should not in this continue response */
case OBEX_HEADER_ID_END_OF_BODY:
if (body_data == NULL) {
/* first body header */
body_data = header + 3; /* skip opcode, length */
body_data_len = OBEX_GetHeaderLength(header) - 3;
}
else {
/* another body header found */
report_data_event(NULL, body_data, body_data_len, FALSE);
body_data = header + 3; /* skip opcode, length */
body_data_len = OBEX_GetHeaderLength(header) - 3;
}
break;
default:
break;
}
}
if (body_data != NULL) {
/* the only one or the last body data */
report_data_event(p_data->ca_response.pkt, body_data, body_data_len, FALSE);
}
else {
/* not any body data */
osi_free(p_data->ca_response.pkt);
}
/* if SRM not enable, we need to send a empty get request */
if (!p_data->ca_response.srm_en || p_data->ca_response.srm_wait) {
build_and_send_empty_get_req(p_rcb);
}
}
else {
osi_free(p_data->ca_response.pkt);
goto error;
}
return;
error:
close_goepc_and_disconnect(p_rcb);
}
void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
tOBEX_PARSE_INFO info;
OBEX_ParseResponse(p_data->ca_response.pkt, p_data->ca_response.opcode, &info);
UINT8 *header = NULL;
if (p_data->ca_response.opcode == OBEX_OPCODE_CONNECT) {
/* we expect a success response code with final bit set */
if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) {
if (info.max_packet_length < 255) {
p_rcb->cover_art_max_tx = 255;
}
else {
p_rcb->cover_art_max_tx = info.max_packet_length;
}
BOOLEAN cid_found = false;
while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) {
if (*header == OBEX_HEADER_ID_CONNECTION_ID) {
cid_found = true;
memcpy((UINT8 *)(&p_rcb->cover_art_cid), header + 1, 4);
break;
}
}
if (!cid_found) {
goto error;
}
tBTA_AV_CA_STATUS ca_status;
ca_status.connected = TRUE;
ca_status.reason = BT_STATUS_SUCCESS;
(*bta_av_cb.p_cback)(BTA_AV_CA_STATUS_EVT, (tBTA_AV *) &ca_status);
/* done, free response packet */
osi_free(p_data->ca_response.pkt);
}
else {
osi_free(p_data->ca_response.pkt);
goto error;
}
}
else if (p_data->ca_response.opcode == OBEX_OPCODE_GET_FINAL) {
UINT8 *body_data = NULL;
UINT16 body_data_len = 0;
/* check response code is success */
if (info.response_code == (OBEX_RESPONSE_CODE_OK | OBEX_FINAL_BIT_MASK)) {
while((header = OBEX_GetNextHeader(p_data->ca_response.pkt, &info)) != NULL) {
switch (*header)
{
/* actually, BODY should not in this final response */
case OBEX_HEADER_ID_BODY:
case OBEX_HEADER_ID_END_OF_BODY:
if (body_data == NULL) {
/* first body header */
body_data = header + 3; /* skip opcode, length */
body_data_len = OBEX_GetHeaderLength(header) - 3;
}
else {
/* another body header found */
report_data_event(NULL, body_data, body_data_len, FALSE);
body_data = header + 3; /* skip opcode, length */
body_data_len = OBEX_GetHeaderLength(header) - 3;
}
break;
default:
break;
}
}
if (body_data != NULL) {
/* the only one or the last body data, packet will be free by upper layer */
report_data_event(p_data->ca_response.pkt, body_data, body_data_len, TRUE);
}
else {
/* not any body data */
osi_free(p_data->ca_response.pkt);
}
}
else {
report_error_data_event(BT_STATUS_FAIL);
osi_free(p_data->ca_response.pkt);
}
}
else if (p_data->ca_response.opcode == OBEX_OPCODE_DISCONNECT) {
/* received disconnect response, close l2cap channel and reset cover art value */
bta_av_ca_force_disconnect(p_rcb, p_data);
osi_free(p_data->ca_response.pkt);
}
else {
osi_free(p_data->ca_response.pkt);
goto error;
}
return;
error:
close_goepc_and_disconnect(p_rcb);
}
void bta_av_ca_goep_connect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
/* goep connection open, use a smaller value as max_rx */
if (p_rcb->cover_art_max_rx > p_data->ca_connect.max_rx) {
p_rcb->cover_art_max_rx = p_data->ca_connect.max_rx;
}
build_and_send_connect_req(p_rcb);
}
void bta_av_ca_goep_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
p_rcb->cover_art_goep_hdl = 0;
p_rcb->cover_art_max_rx = 0;
p_rcb->cover_art_max_tx = 0;
p_rcb->cover_art_cid = 0;
tBTA_AV_CA_STATUS ca_status;
ca_status.connected = FALSE;
ca_status.reason = p_data->ca_disconnect.reason;
(*bta_av_cb.p_cback)(BTA_AV_CA_STATUS_EVT, (tBTA_AV *) &ca_status);
}
void bta_av_ca_force_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data)
{
if (p_rcb->cover_art_goep_hdl) {
GOEPC_Close(p_rcb->cover_art_goep_hdl);
}
/* dont reset p_rcb->cover_art_l2cap_psm */
p_rcb->cover_art_goep_hdl = 0;
p_rcb->cover_art_cid = 0;
p_rcb->cover_art_max_tx = 0;
p_rcb->cover_art_max_rx = 0;
tBTA_AV_CA_STATUS ca_status;
ca_status.connected = FALSE;
/* force disconnect by upper, set reason to success */
ca_status.reason = BT_STATUS_SUCCESS;
(*bta_av_cb.p_cback)(BTA_AV_CA_STATUS_EVT, (tBTA_AV *) &ca_status);
}
void bta_av_ca_reset(tBTA_AV_RCB *p_rcb)
{
if (p_rcb->cover_art_goep_hdl) {
GOEPC_Close(p_rcb->cover_art_goep_hdl);
}
p_rcb->cover_art_l2cap_psm = 0;
p_rcb->cover_art_goep_hdl = 0;
p_rcb->cover_art_state = 0;
p_rcb->cover_art_cid = 0;
p_rcb->cover_art_max_tx = 0;
p_rcb->cover_art_max_rx = 0;
}
#endif /* BTA_AV_CA_INCLUDED */

View File

@ -0,0 +1,170 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "common/bt_target.h"
#if BTA_AV_CA_INCLUDED
#include <string.h>
#include "bta/bta_av_api.h"
#include "bta_av_int.h"
#include "stack/avdt_api.h"
#include "bta/utl.h"
#include "stack/l2c_api.h"
#include "osi/allocator.h"
#include "osi/list.h"
#include "common/bt_trace.h"
/* state machine states */
enum {
BTA_AV_CA_INIT_ST,
BTA_AV_CA_OPENING_ST,
BTA_AV_CA_CONNECTING_ST,
BTA_AV_CA_CONNECTED_ST,
BTA_AV_CA_GETTING_ST,
BTA_AV_CA_CLOSING_ST
};
/* state machine action enumeration list */
enum {
BTA_AV_API_CA_OPEN,
BTA_AV_API_CA_CLOSE,
BTA_AV_API_CA_GET,
BTA_AV_CA_RESPONSE,
BTA_AV_CA_RESPONSE_FINAL,
BTA_AV_CA_GOEP_CONNECT,
BTA_AV_CA_GOEP_DISCONNECT,
BTA_AV_CA_FORCE_DISCONNECT,
BTA_AV_CA_NUM_ACTIONS
};
#define BTA_AV_CA_IGNORE BTA_AV_CA_NUM_ACTIONS
/* type for action functions */
typedef void (*tBTA_AV_CA_ACTION)(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
/* action functions */
const tBTA_AV_CA_ACTION bta_av_ca_action[] = {
bta_av_ca_api_open,
bta_av_ca_api_close,
bta_av_ca_api_get,
bta_av_ca_response,
bta_av_ca_response_final,
bta_av_ca_goep_connect,
bta_av_ca_goep_disconnect,
bta_av_ca_force_disconnect,
NULL
};
/* state table information */
#define BTA_AV_CA_ACTION_COL 0 /* position of actions */
#define BTA_AV_CA_NEXT_STATE 1 /* position of next state */
#define BTA_AV_CA_NUM_COLS 2 /* number of columns in state tables */
/* state table for init state */
static const UINT8 bta_av_ca_st_init[][BTA_AV_CA_NUM_COLS] = {
/* Event Action 1 Next state */
/* API_CA_OPEN_EVT */ {BTA_AV_API_CA_OPEN, BTA_AV_CA_OPENING_ST },
/* API_CA_CLOSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST },
/* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST },
/* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST },
/* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST },
/* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST },
/* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_INIT_ST },
};
/* state table for opening state */
static const UINT8 bta_av_ca_st_opening[][BTA_AV_CA_NUM_COLS] = {
/* Event Action 1 Next state */
/* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST },
/* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST },
/* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST },
/* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST },
/* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_OPENING_ST },
/* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_GOEP_CONNECT, BTA_AV_CA_CONNECTING_ST },
/* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST },
};
/* state table for connecting state */
static const UINT8 bta_av_ca_st_connecting[][BTA_AV_CA_NUM_COLS] = {
/* Event Action 1 Next state */
/* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST },
/* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST },
/* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST },
/* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST },
/* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_RESPONSE_FINAL, BTA_AV_CA_CONNECTED_ST },
/* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTING_ST },
/* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST },
};
/* state table for connected state */
static const UINT8 bta_av_ca_st_connected[][BTA_AV_CA_NUM_COLS] = {
/* Event Action 1 Next state */
/* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST },
/* API_CA_CLOSE_EVT */ {BTA_AV_API_CA_CLOSE, BTA_AV_CA_CLOSING_ST },
/* API_CA_GET_EVT */ {BTA_AV_API_CA_GET, BTA_AV_CA_GETTING_ST },
/* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST },
/* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST },
/* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CONNECTED_ST },
/* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST },
};
/* state table for getting state */
static const UINT8 bta_av_ca_st_getting[][BTA_AV_CA_NUM_COLS] = {
/* Event Action 1 Next state */
/* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_GETTING_ST },
/* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST },
/* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_GETTING_ST },
/* CA_RESPONSE_EVT */ {BTA_AV_CA_RESPONSE, BTA_AV_CA_GETTING_ST },
/* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_RESPONSE_FINAL, BTA_AV_CA_CONNECTED_ST },
/* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_GETTING_ST },
/* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST },
};
/* state table for closing state */
static const UINT8 bta_av_ca_st_closing[][BTA_AV_CA_NUM_COLS] = {
/* Event Action 1 Next state */
/* API_CA_OPEN_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST },
/* API_CA_CLOSE_EVT */ {BTA_AV_CA_FORCE_DISCONNECT, BTA_AV_CA_INIT_ST },
/* API_CA_GET_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST },
/* CA_RESPONSE_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST },
/* CA_RESPONSE_FINAL_EVT */ {BTA_AV_CA_RESPONSE_FINAL, BTA_AV_CA_INIT_ST },
/* CA_GOEP_CONNECT_EVT */ {BTA_AV_CA_IGNORE, BTA_AV_CA_CLOSING_ST },
/* CA_GOEP_DISCONNECT_EVT */ {BTA_AV_CA_GOEP_DISCONNECT, BTA_AV_CA_INIT_ST },
};
/* type for state table */
typedef const UINT8 (*tBTA_AV_CA_ST_TBL)[BTA_AV_CA_NUM_COLS];
/* state table */
static const tBTA_AV_CA_ST_TBL bta_av_ca_st_tbl[] = {
bta_av_ca_st_init,
bta_av_ca_st_opening,
bta_av_ca_st_connecting,
bta_av_ca_st_connected,
bta_av_ca_st_getting,
bta_av_ca_st_closing
};
void bta_av_ca_sm_execute(tBTA_AV_RCB *p_rcb, UINT16 event, tBTA_AV_DATA *p_data)
{
tBTA_AV_CA_ST_TBL state_table;
UINT8 action;
/* look up the state table for the current state */
state_table = bta_av_ca_st_tbl[p_rcb->cover_art_state];
event -= BTA_AV_CA_FIRST_SM_EVT;
/* set next state */
p_rcb->cover_art_state = state_table[event][BTA_AV_CA_NEXT_STATE];
/* execute action functions */
if ((action = state_table[event][BTA_AV_CA_ACTION_COL]) != BTA_AV_CA_IGNORE) {
(*bta_av_ca_action[action])(p_rcb, p_data);
}
}
#endif /* BTA_AV_CA_INCLUDED */

View File

@ -461,7 +461,7 @@ static void bta_av_a2dp_report_cback(UINT8 handle, AVDT_REPORT_TYPE type,
** **
** Function bta_av_api_sink_enable ** Function bta_av_api_sink_enable
** **
** Description activate, deactive A2DP Sink, ** Description activate, deactivate A2DP Sink,
** **
** Returns void ** Returns void
** **
@ -593,7 +593,7 @@ static void bta_av_api_register(tBTA_AV_DATA *p_data)
#endif #endif
} }
/* Set the Calss of Device (Audio & Capturing/Rendering service class bit) */ /* Set the Class of Device (Audio & Capturing/Rendering service class bit) */
if (p_data->api_reg.tsep == AVDT_TSEP_SRC) { if (p_data->api_reg.tsep == AVDT_TSEP_SRC) {
cod.service = BTM_COD_SERVICE_CAPTURING | BTM_COD_SERVICE_AUDIO; cod.service = BTM_COD_SERVICE_CAPTURING | BTM_COD_SERVICE_AUDIO;
cod.major = BTM_COD_MAJOR_AUDIO; cod.major = BTM_COD_MAJOR_AUDIO;
@ -1248,6 +1248,13 @@ BOOLEAN bta_av_hdl_event(BT_HDR *p_msg)
APPL_TRACE_VERBOSE("AV sm event=0x%x(%s)\n", event, bta_av_evt_code(event)); APPL_TRACE_VERBOSE("AV sm event=0x%x(%s)\n", event, bta_av_evt_code(event));
/* state machine events */ /* state machine events */
bta_av_sm_execute(&bta_av_cb, p_msg->event, (tBTA_AV_DATA *) p_msg); bta_av_sm_execute(&bta_av_cb, p_msg->event, (tBTA_AV_DATA *) p_msg);
#if BTA_AV_CA_INCLUDED
} else if (event >= BTA_AV_CA_FIRST_SM_EVT && event <= BTA_AV_CA_LAST_SM_EVT) {
if (p_msg->layer_specific < BTA_AV_NUM_RCB) {
tBTA_AV_RCB *p_rcb = &bta_av_cb.rcb[p_msg->layer_specific];
bta_av_ca_sm_execute(p_rcb, p_msg->event, (tBTA_AV_DATA *) p_msg);
}
#endif
} else { } else {
APPL_TRACE_VERBOSE("handle=0x%x\n", p_msg->layer_specific); APPL_TRACE_VERBOSE("handle=0x%x\n", p_msg->layer_specific);
tBTA_AV_SCB *p_scb = bta_av_hndl_to_scb(p_msg->layer_specific); tBTA_AV_SCB *p_scb = bta_av_hndl_to_scb(p_msg->layer_specific);

View File

@ -85,7 +85,16 @@ enum {
BTA_AV_ROLE_CHANGE_EVT, BTA_AV_ROLE_CHANGE_EVT,
BTA_AV_AVDT_DELAY_RPT_EVT, BTA_AV_AVDT_DELAY_RPT_EVT,
BTA_AV_ACP_CONNECT_EVT, BTA_AV_ACP_CONNECT_EVT,
#if BTA_AV_CA_INCLUDED
/* these events are handled by the Cover Art Client state machine */
BTA_AV_API_CA_OPEN_EVT,
BTA_AV_API_CA_CLOSE_EVT,
BTA_AV_API_CA_GET_EVT,
BTA_AV_CA_RESPONSE_EVT,
BTA_AV_CA_RESPONSE_FINAL_EVT,
BTA_AV_CA_GOEP_CONNECT_EVT,
BTA_AV_CA_GOEP_DISCONNECT_EVT,
#endif
/* these events are handled outside of the state machine */ /* these events are handled outside of the state machine */
BTA_AV_API_ENABLE_EVT, BTA_AV_API_ENABLE_EVT,
BTA_AV_API_REGISTER_EVT, BTA_AV_API_REGISTER_EVT,
@ -115,6 +124,12 @@ enum {
#define BTA_AV_FIRST_SM_EVT BTA_AV_API_DISABLE_EVT #define BTA_AV_FIRST_SM_EVT BTA_AV_API_DISABLE_EVT
#define BTA_AV_LAST_SM_EVT BTA_AV_AVRC_NONE_EVT #define BTA_AV_LAST_SM_EVT BTA_AV_AVRC_NONE_EVT
#if BTA_AV_CA_INCLUDED
/* events for AVRC Cover Art state machine */
#define BTA_AV_CA_FIRST_SM_EVT BTA_AV_API_CA_OPEN_EVT
#define BTA_AV_CA_LAST_SM_EVT BTA_AV_CA_GOEP_DISCONNECT_EVT
#endif
/* events for AV stream control block state machine */ /* events for AV stream control block state machine */
#define BTA_AV_FIRST_SSM_EVT BTA_AV_API_OPEN_EVT #define BTA_AV_FIRST_SSM_EVT BTA_AV_API_OPEN_EVT
@ -348,6 +363,52 @@ typedef struct {
BT_HDR hdr; BT_HDR hdr;
} tBTA_AV_API_GET_DELAY_VALUE; } tBTA_AV_API_GET_DELAY_VALUE;
#if BTA_AV_CA_INCLUDED
/* data type for BTA_AV_API_CA_OPEN_EVT */
typedef struct {
BT_HDR hdr;
UINT16 mtu;
} tBTA_AV_API_CA_OPEN;
/* data type for BTA_AV_API_CA_CLOSE_EVT */
typedef struct {
BT_HDR hdr;
} tBTA_AV_API_CA_CLOSE;
/* data type for BTA_AV_API_CA_GET_EVT */
typedef struct {
BT_HDR hdr;
tBTA_AV_GET_TYPE type;
UINT8 image_handle[7];
/* Image descriptor used in get image function */
UINT16 image_descriptor_len;
UINT8 *image_descriptor;
} tBTA_AV_API_CA_GET;
/* data type for BTA_AV_CA_RESPONSE_EVT and BTA_AV_CA_RESPONSE_FINAL_EVT */
typedef struct {
BT_HDR hdr;
BT_HDR *pkt;
UINT8 opcode;
BOOLEAN srm_en;
BOOLEAN srm_wait;
} tBTA_AV_CA_RESPONSE;
/* data type for BTA_AV_CA_CONNECT_EVT */
typedef struct {
BT_HDR hdr;
UINT16 max_rx;
} tBTA_AV_CA_CONNECT;
/* data type for BTA_AV_CA_DISCONNECT_EVT */
typedef struct {
BT_HDR hdr;
UINT16 reason;
} tBTA_AV_CA_DISCONNECT;
#endif /* BTA_AV_CA_INCLUDED */
/* initiator/acceptor role for adaption */ /* initiator/acceptor role for adaption */
#define BTA_AV_ROLE_AD_INT 0x00 /* initiator */ #define BTA_AV_ROLE_AD_INT 0x00 /* initiator */
#define BTA_AV_ROLE_AD_ACP 0x01 /* acceptor */ #define BTA_AV_ROLE_AD_ACP 0x01 /* acceptor */
@ -382,6 +443,14 @@ typedef union {
tBTA_AV_API_META_RSP api_meta_rsp; tBTA_AV_API_META_RSP api_meta_rsp;
tBTA_AV_API_SET_DELAY_VALUE api_set_delay_vlaue; tBTA_AV_API_SET_DELAY_VALUE api_set_delay_vlaue;
tBTA_AV_API_GET_DELAY_VALUE api_get_delay_value; tBTA_AV_API_GET_DELAY_VALUE api_get_delay_value;
#if BTA_AV_CA_INCLUDED
tBTA_AV_API_CA_OPEN api_ca_open;
tBTA_AV_API_CA_CLOSE api_ca_close;
tBTA_AV_API_CA_GET api_ca_get;
tBTA_AV_CA_RESPONSE ca_response;
tBTA_AV_CA_CONNECT ca_connect;
tBTA_AV_CA_DISCONNECT ca_disconnect;
#endif
} tBTA_AV_DATA; } tBTA_AV_DATA;
typedef void (tBTA_AV_VDP_DATA_ACT)(void *p_scb); typedef void (tBTA_AV_VDP_DATA_ACT)(void *p_scb);
@ -405,8 +474,8 @@ typedef union {
#define BTA_AV_Q_TAG_START 0x02 /* before start sending media packets */ #define BTA_AV_Q_TAG_START 0x02 /* before start sending media packets */
#define BTA_AV_Q_TAG_STREAM 0x03 /* during streaming */ #define BTA_AV_Q_TAG_STREAM 0x03 /* during streaming */
#define BTA_AV_WAIT_ACP_CAPS_ON 0x01 /* retriving the peer capabilities */ #define BTA_AV_WAIT_ACP_CAPS_ON 0x01 /* retrieving the peer capabilities */
#define BTA_AV_WAIT_ACP_CAPS_STARTED 0x02 /* started while retriving peer capabilities */ #define BTA_AV_WAIT_ACP_CAPS_STARTED 0x02 /* started while retrieving peer capabilities */
#define BTA_AV_WAIT_ROLE_SW_RES_OPEN 0x04 /* waiting for role switch result after API_OPEN, before STR_OPENED */ #define BTA_AV_WAIT_ROLE_SW_RES_OPEN 0x04 /* waiting for role switch result after API_OPEN, before STR_OPENED */
#define BTA_AV_WAIT_ROLE_SW_RES_START 0x08 /* waiting for role switch result before streaming */ #define BTA_AV_WAIT_ROLE_SW_RES_START 0x08 /* waiting for role switch result before streaming */
#define BTA_AV_WAIT_ROLE_SW_STARTED 0x10 /* started while waiting for role switch result */ #define BTA_AV_WAIT_ROLE_SW_STARTED 0x10 /* started while waiting for role switch result */
@ -462,7 +531,7 @@ typedef struct {
BOOLEAN use_rc; /* TRUE if AVRCP is allowed */ BOOLEAN use_rc; /* TRUE if AVRCP is allowed */
BOOLEAN started; /* TRUE if stream started */ BOOLEAN started; /* TRUE if stream started */
UINT8 co_started; /* non-zero, if stream started from call-out perspective */ UINT8 co_started; /* non-zero, if stream started from call-out perspective */
BOOLEAN recfg_sup; /* TRUE if the first attempt to reconfigure the stream was successfull, else False if command fails */ BOOLEAN recfg_sup; /* TRUE if the first attempt to reconfigure the stream was successful, else False if command fails */
BOOLEAN suspend_sup; /* TRUE if Suspend stream is supported, else FALSE if suspend command fails */ BOOLEAN suspend_sup; /* TRUE if Suspend stream is supported, else FALSE if suspend command fails */
BOOLEAN deregistring; /* TRUE if deregistering */ BOOLEAN deregistring; /* TRUE if deregistering */
BOOLEAN sco_suspend; /* TRUE if SUSPEND is issued automatically for SCO */ BOOLEAN sco_suspend; /* TRUE if SUSPEND is issued automatically for SCO */
@ -471,7 +540,7 @@ typedef struct {
UINT8 wait; /* set 0x1, when getting Caps as ACP, set 0x2, when started */ UINT8 wait; /* set 0x1, when getting Caps as ACP, set 0x2, when started */
UINT8 q_tag; /* identify the associated q_info union member */ UINT8 q_tag; /* identify the associated q_info union member */
BOOLEAN no_rtp_hdr; /* TRUE if add no RTP header*/ BOOLEAN no_rtp_hdr; /* TRUE if add no RTP header*/
UINT8 disc_rsn; /* disconenction reason */ UINT8 disc_rsn; /* disconnection reason */
UINT16 uuid_int; /*intended UUID of Initiator to connect to */ UINT16 uuid_int; /*intended UUID of Initiator to connect to */
} tBTA_AV_SCB; } tBTA_AV_SCB;
@ -481,6 +550,13 @@ typedef struct {
#define BTA_AV_RC_CONN_MASK 0x20 #define BTA_AV_RC_CONN_MASK 0x20
#define BTA_AV_CA_IMG_HDL_UTF16_LEN 16 /* Cover Art image handle in utf-16 format, fixed to 16 */
#define BTA_AV_CA_SRM_DISABLE 0x00
#define BTA_AV_CA_SRM_ENABLE_REQ 0x01
#define BTA_AV_CA_SRM_WAIT 0x02
#define BTA_AV_CA_SRM_ENABLE 0x03
/* type for AV RCP control block */ /* type for AV RCP control block */
/* index to this control block is the rc handle */ /* index to this control block is the rc handle */
typedef struct { typedef struct {
@ -491,6 +567,14 @@ typedef struct {
tBTA_AV_FEAT peer_features; /* peer features mask */ tBTA_AV_FEAT peer_features; /* peer features mask */
UINT16 peer_ct_features; UINT16 peer_ct_features;
UINT16 peer_tg_features; UINT16 peer_tg_features;
#if BTA_AV_CA_INCLUDED
UINT16 cover_art_l2cap_psm; /* OBEX over L2CAP PSM */
UINT16 cover_art_goep_hdl; /* Cover Art client GOEP connection handle */
UINT8 cover_art_state; /* Cover Art client state machine */
UINT32 cover_art_cid; /* Cover Art client connection id */
UINT16 cover_art_max_tx; /* max packet length peer device can receive */
UINT16 cover_art_max_rx; /* max packet length we can receive */
#endif
} tBTA_AV_RCB; } tBTA_AV_RCB;
#define BTA_AV_NUM_RCB (BTA_AV_NUM_STRS + 2) #define BTA_AV_NUM_RCB (BTA_AV_NUM_STRS + 2)
@ -705,6 +789,19 @@ extern void bta_av_do_disc_vdp (tBTA_AV_SCB *p_scb, tBTA_AV_DATA *p_data);
extern void bta_av_vdp_str_opened (tBTA_AV_SCB *p_scb, tBTA_AV_DATA *p_data); extern void bta_av_vdp_str_opened (tBTA_AV_SCB *p_scb, tBTA_AV_DATA *p_data);
extern void bta_av_reg_vdp (tAVDT_CS *p_cs, char *p_service_name, void *p_data); extern void bta_av_reg_vdp (tAVDT_CS *p_cs, char *p_service_name, void *p_data);
#if BTA_AV_CA_INCLUDED
extern void bta_av_ca_api_open(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_api_close(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_api_get(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_response(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_response_final(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_goep_connect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_goep_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_force_disconnect(tBTA_AV_RCB *p_rcb, tBTA_AV_DATA *p_data);
extern void bta_av_ca_sm_execute(tBTA_AV_RCB *p_rcb, UINT16 event, tBTA_AV_DATA *p_data);
extern void bta_av_ca_reset(tBTA_AV_RCB *p_rcb);
#endif
#endif ///BTA_AV_INCLUDED == TRUE #endif ///BTA_AV_INCLUDED == TRUE
#endif /* BTA_AV_INT_H */ #endif /* BTA_AV_INT_H */

View File

@ -43,14 +43,14 @@
#define BTA_AV_FAIL_STREAM 3 /* stream connection failed */ #define BTA_AV_FAIL_STREAM 3 /* stream connection failed */
#define BTA_AV_FAIL_RESOURCES 4 /* no resources */ #define BTA_AV_FAIL_RESOURCES 4 /* no resources */
#define BTA_AV_FAIL_ROLE 5 /* failed due to role management related issues */ #define BTA_AV_FAIL_ROLE 5 /* failed due to role management related issues */
#define BTA_AV_FAIL_GET_CAP 6 /* get capability failed due to no SEP availale on the peer */ #define BTA_AV_FAIL_GET_CAP 6 /* get capability failed due to no SEP available on the peer */
typedef UINT8 tBTA_AV_STATUS; typedef UINT8 tBTA_AV_STATUS;
/* AV features masks */ /* AV features masks */
#define BTA_AV_FEAT_RCTG 0x0001 /* remote control target */ #define BTA_AV_FEAT_RCTG 0x0001 /* remote control target */
#define BTA_AV_FEAT_RCCT 0x0002 /* remote control controller */ #define BTA_AV_FEAT_RCCT 0x0002 /* remote control controller */
#define BTA_AV_FEAT_PROTECT 0x0004 /* streaming media contect protection */ #define BTA_AV_FEAT_PROTECT 0x0004 /* streaming media context protection */
#define BTA_AV_FEAT_VENDOR 0x0008 /* remote control vendor dependent commands */ #define BTA_AV_FEAT_VENDOR 0x0008 /* remote control vendor dependent commands */
#define BTA_AV_FEAT_REPORT 0x0020 /* use reporting service for VDP */ #define BTA_AV_FEAT_REPORT 0x0020 /* use reporting service for VDP */
#define BTA_AV_FEAT_METADATA 0x0040 /* remote control Metadata Transfer command/response */ #define BTA_AV_FEAT_METADATA 0x0040 /* remote control Metadata Transfer command/response */
@ -60,6 +60,7 @@ typedef UINT8 tBTA_AV_STATUS;
#define BTA_AV_FEAT_ADV_CTRL 0x0200 /* remote control Advanced Control command/response */ #define BTA_AV_FEAT_ADV_CTRL 0x0200 /* remote control Advanced Control command/response */
#define BTA_AV_FEAT_DELAY_RPT 0x0400 /* allow delay reporting */ #define BTA_AV_FEAT_DELAY_RPT 0x0400 /* allow delay reporting */
#define BTA_AV_FEAT_ACP_START 0x0800 /* start stream when 2nd SNK was accepted */ #define BTA_AV_FEAT_ACP_START 0x0800 /* start stream when 2nd SNK was accepted */
#define BTA_AV_FEAT_COVER_ART 0x1000 /* remote control target cover art */
/* Internal features */ /* Internal features */
#define BTA_AV_FEAT_NO_SCO_SSPD 0x8000 /* Do not suspend av streaming as to AG events(SCO or Call) */ #define BTA_AV_FEAT_NO_SCO_SSPD 0x8000 /* Do not suspend av streaming as to AG events(SCO or Call) */
@ -107,6 +108,7 @@ typedef UINT8 tBTA_AV_HNDL;
#define BTA_AV_MAX_VDP_MTU 1008 #define BTA_AV_MAX_VDP_MTU 1008
#endif #endif
#define BTA_AV_CA_IMG_HDL_LEN 7 /* Cover Art image handle len, fixed to 7 */
/* codec type */ /* codec type */
#define BTA_AV_CODEC_SBC A2D_MEDIA_CT_SBC /* SBC media codec type */ #define BTA_AV_CODEC_SBC A2D_MEDIA_CT_SBC /* SBC media codec type */
@ -224,6 +226,12 @@ typedef UINT8 tBTA_AV_CODE;
typedef UINT8 tBTA_AV_ERR; typedef UINT8 tBTA_AV_ERR;
/* type codes for BTA_AV_API_CA_GET */
#define BTA_AV_CA_GET_IMAGE_PROPERTIES 0x01
#define BTA_AV_CA_GET_IMAGE 0x02
#define BTA_AV_CA_GET_LINKED_THUMBNAIL 0x03
typedef UINT8 tBTA_AV_GET_TYPE;
/* AV callback events */ /* AV callback events */
#define BTA_AV_ENABLE_EVT 0 /* AV enabled */ #define BTA_AV_ENABLE_EVT 0 /* AV enabled */
@ -253,8 +261,13 @@ typedef UINT8 tBTA_AV_ERR;
#define BTA_AV_SET_DELAY_VALUE_EVT 22 /* set delay reporting value */ #define BTA_AV_SET_DELAY_VALUE_EVT 22 /* set delay reporting value */
#define BTA_AV_GET_DELAY_VALUE_EVT 23 /* get delay reporting value */ #define BTA_AV_GET_DELAY_VALUE_EVT 23 /* get delay reporting value */
#define BTA_AV_SNK_PSC_CFG_EVT 24 /* Protocol service capabilities. */ #define BTA_AV_SNK_PSC_CFG_EVT 24 /* Protocol service capabilities. */
/* still keep Cover Art event here if Cover Art feature not enabled */
#define BTA_AV_CA_STATUS_EVT 25 /* Cover Art Client status event */
#define BTA_AV_CA_DATA_EVT 26 /* Cover Art response body data */
/* Max BTA event */ /* Max BTA event */
#define BTA_AV_MAX_EVT 25 #define BTA_AV_MAX_EVT 27
/* function types for call-out functions */ /* function types for call-out functions */
@ -482,6 +495,25 @@ typedef struct {
} tBTA_AV_SNK_PSC_CFG; } tBTA_AV_SNK_PSC_CFG;
#if BTA_AV_CA_INCLUDED
/* data associated with BTA_AV_CA_STATUS_EVT */
typedef struct {
BOOLEAN connected; /* whether Cover Art connection is connected */
UINT16 reason; /* connect failed or disconnect reason */
} tBTA_AV_CA_STATUS;
/* data associated with BTA_AV_CA_DATA_EVT */
typedef struct {
UINT16 status; /* OBEX response status */
BOOLEAN final; /* final data packet */
UINT16 data_len; /* data len */
UINT8 *p_data; /* point to the data in p_hdr */
BT_HDR *p_hdr; /* after data pass to application, free this packet */
} tBTA_AV_CA_DATA;
#endif
/* union of data associated with AV callback */ /* union of data associated with AV callback */
typedef union { typedef union {
tBTA_AV_CHNL chnl; tBTA_AV_CHNL chnl;
@ -506,6 +538,10 @@ typedef union {
tBTA_AV_RC_FEAT rc_feat; tBTA_AV_RC_FEAT rc_feat;
tBTA_AV_DELAY delay; tBTA_AV_DELAY delay;
tBTA_AV_SNK_PSC_CFG psc; tBTA_AV_SNK_PSC_CFG psc;
#if BTA_AV_CA_INCLUDED
tBTA_AV_CA_STATUS ca_status;
tBTA_AV_CA_DATA ca_data;
#endif
} tBTA_AV; } tBTA_AV;
/* union of data associated with AV Media callback */ /* union of data associated with AV Media callback */
@ -865,6 +901,47 @@ void BTA_AvMetaRsp(UINT8 rc_handle, UINT8 label, tBTA_AV_CODE rsp_code,
*******************************************************************************/ *******************************************************************************/
void BTA_AvMetaCmd(UINT8 rc_handle, UINT8 label, tBTA_AV_CMD cmd_code, BT_HDR *p_pkt); void BTA_AvMetaCmd(UINT8 rc_handle, UINT8 label, tBTA_AV_CMD cmd_code, BT_HDR *p_pkt);
#if BTA_AV_CA_INCLUDED
/*******************************************************************************
**
** Function BTA_AvCaOpen
**
** Description Open a Cover Art OBEX connection to peer device. This function
** can only be used if peer device TG support Cover Art feature and
** AV is enabled with feature BTA_AV_FEAT_METADATA.
**
** Returns void
**
*******************************************************************************/
void BTA_AvCaOpen(UINT8 rc_handle, UINT16 pref_packet_len);
/*******************************************************************************
**
** Function BTA_AvCaClose
**
** Description Close a Cover Art OBEX connection.
**
** Returns void
**
*******************************************************************************/
void BTA_AvCaClose(UINT8 rc_handle);
/*******************************************************************************
**
** Function BTA_AvCaGet
**
** Description Start the process to get image properties, get image or get
** linked thumbnail. This function can only be used if Cover Art
** OBEX connection is established.
**
** Returns void
**
*******************************************************************************/
void BTA_AvCaGet(UINT8 rc_handle, tBTA_AV_GET_TYPE type, UINT8 *image_handle, UINT8 *image_descriptor, UINT16 image_descriptor_len);
#endif /* BTA_AV_CA_INCLUDED */
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -91,7 +91,8 @@ typedef UINT16 tBTA_SYS_HW_MODULE;
#define BTA_ID_GATTC 31 /* GATT Client */ #define BTA_ID_GATTC 31 /* GATT Client */
#define BTA_ID_GATTS 32 /* GATT Client */ #define BTA_ID_GATTS 32 /* GATT Client */
#define BTA_ID_SDP 33 /* SDP Client */ #define BTA_ID_SDP 33 /* SDP Client */
#define BTA_ID_BLUETOOTH_MAX 34 /* last BT profile */ #define BTA_ID_GOEPC 34 /* GOEP Client */
#define BTA_ID_BLUETOOTH_MAX 35 /* last BT profile */
/* GENERIC */ /* GENERIC */
#define BTA_ID_PRM 38 #define BTA_ID_PRM 38
@ -142,7 +143,7 @@ typedef void (tBTA_SYS_CONN_CBACK)(tBTA_SYS_CONN_STATUS status, UINT8 id, UINT8
typedef void (tBTA_SYS_SSR_CFG_CBACK)(UINT8 id, UINT8 app_id, UINT16 latency, UINT16 tout); typedef void (tBTA_SYS_SSR_CFG_CBACK)(UINT8 id, UINT8 app_id, UINT16 latency, UINT16 tout);
#if (BTA_EIR_CANNED_UUID_LIST != TRUE) #if (BTA_EIR_CANNED_UUID_LIST != TRUE)
/* eir callback for adding/removeing UUID */ /* eir callback for adding/removing UUID */
typedef void (tBTA_SYS_EIR_CBACK)(tBT_UUID uuid, BOOLEAN adding); typedef void (tBTA_SYS_EIR_CBACK)(tBT_UUID uuid, BOOLEAN adding);
#endif #endif

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -34,7 +34,7 @@
#if BTC_AV_INCLUDED #if BTC_AV_INCLUDED
// global variable to inidcate avrc is initialized with a2dp // global variable to indicate avrc is initialized with a2dp
bool g_av_with_rc; bool g_av_with_rc;
// global variable to indicate a2dp is initialized // global variable to indicate a2dp is initialized
bool g_a2dp_on_init; bool g_a2dp_on_init;
@ -127,6 +127,8 @@ static btc_av_cb_t *btc_av_cb_ptr = NULL;
case BTA_AV_META_MSG_EVT: \ case BTA_AV_META_MSG_EVT: \
case BTA_AV_RC_FEAT_EVT: \ case BTA_AV_RC_FEAT_EVT: \
case BTA_AV_REMOTE_RSP_EVT: \ case BTA_AV_REMOTE_RSP_EVT: \
case BTA_AV_CA_STATUS_EVT: \
case BTA_AV_CA_DATA_EVT: \
{ \ { \
btc_rc_handler(e, d);\ btc_rc_handler(e, d);\
}break; \ }break; \
@ -382,6 +384,8 @@ static BOOLEAN btc_av_state_idle_handler(btc_sm_event_t event, void *p_data)
case BTA_AV_META_MSG_EVT: case BTA_AV_META_MSG_EVT:
case BTA_AV_RC_FEAT_EVT: case BTA_AV_RC_FEAT_EVT:
case BTA_AV_REMOTE_RSP_EVT: case BTA_AV_REMOTE_RSP_EVT:
case BTA_AV_CA_STATUS_EVT:
case BTA_AV_CA_DATA_EVT:
btc_rc_handler(event, (tBTA_AV *)p_data); btc_rc_handler(event, (tBTA_AV *)p_data);
break; break;
@ -1355,7 +1359,7 @@ static void bte_av_media_callback(tBTA_AV_EVT event, tBTA_AV_MEDIA *p_data)
/* send a command to BT Media Task */ /* send a command to BT Media Task */
btc_a2dp_sink_reset_decoder((UINT8 *)p_data); btc_a2dp_sink_reset_decoder((UINT8 *)p_data);
/* currently only supportes SBC */ /* currently only supports SBC */
a2d_status = A2D_ParsSbcInfo(&sbc_cie, (UINT8 *)p_data, FALSE); a2d_status = A2D_ParsSbcInfo(&sbc_cie, (UINT8 *)p_data, FALSE);
if (a2d_status == A2D_SUCCESS) { if (a2d_status == A2D_SUCCESS) {
btc_msg_t msg; btc_msg_t msg;

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -40,7 +40,7 @@ static void btc_rc_upstreams_evt(UINT16 event, tAVRC_COMMAND *pavrc_cmd, UINT8 c
** Static variables ** Static variables
******************************************************************************/ ******************************************************************************/
/* flag indicating wheter TG/CT is initialized */ /* flag indicating whether TG/CT is initialized */
static uint32_t s_rc_ct_init; static uint32_t s_rc_ct_init;
static uint32_t s_rc_tg_init; static uint32_t s_rc_tg_init;
@ -157,6 +157,11 @@ bool btc_avrc_ct_rn_evt_supported(uint8_t event_id)
true : false; true : false;
} }
bool btc_avrc_ct_check_cover_art_support(void)
{
return (btc_rc_cb.rc_features & BTA_AV_FEAT_COVER_ART);
}
bool btc_avrc_tg_init_p(void) bool btc_avrc_tg_init_p(void)
{ {
return (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC); return (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC);
@ -181,6 +186,52 @@ bool btc_avrc_ct_connected_p(void)
(btc_rc_cb.rc_features & BTA_AV_FEAT_RCTG); (btc_rc_cb.rc_features & BTA_AV_FEAT_RCTG);
} }
void btc_avrc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src)
{
btc_avrc_args_t *dst = (btc_avrc_args_t *)p_dest;
btc_avrc_args_t *src = (btc_avrc_args_t *)p_src;
size_t len;
switch (msg->act) {
#if BTC_AV_CA_INCLUDED
case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT:
len = src->ca_get_img.image_descriptor_len;
dst->ca_get_img.image_descriptor = (uint8_t *)osi_malloc(len);
if (dst->ca_get_img.image_descriptor) {
memcpy(dst->ca_get_img.image_descriptor, src->ca_get_img.image_descriptor, len);
} else {
BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act);
}
break;
#endif
default:
BTC_TRACE_DEBUG("%s Unhandled deep copy %d\n", __FUNCTION__, msg->act);
UNUSED(dst);
UNUSED(src);
UNUSED(len);
break;
}
}
void btc_avrc_arg_deep_free(btc_msg_t *msg)
{
btc_avrc_args_t *arg = (btc_avrc_args_t *)msg->arg;
switch (msg->act) {
#if BTC_AV_CA_INCLUDED
case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT:
if (arg->ca_get_img.image_descriptor) {
osi_free(arg->ca_get_img.image_descriptor);
}
break;
#endif
default:
BTC_TRACE_DEBUG("%s Unhandled deep free %d\n", __FUNCTION__, msg->act);
UNUSED(arg);
break;
}
}
void btc_avrc_tg_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src) void btc_avrc_tg_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src)
{ {
btc_avrc_tg_args_t *dst = (btc_avrc_tg_args_t *) p_dest; btc_avrc_tg_args_t *dst = (btc_avrc_tg_args_t *) p_dest;
@ -499,14 +550,28 @@ static void handle_rc_disconnect (tBTA_AV_RC_CLOSE *p_rc_close)
// clean up the state // clean up the state
btc_rc_cb.rc_handle = 0; btc_rc_cb.rc_handle = 0;
btc_rc_cb.rc_connected = FALSE; btc_rc_cb.rc_connected = FALSE;
#if BTC_AV_CA_INCLUDED
bool cover_art_connected = btc_rc_cb.rc_cover_art_connected;
btc_rc_cb.rc_cover_art_connected = FALSE;
#endif
btc_rc_cb.rc_features = 0; btc_rc_cb.rc_features = 0;
btc_rc_cb.rc_ct_features = 0; btc_rc_cb.rc_ct_features = 0;
btc_rc_cb.rc_tg_features = 0; btc_rc_cb.rc_tg_features = 0;
memset(btc_rc_cb.rc_addr, 0, sizeof(BD_ADDR)); memset(btc_rc_cb.rc_addr, 0, sizeof(BD_ADDR));
memset(btc_rc_cb.rc_ntf, 0, sizeof(btc_rc_cb.rc_ntf)); memset(btc_rc_cb.rc_ntf, 0, sizeof(btc_rc_cb.rc_ntf));
#if BTC_AV_CA_INCLUDED
/* report connection state */ /* report connection state */
if (cover_art_connected) {
/* if rc disconnect, cover art disconnect too */
esp_avrc_ct_cb_param_t param;
memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t));
param.cover_art_state.state = ESP_AVRC_COVER_ART_DISCONNECTED;
param.cover_art_state.reason = BT_STATUS_FAIL;
btc_avrc_ct_cb_to_app(ESP_AVRC_CT_COVER_ART_STATE_EVT, &param);
}
#endif
if (rc_features & BTA_AV_FEAT_RCTG) { if (rc_features & BTA_AV_FEAT_RCTG) {
esp_avrc_ct_cb_param_t param; esp_avrc_ct_cb_param_t param;
memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t)); memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t));
@ -751,7 +816,7 @@ static void btc_rc_upstreams_evt(UINT16 event, tAVRC_COMMAND *pavrc_cmd, UINT8 c
btc_rc_cb.rc_ntf[event_id - 1].registered = TRUE; btc_rc_cb.rc_ntf[event_id - 1].registered = TRUE;
btc_rc_cb.rc_ntf[event_id - 1].label = label; btc_rc_cb.rc_ntf[event_id - 1].label = label;
BTC_TRACE_EVENT("%s: New registerd notification: event_id:0x%x, label:0x%x", BTC_TRACE_EVENT("%s: New registered notification: event_id:0x%x, label:0x%x",
__FUNCTION__, event_id, label); __FUNCTION__, event_id, label);
// set up callback // set up callback
@ -947,7 +1012,7 @@ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data)
memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t)); memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t));
param.conn_stat.connected = true; param.conn_stat.connected = true;
memcpy(param.conn_stat.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); memcpy(param.conn_stat.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t));
btc_avrc_tg_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, &param); btc_avrc_tg_cb_to_app(ESP_AVRC_TG_CONNECTION_STATE_EVT, &param);
} }
} while (0); } while (0);
btc_rc_cb.rc_features = p_data->rc_feat.peer_features; btc_rc_cb.rc_features = p_data->rc_feat.peer_features;
@ -967,6 +1032,36 @@ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data)
handle_rc_passthrough_cmd(&p_data->remote_cmd); handle_rc_passthrough_cmd(&p_data->remote_cmd);
} }
break; break;
#if BTC_AV_CA_INCLUDED
case BTA_AV_CA_STATUS_EVT: {
btc_rc_cb.rc_cover_art_connected = p_data->ca_status.connected;
esp_avrc_ct_cb_param_t param;
memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t));
if (p_data->ca_status.connected) {
param.cover_art_state.state = ESP_AVRC_COVER_ART_CONNECTED;
}
else {
param.cover_art_state.state = ESP_AVRC_COVER_ART_DISCONNECTED;
}
param.cover_art_state.reason = p_data->ca_status.reason;
btc_avrc_ct_cb_to_app(ESP_AVRC_CT_COVER_ART_STATE_EVT, &param);
}
break;
case BTA_AV_CA_DATA_EVT: {
esp_avrc_ct_cb_param_t param;
memset(&param, 0, sizeof(esp_avrc_ct_cb_param_t));
param.cover_art_data.status = p_data->ca_data.status;
param.cover_art_data.final = p_data->ca_data.final;
param.cover_art_data.data_len = p_data->ca_data.data_len;
param.cover_art_data.p_data = p_data->ca_data.p_data;
btc_avrc_ct_cb_to_app(ESP_AVRC_CT_COVER_ART_DATA_EVT, &param);
/* free the data packet now */
if (p_data->ca_data.p_hdr != NULL) {
osi_free(p_data->ca_data.p_hdr);
}
}
break;
#endif /* BTC_AV_CA_INCLUDED */
default: default:
BTC_TRACE_DEBUG("Unhandled RC event : 0x%x", event); BTC_TRACE_DEBUG("Unhandled RC event : 0x%x", event);
} }
@ -1041,7 +1136,7 @@ static void btc_avrc_ct_deinit(void)
BTC_TRACE_API("## %s ##", __FUNCTION__); BTC_TRACE_API("## %s ##", __FUNCTION__);
if (g_a2dp_on_deinit) { if (g_a2dp_on_deinit) {
BTC_TRACE_WARNING("A2DP already deinit, AVRC CT shuold deinit in advance of A2DP !!!"); BTC_TRACE_WARNING("A2DP already deinit, AVRC CT should deinit in advance of A2DP !!!");
} }
if (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) { if (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) {
@ -1255,7 +1350,7 @@ static bt_status_t btc_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code
BTA_AvRemoteCmd(btc_rc_cb.rc_handle, tl, BTA_AvRemoteCmd(btc_rc_cb.rc_handle, tl,
(tBTA_AV_RC)key_code, (tBTA_AV_STATE)key_state); (tBTA_AV_RC)key_code, (tBTA_AV_STATE)key_state);
status = BT_STATUS_SUCCESS; status = BT_STATUS_SUCCESS;
BTC_TRACE_API("%s: succesfully sent passthrough command to BTA", __FUNCTION__); BTC_TRACE_API("%s: successfully sent passthrough command to BTA", __FUNCTION__);
} else { } else {
status = BT_STATUS_FAIL; status = BT_STATUS_FAIL;
BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__);
@ -1267,6 +1362,64 @@ static bt_status_t btc_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code
return status; return status;
} }
#if BTC_AV_CA_INCLUDED
static void btc_avrc_ct_cover_art_connect(UINT16 mtu)
{
if (!btc_rc_cb.rc_cover_art_connected) {
BTA_AvCaOpen(btc_rc_cb.rc_handle, mtu);
}
else {
BTC_TRACE_WARNING("%s: cover art already connected", __FUNCTION__);
}
return;
}
static void btc_avrc_ct_cover_art_disconnect(void)
{
if (btc_rc_cb.rc_cover_art_connected) {
BTA_AvCaClose(btc_rc_cb.rc_handle);
}
else {
BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__);
}
return;
}
static void btc_avrc_ct_cover_art_get_image_properties(UINT8 *image_handle)
{
if (btc_rc_cb.rc_cover_art_connected) {
BTA_AvCaGet(btc_rc_cb.rc_handle, BTA_AV_CA_GET_IMAGE_PROPERTIES, image_handle, NULL, 0);
}
else {
BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__);
}
return;
}
static void btc_avrc_ct_cover_art_get_image(UINT8 *image_handle, UINT8 *image_descriptor, UINT16 image_descriptor_len)
{
if (btc_rc_cb.rc_cover_art_connected) {
BTA_AvCaGet(btc_rc_cb.rc_handle, BTA_AV_CA_GET_IMAGE, image_handle, image_descriptor, image_descriptor_len);
}
else {
BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__);
}
return;
}
static void btc_avrc_ct_cover_art_get_linked_thumbnail(UINT8 *image_handle)
{
if (btc_rc_cb.rc_cover_art_connected) {
BTA_AvCaGet(btc_rc_cb.rc_handle, BTA_AV_CA_GET_LINKED_THUMBNAIL, image_handle, NULL, 0);
}
else {
BTC_TRACE_WARNING("%s: cover art not connected", __FUNCTION__);
}
return;
}
#endif /* BTC_AV_CA_INCLUDED */
/******************************************************************************* /*******************************************************************************
** **
@ -1298,7 +1451,7 @@ static void btc_avrc_tg_init(void)
} }
if (g_a2dp_on_init) { if (g_a2dp_on_init) {
BTC_TRACE_WARNING("AVRC Taget is expected to be initialized in advance of A2DP !!!"); BTC_TRACE_WARNING("AVRC Target is expected to be initialized in advance of A2DP !!!");
} }
} }
@ -1320,7 +1473,7 @@ static void btc_avrc_tg_deinit(void)
BTC_TRACE_API("## %s ##", __FUNCTION__); BTC_TRACE_API("## %s ##", __FUNCTION__);
if (g_a2dp_on_deinit) { if (g_a2dp_on_deinit) {
BTC_TRACE_WARNING("A2DP already deinit, AVRC TG shuold deinit in advance of A2DP !!!"); BTC_TRACE_WARNING("A2DP already deinit, AVRC TG should deinit in advance of A2DP !!!");
} }
if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) { if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) {
@ -1418,6 +1571,28 @@ void btc_avrc_ct_call_handler(btc_msg_t *msg)
btc_avrc_ct_send_set_absolute_volume_cmd(arg->set_abs_vol_cmd.tl, arg->set_abs_vol_cmd.volume); btc_avrc_ct_send_set_absolute_volume_cmd(arg->set_abs_vol_cmd.tl, arg->set_abs_vol_cmd.volume);
break; break;
} }
#if BTC_AV_CA_INCLUDED
case BTC_AVRC_CT_API_COVER_ART_CONNECT_EVT: {
btc_avrc_ct_cover_art_connect(arg->ca_conn.mtu);
break;
}
case BTC_AVRC_CT_API_COVER_ART_DISCONNECT_EVT: {
btc_avrc_ct_cover_art_disconnect();
break;
}
case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_PROPERTIES_EVT: {
btc_avrc_ct_cover_art_get_image_properties(arg->ca_get_img_prop.image_handle);
break;
}
case BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT: {
btc_avrc_ct_cover_art_get_image(arg->ca_get_img.image_handle, arg->ca_get_img.image_descriptor, arg->ca_get_img.image_descriptor_len);
break;
}
case BTC_AVRC_CT_API_COVER_ART_GET_LINKED_THUMBNAIL_EVT: {
btc_avrc_ct_cover_art_get_linked_thumbnail(arg->ca_get_lk_thn.image_handle);
break;
}
#endif
default: default:
BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act);
} }

View File

@ -39,7 +39,14 @@ typedef enum {
BTC_AVRC_STATUS_API_SND_GET_RN_CAPS_EVT, BTC_AVRC_STATUS_API_SND_GET_RN_CAPS_EVT,
BTC_AVRC_NOTIFY_API_SND_REG_NOTIFY_EVT, BTC_AVRC_NOTIFY_API_SND_REG_NOTIFY_EVT,
BTC_AVRC_CTRL_API_SND_SET_PLAYER_SETTING_EVT, BTC_AVRC_CTRL_API_SND_SET_PLAYER_SETTING_EVT,
BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT,
#if BTC_AV_CA_INCLUDED
BTC_AVRC_CT_API_COVER_ART_CONNECT_EVT,
BTC_AVRC_CT_API_COVER_ART_DISCONNECT_EVT,
BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_PROPERTIES_EVT,
BTC_AVRC_CT_API_COVER_ART_GET_IMAGE_EVT,
BTC_AVRC_CT_API_COVER_ART_GET_LINKED_THUMBNAIL_EVT,
#endif
} btc_avrc_act_t; } btc_avrc_act_t;
typedef struct { typedef struct {
@ -77,6 +84,28 @@ typedef struct {
uint8_t volume; uint8_t volume;
} set_abs_vol_cmd_t; } set_abs_vol_cmd_t;
#if BTC_AV_CA_INCLUDED
typedef struct {
uint16_t mtu;
} ca_conn_t;
typedef struct {
uint8_t image_handle[7];
} ca_get_img_prop_t;
typedef struct {
uint8_t image_handle[7];
uint16_t image_descriptor_len;
uint8_t *image_descriptor;
} ca_get_img_t;
typedef struct {
uint8_t image_handle[7];
} ca_get_lk_thn_t;
#endif /* BTC_AV_CA_INCLUDED */
/* btc_avrc_args_t */ /* btc_avrc_args_t */
typedef union { typedef union {
pt_cmd_t pt_cmd; pt_cmd_t pt_cmd;
@ -85,6 +114,12 @@ typedef union {
ps_cmd_t ps_cmd; ps_cmd_t ps_cmd;
get_caps_cmd_t get_caps_cmd; get_caps_cmd_t get_caps_cmd;
set_abs_vol_cmd_t set_abs_vol_cmd; set_abs_vol_cmd_t set_abs_vol_cmd;
#if BTC_AV_CA_INCLUDED
ca_conn_t ca_conn;
ca_get_img_prop_t ca_get_img_prop;
ca_get_img_t ca_get_img;
ca_get_lk_thn_t ca_get_lk_thn;
#endif
} btc_avrc_args_t; } btc_avrc_args_t;
/* btc_avrc_tg_act_t */ /* btc_avrc_tg_act_t */
@ -124,6 +159,9 @@ typedef struct {
typedef struct { typedef struct {
BOOLEAN rc_connected; BOOLEAN rc_connected;
#if BTC_AV_CA_INCLUDED
BOOLEAN rc_cover_art_connected;
#endif
UINT8 rc_handle; UINT8 rc_handle;
tBTA_AV_FEAT rc_features; tBTA_AV_FEAT rc_features;
UINT16 rc_ct_features; UINT16 rc_ct_features;
@ -162,6 +200,8 @@ BOOLEAN btc_rc_get_connected_peer(BD_ADDR peer_addr);
********************************************************************************/ ********************************************************************************/
void btc_avrc_ct_call_handler(btc_msg_t *msg); void btc_avrc_ct_call_handler(btc_msg_t *msg);
void btc_avrc_tg_call_handler(btc_msg_t *msg); void btc_avrc_tg_call_handler(btc_msg_t *msg);
void btc_avrc_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src);
void btc_avrc_arg_deep_free(btc_msg_t *msg);
void btc_avrc_tg_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src); void btc_avrc_tg_arg_deep_copy(btc_msg_t *msg, void *p_dest, void *p_src);
void btc_avrc_tg_arg_deep_free(btc_msg_t *msg); void btc_avrc_tg_arg_deep_free(btc_msg_t *msg);
@ -179,6 +219,7 @@ uint16_t btc_avrc_tg_get_rn_supported_evt(void);
bool btc_avrc_tg_check_rn_supported_evt(uint16_t evt_set); bool btc_avrc_tg_check_rn_supported_evt(uint16_t evt_set);
bool btc_avrc_tg_rn_evt_supported(uint8_t event_id); bool btc_avrc_tg_rn_evt_supported(uint8_t event_id);
bool btc_avrc_ct_rn_evt_supported(uint8_t event_id); bool btc_avrc_ct_rn_evt_supported(uint8_t event_id);
bool btc_avrc_ct_check_cover_art_support(void);
#endif ///BTC_AV_INCLUDED == TRUE #endif ///BTC_AV_INCLUDED == TRUE

View File

@ -39,6 +39,19 @@
#define UC_BT_A2DP_ENABLED FALSE #define UC_BT_A2DP_ENABLED FALSE
#endif #endif
//AVRCP
#ifdef CONFIG_BT_AVRCP_ENABLED
#define UC_BT_AVRCP_ENABLED TRUE
#ifdef CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED
#define UC_BT_AVRCP_CT_COVER_ART_ENABLED CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED
#else
#define UC_BT_AVRCP_CT_COVER_ART_ENABLED FALSE
#endif
#else
#define UC_BT_AVRCP_ENABLED FALSE
#define UC_BT_AVRCP_CT_COVER_ART_ENABLED FALSE
#endif
//SPP //SPP
#ifdef CONFIG_BT_SPP_ENABLED #ifdef CONFIG_BT_SPP_ENABLED
#define UC_BT_SPP_ENABLED CONFIG_BT_SPP_ENABLED #define UC_BT_SPP_ENABLED CONFIG_BT_SPP_ENABLED
@ -111,6 +124,13 @@
#define UC_BT_ENC_KEY_SIZE_CTRL_MODE 0 #define UC_BT_ENC_KEY_SIZE_CTRL_MODE 0
#endif #endif
//GOEPC (BT)
#ifdef CONFIG_BT_GOEPC_ENABLED
#define UC_BT_GOEPC_ENABLED CONFIG_BT_GOEPC_ENABLED
#else
#define UC_BT_GOEPC_ENABLED FALSE
#endif
//BLE //BLE
#ifdef CONFIG_BT_BLE_ENABLED #ifdef CONFIG_BT_BLE_ENABLED
#define UC_BT_BLE_ENABLED CONFIG_BT_BLE_ENABLED #define UC_BT_BLE_ENABLED CONFIG_BT_BLE_ENABLED

View File

@ -91,6 +91,11 @@
#define SBC_DEC_INCLUDED TRUE #define SBC_DEC_INCLUDED TRUE
#define BTC_AV_SRC_INCLUDED TRUE #define BTC_AV_SRC_INCLUDED TRUE
#define SBC_ENC_INCLUDED TRUE #define SBC_ENC_INCLUDED TRUE
#if UC_BT_AVRCP_CT_COVER_ART_ENABLED
#define BTA_AV_CA_INCLUDED TRUE
#define BTC_AV_CA_INCLUDED TRUE
#define AVRC_CA_INCLUDED TRUE
#endif /* UC_BT_AVRCP_CT_COVER_ART_ENABLED */
#endif /* UC_BT_A2DP_ENABLED */ #endif /* UC_BT_A2DP_ENABLED */
#if (UC_BT_SPP_ENABLED == TRUE) #if (UC_BT_SPP_ENABLED == TRUE)
@ -171,6 +176,13 @@
#define BTC_HD_INCLUDED TRUE #define BTC_HD_INCLUDED TRUE
#endif /* UC_BT_HID_DEVICE_ENABLED */ #endif /* UC_BT_HID_DEVICE_ENABLED */
#if UC_BT_GOEPC_ENABLED
#ifndef OBEX_INCLUDED
#define OBEX_INCLUDED TRUE
#endif
#define GOEPC_INCLUDED TRUE
#endif /* UC_BT_GOEPC_ENABLED */
#endif /* UC_BT_CLASSIC_ENABLED */ #endif /* UC_BT_CLASSIC_ENABLED */
/* This is set to enable use of GAP L2CAP connections. */ /* This is set to enable use of GAP L2CAP connections. */
@ -376,6 +388,10 @@
#define BTC_AV_INCLUDED FALSE #define BTC_AV_INCLUDED FALSE
#endif #endif
#ifndef BTC_AV_CA_INCLUDED
#define BTC_AV_CA_INCLUDED FALSE
#endif
#ifndef BTC_AV_SINK_INCLUDED #ifndef BTC_AV_SINK_INCLUDED
#define BTC_AV_SINK_INCLUDED FALSE #define BTC_AV_SINK_INCLUDED FALSE
#endif #endif
@ -449,6 +465,10 @@
#define BTA_AV_INCLUDED FALSE #define BTA_AV_INCLUDED FALSE
#endif #endif
#ifndef BTA_AV_CA_INCLUDED
#define BTA_AV_CA_INCLUDED FALSE
#endif
#ifndef BTA_AV_SINK_INCLUDED #ifndef BTA_AV_SINK_INCLUDED
#define BTA_AV_SINK_INCLUDED FALSE #define BTA_AV_SINK_INCLUDED FALSE
#endif #endif
@ -1809,6 +1829,26 @@
#define OBX_FCR_TX_POOL_ID 3 #define OBX_FCR_TX_POOL_ID 3
#endif #endif
/* Maximum OBEX connection allowed */
#ifndef OBEX_MAX_CONNECTION
#define OBEX_MAX_CONNECTION 3
#endif
/* Maximum OBEX server allowed */
#ifndef OBEX_MAX_SERVER
#define OBEX_MAX_SERVER 2
#endif
/******************************************************************************
**
** GOEP
**
******************************************************************************/
/* Maximum GOEP client connection allowed */
#ifndef GOEPC_MAX_CONNECTION
#define GOEPC_MAX_CONNECTION 3
#endif
/****************************************************************************** /******************************************************************************
** **
@ -2141,6 +2181,20 @@
#define HID_HOST_REPAGE_WIN (2) #define HID_HOST_REPAGE_WIN (2)
#endif #endif
/*************************************************************************
** Definitions for OBEX
*/
#ifndef OBEX_INCLUDED
#define OBEX_INCLUDED FALSE
#endif
/*************************************************************************
** Definitions for OBEX
*/
#ifndef GOEPC_INCLUDED
#define GOEPC_INCLUDED FALSE
#endif
/************************************************************************* /*************************************************************************
* A2DP Definitions * A2DP Definitions
*/ */
@ -2179,6 +2233,10 @@
#define AVRC_INCLUDED FALSE #define AVRC_INCLUDED FALSE
#endif #endif
#ifndef AVRC_CA_INCLUDED
#define AVRC_CA_INCLUDED FALSE
#endif
#ifndef AVRC_METADATA_INCLUDED #ifndef AVRC_METADATA_INCLUDED
#if AVRC_INCLUDED == TRUE #if AVRC_INCLUDED == TRUE
#define AVRC_METADATA_INCLUDED TRUE #define AVRC_METADATA_INCLUDED TRUE

View File

@ -316,6 +316,27 @@ static inline void trc_dump_buffer(const char *prefix, uint8_t *data, uint16_t l
#define AVRC_TRACE_EVENT(fmt, args...) {if (avrc_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_AVRC", fmt, ## args);} #define AVRC_TRACE_EVENT(fmt, args...) {if (avrc_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_AVRC", fmt, ## args);}
#define AVRC_TRACE_DEBUG(fmt, args...) {if (avrc_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_AVRC", fmt, ## args);} #define AVRC_TRACE_DEBUG(fmt, args...) {if (avrc_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_AVRC", fmt, ## args);}
/* Define tracing for OBEX */
#define OBEX_TRACE_ERROR(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("BT_OBEX", fmt, ## args);}
#define OBEX_TRACE_WARNING(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("BT_OBEX", fmt, ## args);}
#define OBEX_TRACE_API(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("BT_OBEX", fmt, ## args);}
#define OBEX_TRACE_EVENT(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_OBEX", fmt, ## args);}
#define OBEX_TRACE_DEBUG(fmt, args...) {if (obex_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_OBEX", fmt, ## args);}
/* Define tracing for OBEX_TL_L2CAP */
#define OBEX_TL_L2CAP_TRACE_ERROR(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("OBEX_TL_L2CAP", fmt, ## args);}
#define OBEX_TL_L2CAP_TRACE_WARNING(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("OBEX_TL_L2CAP", fmt, ## args);}
#define OBEX_TL_L2CAP_TRACE_API(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("OBEX_TL_L2CAP", fmt, ## args);}
#define OBEX_TL_L2CAP_TRACE_EVENT(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("OBEX_TL_L2CAP", fmt, ## args);}
#define OBEX_TL_L2CAP_TRACE_DEBUG(fmt, args...) {if (obex_tl_l2cap_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("OBEX_TL_L2CAP", fmt, ## args);}
/* Define tracing for GOEPC */
#define GOEPC_TRACE_ERROR(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(AVRC, ERROR)) BT_PRINT_E("BT_GOEPC", fmt, ## args);}
#define GOEPC_TRACE_WARNING(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_WARNING && BT_LOG_LEVEL_CHECK(AVRC, WARNING)) BT_PRINT_W("BT_GOEPC", fmt, ## args);}
#define GOEPC_TRACE_API(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_API && BT_LOG_LEVEL_CHECK(AVRC,API)) BT_PRINT_I("BT_GOEPC", fmt, ## args);}
#define GOEPC_TRACE_EVENT(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_EVENT && BT_LOG_LEVEL_CHECK(AVRC,EVENT)) BT_PRINT_D("BT_GOEPC", fmt, ## args);}
#define GOEPC_TRACE_DEBUG(fmt, args...) {if (goepc_cb.trace_level >= BT_TRACE_LEVEL_DEBUG && BT_LOG_LEVEL_CHECK(AVRC,DEBUG)) BT_PRINT_D("BT_GOEPC", fmt, ## args);}
/* MCAP /* MCAP
*/ */
#define MCA_TRACE_ERROR(fmt, args...) {if (mca_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(MCA, ERROR)) BT_PRINT_E("BT_MCA", fmt, ## args);} #define MCA_TRACE_ERROR(fmt, args...) {if (mca_cb.trace_level >= BT_TRACE_LEVEL_ERROR && BT_LOG_LEVEL_CHECK(MCA, ERROR)) BT_PRINT_E("BT_MCA", fmt, ## args);}
@ -484,6 +505,26 @@ extern UINT8 btif_trace_level;
#define AVRC_TRACE_DEBUG(fmt, args...) #define AVRC_TRACE_DEBUG(fmt, args...)
#define AVRC_TRACE_API(fmt, args...) #define AVRC_TRACE_API(fmt, args...)
/* Define tracing for OBEX */
#define OBEX_TRACE_ERROR(fmt, args...)
#define OBEX_TRACE_WARNING(fmt, args...)
#define OBEX_TRACE_API(fmt, args...)
#define OBEX_TRACE_EVENT(fmt, args...)
#define OBEX_TRACE_DEBUG(fmt, args...)
#define OBEX_TL_L2CAP_TRACE_ERROR(fmt, args...)
#define OBEX_TL_L2CAP_TRACE_WARNING(fmt, args...)
#define OBEX_TL_L2CAP_TRACE_API(fmt, args...)
#define OBEX_TL_L2CAP_TRACE_EVENT(fmt, args...)
#define OBEX_TL_L2CAP_TRACE_DEBUG(fmt, args...)
/* Define tracing for GOEPC */
#define GOEPC_TRACE_ERROR(fmt, args...)
#define GOEPC_TRACE_WARNING(fmt, args...)
#define GOEPC_TRACE_API(fmt, args...)
#define GOEPC_TRACE_EVENT(fmt, args...)
#define GOEPC_TRACE_DEBUG(fmt, args...)
/* MCAP /* MCAP
*/ */
#define MCA_TRACE_ERROR(fmt, args...) #define MCA_TRACE_ERROR(fmt, args...)

View File

@ -27,8 +27,8 @@
#include <string.h> #include <string.h>
/* Stack Configuation Related Init Definaton /* Stack Configuration Related Init Definaton
* TODO: Now Just Unmask these defination until stack layer is OK * TODO: Now Just Unmask these definition until stack layer is OK
*/ */
#ifndef BTA_INCLUDED #ifndef BTA_INCLUDED
@ -91,6 +91,15 @@
#endif #endif
#endif #endif
#if (defined(OBEX_INCLUDED) && OBEX_INCLUDED == TRUE)
#include "stack/obex_api.h"
#endif
#if (defined(GOEPC_INCLUDED) && GOEPC_INCLUDED == TRUE)
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#endif
//BTA Modules //BTA Modules
#if BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE #if BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE
#include "bta/bta_api.h" #include "bta/bta_api.h"
@ -267,6 +276,14 @@ void BTE_DeinitStack(void)
} }
#endif // BTA_INCLUDED == TRUE #endif // BTA_INCLUDED == TRUE
#if (defined(GOEPC_INCLUDED) && GOEPC_INCLUDED == TRUE)
GOEPC_Deinit();
#endif
#if (defined(OBEX_INCLUDED) && OBEX_INCLUDED == TRUE)
OBEX_Deinit();
#endif
#if (defined(HID_DEV_INCLUDED) && HID_DEV_INCLUDED == TRUE) #if (defined(HID_DEV_INCLUDED) && HID_DEV_INCLUDED == TRUE)
HID_DevDeinit(); HID_DevDeinit();
#endif #endif
@ -388,6 +405,18 @@ bt_status_t BTE_InitStack(void)
MCA_Init(); MCA_Init();
#endif #endif
#if (defined(OBEX_INCLUDED) && OBEX_INCLUDED == TRUE)
if (OBEX_Init() != OBEX_SUCCESS) {
goto error_exit;
}
#endif
#if (defined(GOEPC_INCLUDED) && GOEPC_INCLUDED == TRUE)
if (GOEPC_Init() != GOEP_SUCCESS) {
goto error_exit;
}
#endif
//BTA Modules //BTA Modules
#if (BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE) #if (BTA_INCLUDED == TRUE && BTA_DYNAMIC_MEMORY == TRUE)
if ((bta_sys_cb_ptr = (tBTA_SYS_CB *)osi_malloc(sizeof(tBTA_SYS_CB))) == NULL) { if ((bta_sys_cb_ptr = (tBTA_SYS_CB *)osi_malloc(sizeof(tBTA_SYS_CB))) == NULL) {

View File

@ -430,10 +430,10 @@ BOOLEAN avct_lcb_last_ccb(tAVCT_LCB *p_lcb, tAVCT_CCB *p_ccb_last)
tAVCT_CCB *p_ccb = &avct_cb.ccb[0]; tAVCT_CCB *p_ccb = &avct_cb.ccb[0];
int i; int i;
AVCT_TRACE_WARNING("avct_lcb_last_ccb"); AVCT_TRACE_DEBUG("avct_lcb_last_ccb");
for (i = 0; i < AVCT_NUM_CONN; i++, p_ccb++) { for (i = 0; i < AVCT_NUM_CONN; i++, p_ccb++) {
AVCT_TRACE_WARNING("%x: aloc:%d, lcb:%p/%p, ccb:%p/%p", AVCT_TRACE_DEBUG("%x: aloc:%d, lcb:%p/%p, ccb:%p/%p",
i, p_ccb->allocated, p_ccb->p_lcb, p_lcb, p_ccb, p_ccb_last); i, p_ccb->allocated, p_ccb->p_lcb, p_lcb, p_ccb, p_ccb_last);
if (p_ccb->allocated && (p_ccb->p_lcb == p_lcb) && (p_ccb != p_ccb_last)) { if (p_ccb->allocated && (p_ccb->p_lcb == p_lcb) && (p_ccb != p_ccb_last)) {
return FALSE; return FALSE;
} }

View File

@ -29,8 +29,8 @@
#if (defined(AVRC_INCLUDED) && AVRC_INCLUDED == TRUE) #if (defined(AVRC_INCLUDED) && AVRC_INCLUDED == TRUE)
#ifndef SDP_AVRCP_1_5 #ifndef SDP_AVRCP_1_6
#define SDP_AVRCP_1_5 TRUE #define SDP_AVRCP_1_6 TRUE
#endif #endif
#ifndef SDP_AVCTP_1_4 #ifndef SDP_AVCTP_1_4
@ -52,7 +52,7 @@ const tSDP_PROTOCOL_ELEM avrc_proto_list [] = {
#if SDP_AVCTP_1_4 == TRUE #if SDP_AVCTP_1_4 == TRUE
{UUID_PROTOCOL_AVCTP, 1, {AVCT_REV_1_4, 0} } {UUID_PROTOCOL_AVCTP, 1, {AVCT_REV_1_4, 0} }
#else #else
#if (SDP_AVRCP_1_4 == TRUE || SDP_AVRCP_1_5 == TRUE) #if SDP_AVRCP_1_6 == TRUE
{UUID_PROTOCOL_AVCTP, 1, {AVCT_REV_1_3, 0} } {UUID_PROTOCOL_AVCTP, 1, {AVCT_REV_1_3, 0} }
#else #else
#if AVRC_METADATA_INCLUDED == TRUE #if AVRC_METADATA_INCLUDED == TRUE
@ -64,7 +64,7 @@ const tSDP_PROTOCOL_ELEM avrc_proto_list [] = {
#endif #endif
}; };
#if SDP_AVRCP_1_5 == TRUE #if SDP_AVRCP_1_6 == TRUE
const tSDP_PROTO_LIST_ELEM avrc_add_proto_list [] = { const tSDP_PROTO_LIST_ELEM avrc_add_proto_list [] = {
{ {
AVRC_NUM_PROTO_ELEMS, AVRC_NUM_PROTO_ELEMS,
@ -251,7 +251,7 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide
/* add service class id list */ /* add service class id list */
class_list[0] = service_uuid; class_list[0] = service_uuid;
#if (SDP_AVCTP_1_4 == TRUE || SDP_AVRCP_1_5 == TRUE) #if (SDP_AVCTP_1_4 == TRUE || SDP_AVRCP_1_6 == TRUE)
if ( service_uuid == UUID_SERVCLASS_AV_REMOTE_CONTROL ) { if ( service_uuid == UUID_SERVCLASS_AV_REMOTE_CONTROL ) {
class_list[1] = UUID_SERVCLASS_AV_REM_CTRL_CONTROL; class_list[1] = UUID_SERVCLASS_AV_REM_CTRL_CONTROL;
count = 2; count = 2;
@ -263,7 +263,7 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide
result &= SDP_AddProtocolList(sdp_handle, AVRC_NUM_PROTO_ELEMS, (tSDP_PROTOCOL_ELEM *)avrc_proto_list); result &= SDP_AddProtocolList(sdp_handle, AVRC_NUM_PROTO_ELEMS, (tSDP_PROTOCOL_ELEM *)avrc_proto_list);
/* add profile descriptor list */ /* add profile descriptor list */
#if SDP_AVRCP_1_5 == TRUE #if SDP_AVRCP_1_6 == TRUE
if (browsing_en) { if (browsing_en) {
add_additional_protocol_list = TRUE; add_additional_protocol_list = TRUE;
} else if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET && } else if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET &&
@ -277,7 +277,7 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide
result &= SDP_AddAdditionProtoLists( sdp_handle, 1, (tSDP_PROTO_LIST_ELEM *)avrc_add_proto_list); result &= SDP_AddAdditionProtoLists( sdp_handle, 1, (tSDP_PROTO_LIST_ELEM *)avrc_add_proto_list);
} }
result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_5); result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_6);
#else #else
#if AVRC_METADATA_INCLUDED == TRUE #if AVRC_METADATA_INCLUDED == TRUE
result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_3); result &= SDP_AddProfileDescriptorList(sdp_handle, UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_3);
@ -292,6 +292,13 @@ UINT16 AVRC_AddRecord(UINT16 service_uuid, char *p_service_name, char *p_provide
} else if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET && media_player_virtual_filesystem_supported) { } else if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_TARGET && media_player_virtual_filesystem_supported) {
supported_feature |= AVRC_SUPF_TG_BROWSE; supported_feature |= AVRC_SUPF_TG_BROWSE;
} }
#if AVRC_CA_INCLUDED
if (service_uuid == UUID_SERVCLASS_AV_REM_CTRL_CONTROL || service_uuid == UUID_SERVCLASS_AV_REMOTE_CONTROL) {
supported_feature |= AVRC_SUPF_CT_COVER_ART_GIP;
supported_feature |= AVRC_SUPF_CT_COVER_ART_GI;
supported_feature |= AVRC_SUPF_CT_COVER_ART_GLT;
}
#endif
/* add supported feature */ /* add supported feature */
p = temp; p = temp;
UINT16_TO_BE_STREAM(p, supported_feature); UINT16_TO_BE_STREAM(p, supported_feature);
@ -383,7 +390,7 @@ bt_status_t AVRC_Init(void)
** **
** Function AVRC_Deinit ** Function AVRC_Deinit
** **
** Description This function is called at stack shotdown to free the ** Description This function is called at stack shutdown to free the
** control block (if using dynamic memory), and deinitializes the ** control block (if using dynamic memory), and deinitializes the
** control block and tracing level. ** control block and tracing level.
** **

View File

@ -0,0 +1,376 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "osi/osi.h"
#include "osi/allocator.h"
#include "common/bt_target.h"
#include "stack/obex_api.h"
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#include "goep_int.h"
#if (GOEPC_INCLUDED == TRUE)
/*******************************************************************************
**
** Function GOEPC_Init
**
** Description Initialize GOEP Client role, must call before using any
** other GOEPC APIs
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_Init(void)
{
#if (GOEP_DYNAMIC_MEMORY)
if (!goepc_cb_ptr) {
goepc_cb_ptr = (tGOEPC_CB *)osi_malloc(sizeof(tGOEPC_CB));
if (!goepc_cb_ptr) {
return GOEP_NO_RESOURCES;
}
}
#endif /* #if (GOEP_DYNAMIC_MEMORY) */
memset(&goepc_cb, 0, sizeof(tGOEPC_CB));
goepc_cb.trace_level = BT_TRACE_LEVEL_ERROR;
return GOEP_SUCCESS;
}
/*******************************************************************************
**
** Function GOEPC_Deinit
**
** Description Deinit GOEP Client role, once deinit, can not use any other
** GOEPC APIs until call GOEPC_Init again
**
*******************************************************************************/
void GOEPC_Deinit(void)
{
#if (GOEP_DYNAMIC_MEMORY)
if (goepc_cb_ptr) {
osi_free(goepc_cb_ptr);
goepc_cb_ptr = NULL;
}
#endif /* #if (GOEP_DYNAMIC_MEMORY) */
}
/*******************************************************************************
**
** Function GOEPC_Open
**
** Description Start the progress to establish a GOEP connection to server
**
** Returns GOEP_SUCCESS if successful, otherwise failed, when the
** connection is established, GOEPC_OPENED_EVT will come
**
*******************************************************************************/
UINT16 GOEPC_Open(tOBEX_SVR_INFO *svr, tGOEPC_EVT_CBACK callback, UINT16 *out_handle)
{
UINT16 ret = GOEP_SUCCESS;
tGOEPC_CCB *p_ccb = NULL;
do {
/* check parameter, allow out_handle to be NULL */
if (svr == NULL || callback == NULL) {
ret = GOEP_INVALID_PARAM;
break;
}
p_ccb = goepc_allocate_ccb();
if (p_ccb == NULL) {
ret = GOEP_NO_RESOURCES;
break;
}
if (OBEX_CreateConn(svr, goepc_obex_callback, &p_ccb->obex_handle) != OBEX_SUCCESS) {
ret = GOEP_TL_ERROR;
break;
}
/* success */
p_ccb->callback = callback;
p_ccb->state = GOEPC_STATE_OPENING;
if (out_handle) {
*out_handle = p_ccb->allocated;
}
} while (0);
if (ret != GOEP_SUCCESS && p_ccb != NULL) {
goepc_free_ccb(p_ccb);
}
return ret;
}
/*******************************************************************************
**
** Function GOEPC_Close
**
** Description Close a GOEP connection immediately
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_Close(UINT16 handle)
{
tGOEPC_CCB *p_ccb = NULL;
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
return GOEP_BAD_HANDLE;
}
p_ccb = &goepc_cb.ccb[ccb_idx];
if (p_ccb->obex_handle) {
OBEX_RemoveConn(p_ccb->obex_handle);
}
goepc_free_ccb(p_ccb);
return GOEP_SUCCESS;
}
/*******************************************************************************
**
** Function GOEPC_SendRequest
**
** Description Send the prepared request packet to server
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_SendRequest(UINT16 handle)
{
UINT16 ret = GOEP_SUCCESS;
tGOEPC_CCB *p_ccb = NULL;
BOOLEAN final = FALSE;
do {
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
ret = GOEP_BAD_HANDLE;
break;
}
p_ccb = &goepc_cb.ccb[ccb_idx];
if (p_ccb->pkt == NULL) {
ret = GOEP_INVALID_STATE;
break;
}
final = OBEX_CheckFinalBit(p_ccb->pkt);
/* check whether state machine allow this operation */
if (!goepc_check_obex_req_allow(p_ccb->state, final)) {
ret = GOEP_INVALID_STATE;
break;
}
if (p_ccb->congest) {
ret = GOEP_CONGEST;
break;
}
/* execute srm state machine */
goepc_srm_sm_execute(p_ccb, TRUE, p_ccb->pkt_srm_en, p_ccb->pkt_srm_wait);
tGOEPC_DATA data;
data.pkt = p_ccb->pkt;
p_ccb->last_pkt_opcode = p_ccb->curr_pkt_opcode;
p_ccb->pkt = NULL;
p_ccb->pkt_srm_en = FALSE;
p_ccb->pkt_srm_wait = FALSE;
/* execute main state machine */
if (final) {
goepc_sm_execute(p_ccb, GOEPC_SM_EVENT_REQ_FB, &data);
}
else {
goepc_sm_execute(p_ccb, GOEPC_SM_EVENT_REQ, &data);
}
/* since goepc_sm_execute may free ccb, can not access ccb here */
} while (0);
return ret;
}
/*******************************************************************************
**
** Function GOEPC_PrepareRequest
**
** Description Prepare a request packet, packet will be store internally
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_PrepareRequest(UINT16 handle, tOBEX_PARSE_INFO *info, UINT16 buff_size)
{
UINT16 ret = GOEP_SUCCESS;
tGOEPC_CCB *p_ccb = NULL;
BT_HDR *pkt = NULL;
do {
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
ret = GOEP_BAD_HANDLE;
break;
}
p_ccb = &goepc_cb.ccb[ccb_idx];
if (info == NULL || buff_size < OBEX_MIN_PACKET_SIZE) {
ret = GOEP_INVALID_PARAM;
break;
}
if (p_ccb->pkt != NULL) {
ret = GOEP_INVALID_STATE;
break;
}
if (!goepc_check_obex_req_param(info)) {
ret = GOEP_INVALID_PARAM;
break;
}
if (OBEX_BuildRequest(info, buff_size, &pkt) != OBEX_SUCCESS) {
ret = GOEP_NO_RESOURCES;
break;
}
p_ccb->curr_pkt_opcode = info->opcode;
p_ccb->pkt = pkt;
} while (0);
return ret;
}
/*******************************************************************************
**
** Function GOEPC_DropRequest
**
** Description Drop the prepared internal request packet
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_DropRequest(UINT16 handle)
{
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
return GOEP_BAD_HANDLE;
}
tGOEPC_CCB *p_ccb = &goepc_cb.ccb[ccb_idx];
if (p_ccb->pkt == NULL) {
return GOEP_INVALID_STATE;
}
osi_free(p_ccb->pkt);
p_ccb->pkt = NULL;
p_ccb->pkt_srm_en = FALSE;
p_ccb->pkt_srm_wait = FALSE;
return GOEP_SUCCESS;
}
/*******************************************************************************
**
** Function GOEPC_RequestSetSRM
**
** Description Modify the prepared internal request packet, append SRM header
** or SRMP header
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_RequestSetSRM(UINT16 handle, BOOLEAN srm_en, BOOLEAN srm_wait)
{
UINT16 ret = GOEP_SUCCESS;
tGOEPC_CCB *p_ccb = NULL;
do {
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
ret = GOEP_BAD_HANDLE;
break;
}
p_ccb = &goepc_cb.ccb[ccb_idx];
if (!srm_en && !srm_wait) {
ret = GOEP_INVALID_PARAM;
break;
}
if (p_ccb->pkt == NULL) {
ret = GOEP_INVALID_STATE;
break;
}
if (srm_en) {
if (OBEX_AppendHeaderSRM(p_ccb->pkt, OBEX_SRM_ENABLE) == OBEX_SUCCESS) {
p_ccb->pkt_srm_en = TRUE;
}
else {
ret = GOEP_NO_RESOURCES;
break;
}
}
if (srm_wait) {
if (OBEX_AppendHeaderSRMP(p_ccb->pkt, OBEX_SRMP_WAIT) == OBEX_SUCCESS) {
p_ccb->pkt_srm_wait = TRUE;
}
else {
ret = GOEP_NO_RESOURCES;
break;
}
}
} while (0);
return ret;
}
/*******************************************************************************
**
** Function GOEPC_RequestAddHeader
**
** Description Modify the prepared internal request packet, append header
**
** Returns GOEP_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, UINT16 data_len)
{
UINT16 ret = GOEP_SUCCESS;
tGOEPC_CCB *p_ccb = NULL;
do {
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= GOEPC_MAX_CONNECTION || !goepc_cb.ccb[ccb_idx].allocated) {
ret = GOEP_BAD_HANDLE;
break;
}
p_ccb = &goepc_cb.ccb[ccb_idx];
if (p_ccb->pkt == NULL) {
ret = GOEP_INVALID_STATE;
break;
}
if (data == NULL || data_len == 0) {
ret = GOEP_INVALID_PARAM;
break;
}
if (OBEX_AppendHeaderRaw(p_ccb->pkt, header_id, data, data_len) != OBEX_SUCCESS) {
ret = GOEP_NO_RESOURCES;
break;
}
} while (0);
return ret;
}
#endif /* #if (GOEPC_INCLUDED == TRUE) */

View File

@ -0,0 +1,528 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "osi/osi.h"
#include "osi/allocator.h"
#include "common/bt_target.h"
#include "stack/obex_api.h"
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#include "goep_int.h"
#if (GOEPC_INCLUDED == TRUE)
#if GOEP_DYNAMIC_MEMORY == FALSE
tGOEPC_CB goepc_cb;
#else
tGOEPC_CB *goepc_cb_ptr = NULL;
#endif
tGOEPC_CCB *goepc_allocate_ccb(void)
{
tGOEPC_CCB *p_ccb = NULL;
for (int i = 0; i < GOEPC_MAX_CONNECTION; ++i) {
if (!goepc_cb.ccb[i].allocated) {
goepc_cb.ccb[i].allocated = i + 1;
p_ccb = &goepc_cb.ccb[i];
break;
}
}
return p_ccb;
}
void goepc_free_ccb(tGOEPC_CCB *p_ccb)
{
if (p_ccb->pkt != NULL) {
osi_free(p_ccb->pkt);
}
memset(p_ccb, 0, sizeof(tGOEPC_CCB));
}
BOOLEAN goepc_check_obex_req_param(tOBEX_PARSE_INFO *info)
{
BOOLEAN ret = TRUE;
switch (info->opcode)
{
case OBEX_OPCODE_CONNECT:
if (info->max_packet_length < 255 || info->obex_version_number == 0) {
ret = FALSE;
}
break;
case OBEX_OPCODE_DISCONNECT:
case OBEX_OPCODE_PUT:
case OBEX_OPCODE_PUT_FINAL:
case OBEX_OPCODE_GET:
case OBEX_OPCODE_GET_FINAL:
case OBEX_OPCODE_SETPATH:
case OBEX_OPCODE_ACTION:
case OBEX_OPCODE_SESSION:
/* opcode allowed */
break;
case OBEX_OPCODE_ABORT:
default:
ret = FALSE;
/* opcode not allowed */
break;
}
return ret;
}
static tGOEPC_CCB *find_ccb_by_obex_handle(UINT16 obex_handle)
{
tGOEPC_CCB *p_ccb = NULL;
for (int i = 0; i < GOEPC_MAX_CONNECTION; ++i) {
if (goepc_cb.ccb[i].allocated && goepc_cb.ccb[i].obex_handle == obex_handle) {
p_ccb = &goepc_cb.ccb[i];
}
}
return p_ccb;
}
static void goepc_extra_srm_rsp(UINT8 opcode, BT_HDR *pkt, BOOLEAN *srm_en, BOOLEAN *srm_wait)
{
tOBEX_PARSE_INFO info;
BOOLEAN srm_found = FALSE;
BOOLEAN srmp_found = FALSE;
if (OBEX_ParseResponse(pkt, opcode, &info) == OBEX_SUCCESS) {
UINT8 *header = NULL;
while((header = OBEX_GetNextHeader(pkt, &info)) != NULL) {
switch (*header)
{
case OBEX_HEADER_ID_SRM:
if (header[1] == OBEX_SRM_ENABLE) {
*srm_en = TRUE;
}
srm_found = TRUE;
break;
case OBEX_HEADER_ID_SRM_PARAM:
switch (header[1])
{
case OBEX_SRMP_ADD_PKT:
/* goep should not use this */
break;
case OBEX_SRMP_WAIT:
*srm_wait = TRUE;
break;
case OBEX_SRMP_ADD_PKT_WAIT:
/* goep should not use this */
break;
default:
break;
}
srmp_found = TRUE;
break;
default:
break;
}
if (srm_found && srmp_found) {
break;
}
}
}
}
static void goepc_act_congest(tGOEPC_CCB *p_ccb)
{
p_ccb->congest = TRUE;
p_ccb->callback(p_ccb->allocated, GOEPC_CONGEST_EVT, NULL);
}
static void goepc_act_uncongest(tGOEPC_CCB *p_ccb)
{
p_ccb->congest = FALSE;
p_ccb->callback(p_ccb->allocated, GOEPC_UNCONGEST_EVT, NULL);
}
static void goepc_act_mtu_chg(tGOEPC_CCB *p_ccb, tGOEPC_MTU_CHG *mtu_chg)
{
tGOEPC_MSG msg;
msg.mtu_changed.peer_mtu = mtu_chg->peer_mtu;
msg.mtu_changed.our_mtu = mtu_chg->our_mtu;
p_ccb->peer_mtu = mtu_chg->peer_mtu;
p_ccb->our_mtu = mtu_chg->our_mtu;
p_ccb->callback(p_ccb->allocated, GOEPC_MTU_CHANGED_EVT, &msg);
}
void goepc_obex_callback(UINT16 handle, UINT8 event, tOBEX_MSG *msg)
{
tGOEPC_DATA data;
UINT8 goepc_sm_event = GOEPC_SM_EVENT_DISCONNECT;
BOOLEAN exec_sm = FALSE;
tGOEPC_CCB *p_ccb = find_ccb_by_obex_handle(handle);
if (p_ccb == NULL) {
GOEPC_TRACE_ERROR("goepc_obex_callback can not find a ccb\n");
/* can not find a ccb in goepc, free resource and remove this connection */
if (event == OBEX_DATA_EVT && msg->data.pkt) {
osi_free(msg->data.pkt);
}
OBEX_RemoveConn(handle);
return;
}
switch (event)
{
case OBEX_CONNECT_EVT:
data.connected.peer_mtu = msg->connect.peer_mtu;
data.connected.our_mtu = msg->connect.our_mtu;
goepc_sm_event = GOEPC_SM_EVENT_CONNECT;
exec_sm = TRUE;
break;
case OBEX_MTU_CHANGE_EVT:
data.mtu_chg.peer_mtu = msg->mtu_change.peer_mtu;
data.mtu_chg.our_mtu = msg->mtu_change.our_mtu;
goepc_act_mtu_chg(p_ccb, &data.mtu_chg);
break;
case OBEX_DISCONNECT_EVT:
/* when we received this event, obex connection already disconnect */
p_ccb->obex_handle = 0;
goepc_sm_event = GOEPC_SM_EVENT_DISCONNECT;;
exec_sm = TRUE;
break;
case OBEX_CONGEST_EVT:
goepc_act_congest(p_ccb);
break;
case OBEX_UNCONGEST_EVT:
goepc_act_uncongest(p_ccb);
break;
case OBEX_DATA_EVT:
data.pkt = msg->data.pkt;
if (OBEX_CheckContinueResponse(data.pkt)) {
/* in OBEX 1.0, final bit of response code will always set, we need to check this */
goepc_sm_event = GOEPC_SM_EVENT_RSP;
}
else if (OBEX_CheckFinalBit(data.pkt)) {
goepc_sm_event = GOEPC_SM_EVENT_RSP_FB;
}
else {
goepc_sm_event = GOEPC_SM_EVENT_RSP;
}
exec_sm = TRUE;
break;
default:
/* other event, ignore */
break;
}
if (exec_sm) {
goepc_sm_execute(p_ccb, goepc_sm_event, &data);
}
}
static void goepc_sm_act_connect(tGOEPC_CCB *p_ccb, tGOEPC_CONNECTED *connected)
{
tGOEPC_MSG msg;
msg.opened.peer_mtu = connected->peer_mtu;
msg.opened.our_mtu = connected->our_mtu;
p_ccb->peer_mtu = connected->peer_mtu;
p_ccb->our_mtu = connected->our_mtu;
/* main state machine transfer to OPENED_IDLE */
p_ccb->state = GOEPC_STATE_OPENED_IDLE;
p_ccb->callback(p_ccb->allocated, GOEPC_OPENED_EVT, &msg);
}
static void goepc_sm_act_disconnect(tGOEPC_CCB *p_ccb)
{
tGOEPC_MSG msg;
if (p_ccb->obex_handle) {
OBEX_RemoveConn(p_ccb->obex_handle);
}
msg.closed.reason = GOEP_TL_ERROR;
p_ccb->callback(p_ccb->allocated, GOEPC_CLOSED_EVT, &msg);
/* free ccb, main state machine end */
goepc_free_ccb(p_ccb);
}
static void goepc_sm_act_send_req(tGOEPC_CCB *p_ccb, BT_HDR *pkt)
{
UINT16 ret = OBEX_SendPacket(p_ccb->obex_handle, pkt);
if (ret == OBEX_SUCCESS) {
/* main state machine transfer to OPENED_REQ */
p_ccb->state = GOEPC_STATE_OPENED_REQ;
}
else {
/* send failed, something error in transport layer, disconnect */
goepc_sm_act_disconnect(p_ccb);
}
}
static void goepc_sm_act_send_req_fb(tGOEPC_CCB *p_ccb, BT_HDR *pkt)
{
UINT16 ret = OBEX_SendPacket(p_ccb->obex_handle, pkt);
if (ret == OBEX_SUCCESS) {
/* main state machine transfer to OPENED_RSP */
p_ccb->state = GOEPC_STATE_OPENED_RSP;
}
else {
/* send failed, something error in transport layer, disconnect */
goepc_sm_act_disconnect(p_ccb);
}
}
static void goepc_sm_act_rsp(tGOEPC_CCB *p_ccb, BT_HDR *pkt)
{
/* handle srm state transfer */
BOOLEAN srm_en = FALSE;
BOOLEAN srm_wait = FALSE;
goepc_extra_srm_rsp(p_ccb->last_pkt_opcode, pkt, &srm_en, &srm_wait);
goepc_srm_sm_execute(p_ccb, FALSE, srm_en, srm_wait);
/* main state machine not change */
tGOEPC_MSG msg;
msg.response.opcode = p_ccb->last_pkt_opcode;
msg.response.final = FALSE;
msg.response.srm_en = (p_ccb->srm_state == GOEPC_SRM_STATE_ENABLE_WAIT || p_ccb->srm_state == GOEPC_SRM_STATE_ENABLE);
msg.response.srm_wait = (p_ccb->srm_state == GOEPC_SRM_STATE_ENABLE_WAIT);
msg.response.pkt = pkt;
p_ccb->callback(p_ccb->allocated, GOEPC_RESPONSE_EVT, &msg);
}
static void goepc_sm_act_rsp_fb(tGOEPC_CCB *p_ccb, BT_HDR *pkt)
{
tGOEPC_MSG msg;
msg.response.opcode = p_ccb->last_pkt_opcode;
msg.response.final = TRUE;
msg.response.srm_en = FALSE;
msg.response.srm_wait = FALSE;
msg.response.pkt = pkt;
/* operation complete, reset srm state */
p_ccb->srm_state = GOEPC_SRM_STATE_IDLE;
/* main state machine transfer to OPENED_IDLE */
p_ccb->state = GOEPC_STATE_OPENED_IDLE;
p_ccb->callback(p_ccb->allocated, GOEPC_RESPONSE_EVT, &msg);
}
static void goepc_sm_state_opening(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data)
{
switch (event)
{
case GOEPC_SM_EVENT_CONNECT:
goepc_sm_act_connect(p_ccb, &p_data->connected);
break;
case GOEPC_SM_EVENT_DISCONNECT:
goepc_sm_act_disconnect(p_ccb);
break;
case GOEPC_SM_EVENT_RSP:
case GOEPC_SM_EVENT_RSP_FB:
GOEPC_TRACE_ERROR("goepc_sm_state_opening received unexpected response from peer\n");
if (p_data->pkt != NULL) {
osi_free(p_data->pkt);
}
goepc_sm_act_disconnect(p_ccb);
break;
default:
GOEPC_TRACE_ERROR("goepc_sm_state_opening unexpected event: 0x%x\n", event);
break;
}
}
static void goepc_sm_state_opened_idle(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data)
{
switch (event)
{
case GOEPC_SM_EVENT_DISCONNECT:
goepc_sm_act_disconnect(p_ccb);
break;
case GOEPC_SM_EVENT_REQ:
goepc_sm_act_send_req(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_REQ_FB:
goepc_sm_act_send_req_fb(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_RSP:
case GOEPC_SM_EVENT_RSP_FB:
GOEPC_TRACE_ERROR("goepc_sm_state_opened_idle received unexpected response from peer\n");
/* peer sent a packet to us when we didn't request */
if (p_data->pkt != NULL) {
osi_free(p_data->pkt);
}
goepc_sm_act_disconnect(p_ccb);
break;
default:
GOEPC_TRACE_ERROR("goepc_sm_state_opened_idle unexpected event: 0x%x\n", event);
break;
}
}
static void goepc_sm_state_opened_req(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data)
{
switch (event)
{
case GOEPC_SM_EVENT_DISCONNECT:
goepc_sm_act_disconnect(p_ccb);
break;
case GOEPC_SM_EVENT_REQ:
goepc_sm_act_send_req(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_REQ_FB:
goepc_sm_act_send_req_fb(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_RSP:
goepc_sm_act_rsp(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_RSP_FB:
goepc_sm_act_rsp_fb(p_ccb, p_data->pkt);
break;
default:
GOEPC_TRACE_ERROR("goepc_sm_state_opened_req unexpected event: 0x%x\n", event);
break;
}
}
static void goepc_sm_state_opened_rsp(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data)
{
switch (event)
{
case GOEPC_SM_EVENT_DISCONNECT:
goepc_sm_act_disconnect(p_ccb);
break;
case GOEPC_SM_EVENT_REQ_FB:
goepc_sm_act_send_req_fb(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_RSP:
goepc_sm_act_rsp(p_ccb, p_data->pkt);
break;
case GOEPC_SM_EVENT_RSP_FB:
goepc_sm_act_rsp_fb(p_ccb, p_data->pkt);
break;
default:
GOEPC_TRACE_ERROR("goepc_sm_state_opened_rsp unexpected event: 0x%x\n", event);
break;
}
}
BOOLEAN goepc_check_obex_req_allow(UINT8 state, BOOLEAN final)
{
BOOLEAN ret = FALSE;
if (final) {
switch (state)
{
case GOEPC_STATE_OPENED_IDLE:
case GOEPC_STATE_OPENED_REQ:
case GOEPC_STATE_OPENED_RSP:
ret = TRUE;
break;
default:
break;
}
}
else {
switch (state)
{
case GOEPC_STATE_OPENED_IDLE:
case GOEPC_STATE_OPENED_REQ:
ret = TRUE;
break;
default:
break;
}
}
return ret;
}
void goepc_sm_execute(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data)
{
switch (p_ccb->state)
{
case GOEPC_STATE_INIT:
/* do nothing */
break;
case GOEPC_STATE_OPENING:
goepc_sm_state_opening(p_ccb, event, p_data);
break;
case GOEPC_STATE_OPENED_IDLE:
goepc_sm_state_opened_idle(p_ccb, event, p_data);
break;
case GOEPC_STATE_OPENED_REQ:
goepc_sm_state_opened_req(p_ccb, event, p_data);
break;
case GOEPC_STATE_OPENED_RSP:
goepc_sm_state_opened_rsp(p_ccb, event, p_data);
break;
default:
GOEPC_TRACE_ERROR("goepc_sm_execute unexpected state: 0x%x\n", p_ccb->state);
break;
}
}
static void goepc_srm_sm_act_req(tGOEPC_CCB *p_ccb, BOOLEAN srm_en, BOOLEAN srm_wait)
{
switch (p_ccb->srm_state)
{
case GOEPC_SRM_STATE_IDLE:
if (srm_en) {
p_ccb->srm_state = GOEPC_SRM_STATE_REQ;
p_ccb->srm_wait = srm_wait;
}
else {
p_ccb->srm_state = GOEPC_SRM_STATE_DISABLE;
}
break;
case GOEPC_SRM_STATE_ENABLE_WAIT:
if (!srm_wait) {
p_ccb->srm_wait = FALSE;
}
if (!p_ccb->srm_wait && !p_ccb->srm_peer_wait) {
/* no more wait, transfer to ENABLE */
p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE;
}
break;
default:
break;
}
}
static void goepc_srm_sm_act_rsp(tGOEPC_CCB *p_ccb, BOOLEAN srm_en, BOOLEAN srm_wait)
{
switch (p_ccb->srm_state)
{
case GOEPC_SRM_STATE_IDLE:
/* peer can not request to enable srm, ignore */
break;
case GOEPC_SRM_STATE_REQ:
if (srm_en) {
p_ccb->srm_peer_wait = srm_wait;
if (p_ccb->srm_wait || p_ccb->srm_peer_wait) {
p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE_WAIT;
}
else {
p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE;
}
}
else {
p_ccb->srm_state = GOEPC_SRM_STATE_DISABLE;
}
break;
case GOEPC_SRM_STATE_ENABLE_WAIT:
if (!srm_wait) {
p_ccb->srm_peer_wait = FALSE;
}
if (!p_ccb->srm_wait && !p_ccb->srm_peer_wait) {
/* no more wait, transfer to ENABLE */
p_ccb->srm_state = GOEPC_SRM_STATE_ENABLE;
}
break;
default:
break;
}
}
void goepc_srm_sm_execute(tGOEPC_CCB *p_ccb, BOOLEAN is_req, BOOLEAN srm_en, BOOLEAN srm_wait)
{
if (is_req) {
goepc_srm_sm_act_req(p_ccb, srm_en, srm_wait);
}
else {
goepc_srm_sm_act_rsp(p_ccb, srm_en, srm_wait);
}
}
#endif /* #if (GOEPC_INCLUDED == TRUE) */

View File

@ -0,0 +1,103 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "common/bt_target.h"
#include "stack/obex_api.h"
#include "stack/goep_common.h"
#include "stack/goepc_api.h"
#if (GOEPC_INCLUDED == TRUE)
/* GOEPC state machine events */
enum {
GOEPC_SM_EVENT_CONNECT = 0,
GOEPC_SM_EVENT_DISCONNECT,
GOEPC_SM_EVENT_REQ,
GOEPC_SM_EVENT_REQ_FB,
GOEPC_SM_EVENT_RSP,
GOEPC_SM_EVENT_RSP_FB,
};
/* GOEPC state machine states */
enum {
GOEPC_STATE_INIT = 0,
GOEPC_STATE_OPENING,
GOEPC_STATE_OPENED_IDLE,
GOEPC_STATE_OPENED_REQ,
GOEPC_STATE_OPENED_RSP,
};
/* GOEPC srm state machine states */
enum {
GOEPC_SRM_STATE_IDLE = 0,
GOEPC_SRM_STATE_REQ,
GOEPC_SRM_STATE_ENABLE_WAIT,
GOEPC_SRM_STATE_ENABLE,
GOEPC_SRM_STATE_DISABLE,
};
/* GOEPC Connection Control block */
typedef struct {
tGOEPC_EVT_CBACK *callback; /* GOEP event callback function */
UINT16 obex_handle; /* OBEX connection handle */
UINT16 peer_mtu; /* lower layer connection peer MTU */
UINT16 our_mtu; /* lower layer connection our MTU */
BOOLEAN congest; /* lower layer connection congestion status */
BT_HDR *pkt; /* packet prepared in this GOEP client */
BOOLEAN pkt_srm_en; /* whether prepared packet had set SRM to enable */
BOOLEAN pkt_srm_wait; /* whether prepared packet had set SRMP to wait */
UINT8 curr_pkt_opcode; /* prepared packet opcode */
UINT8 last_pkt_opcode; /* last sent packet opcode */
BOOLEAN srm_wait; /* whether we had set SRMP to wait */
BOOLEAN srm_peer_wait; /* whether peer had set SRMP to wait */
UINT8 srm_state; /* SRM state machine */
UINT8 state; /* main state machine */
UINT8 allocated; /* 0, not allocated. index+1, otherwise. equal to api handle */
} tGOEPC_CCB;
/* GOEPC Control block */
typedef struct {
tGOEPC_CCB ccb[GOEPC_MAX_CONNECTION]; /* connection control blocks */
UINT8 trace_level; /* trace level */
} tGOEPC_CB;
#if GOEP_DYNAMIC_MEMORY == FALSE
extern tGOEPC_CB goepc_cb;
#else
extern tGOEPC_CB *goepc_cb_ptr;
#define goepc_cb (*goepc_cb_ptr)
#endif
typedef struct {
UINT16 peer_mtu;
UINT16 our_mtu;
} tGOEPC_CONNECTED;
typedef struct {
UINT16 peer_mtu;
UINT16 our_mtu;
} tGOEPC_MTU_CHG;
typedef union {
tGOEPC_CONNECTED connected;
tGOEPC_MTU_CHG mtu_chg;
BT_HDR *pkt;
} tGOEPC_DATA;
tGOEPC_CCB *goepc_allocate_ccb(void);
void goepc_free_ccb(tGOEPC_CCB *p_ccb);
void goepc_obex_callback(UINT16 handle, UINT8 event, tOBEX_MSG *msg);
BOOLEAN goepc_check_obex_req_allow(UINT8 state, BOOLEAN final);
BOOLEAN goepc_check_obex_req_param(tOBEX_PARSE_INFO *info);
void goepc_sm_execute(tGOEPC_CCB *p_ccb, UINT8 event, tGOEPC_DATA *p_data);
void goepc_srm_sm_execute(tGOEPC_CCB *p_ccb, BOOLEAN is_req, BOOLEAN srm_en, BOOLEAN srm_wait);
#endif /* #if (GOEPC_INCLUDED == TRUE) */

View File

@ -99,6 +99,9 @@
#define AVRC_SUPF_CT_CAT3 0x0004 /* Category 3 */ #define AVRC_SUPF_CT_CAT3 0x0004 /* Category 3 */
#define AVRC_SUPF_CT_CAT4 0x0008 /* Category 4 */ #define AVRC_SUPF_CT_CAT4 0x0008 /* Category 4 */
#define AVRC_SUPF_CT_BROWSE 0x0040 /* Browsing */ #define AVRC_SUPF_CT_BROWSE 0x0040 /* Browsing */
#define AVRC_SUPF_CT_COVER_ART_GIP 0x0080 /* Cover Art GetImageProperties */
#define AVRC_SUPF_CT_COVER_ART_GI 0x0100 /* Cover Art GetImage */
#define AVRC_SUPF_CT_COVER_ART_GLT 0x0200 /* Cover Art GetLinkedThumbnail */
#define AVRC_SUPF_TG_CAT1 0x0001 /* Category 1 */ #define AVRC_SUPF_TG_CAT1 0x0001 /* Category 1 */
#define AVRC_SUPF_TG_CAT2 0x0002 /* Category 2 */ #define AVRC_SUPF_TG_CAT2 0x0002 /* Category 2 */
@ -107,7 +110,8 @@
#define AVRC_SUPF_TG_APP_SETTINGS 0x0010 /* Player Application Settings */ #define AVRC_SUPF_TG_APP_SETTINGS 0x0010 /* Player Application Settings */
#define AVRC_SUPF_TG_GROUP_NAVI 0x0020 /* Group Navigation */ #define AVRC_SUPF_TG_GROUP_NAVI 0x0020 /* Group Navigation */
#define AVRC_SUPF_TG_BROWSE 0x0040 /* Browsing */ #define AVRC_SUPF_TG_BROWSE 0x0040 /* Browsing */
#define AVRC_SUPF_TG_MULTI_PLAYER 0x0080 /* Muliple Media Player */ #define AVRC_SUPF_TG_MULTI_PLAYER 0x0080 /* Multiple Media Player */
#define AVRC_SUPF_TG_COVER_ART 0x0100 /* Cover Art */
#define AVRC_META_SUCCESS AVRC_SUCCESS #define AVRC_META_SUCCESS AVRC_SUCCESS
#define AVRC_META_FAIL AVRC_FAIL #define AVRC_META_FAIL AVRC_FAIL
@ -561,7 +565,7 @@ extern bt_status_t AVRC_Init(void);
** **
** Function AVRC_Deinit ** Function AVRC_Deinit
** **
** Description This function is called at stack shotdown to free the ** Description This function is called at stack shutdown to free the
** control block (if using dynamic memory), and deinitializes the ** control block (if using dynamic memory), and deinitializes the
** control block and tracing level. ** control block and tracing level.
** **

View File

@ -35,6 +35,7 @@
#define AVRC_REV_1_3 0x0103 #define AVRC_REV_1_3 0x0103
#define AVRC_REV_1_4 0x0104 #define AVRC_REV_1_4 0x0104
#define AVRC_REV_1_5 0x0105 #define AVRC_REV_1_5 0x0105
#define AVRC_REV_1_6 0x0106
#define AVRC_PACKET_LEN 512 /* Per the spec, you must support 512 byte RC packets */ #define AVRC_PACKET_LEN 512 /* Per the spec, you must support 512 byte RC packets */
@ -185,7 +186,7 @@
#define AVRC_PKT_END 3 #define AVRC_PKT_END 3
#define AVRC_PKT_TYPE_MASK 3 #define AVRC_PKT_TYPE_MASK 3
/* Define the PDUs carried in the vendor dependant data /* Define the PDUs carried in the vendor dependent data
*/ */
#define AVRC_PDU_GET_CAPABILITIES 0x10 #define AVRC_PDU_GET_CAPABILITIES 0x10
#define AVRC_PDU_LIST_PLAYER_APP_ATTR 0x11 #define AVRC_PDU_LIST_PLAYER_APP_ATTR 0x11
@ -297,7 +298,7 @@ typedef UINT8 tAVRC_BATTERY_STATUS;
#define AVRC_MEDIA_ATTR_ID_TRACK_NUM 0x00000004 #define AVRC_MEDIA_ATTR_ID_TRACK_NUM 0x00000004
#define AVRC_MEDIA_ATTR_ID_NUM_TRACKS 0x00000005 #define AVRC_MEDIA_ATTR_ID_NUM_TRACKS 0x00000005
#define AVRC_MEDIA_ATTR_ID_GENRE 0x00000006 #define AVRC_MEDIA_ATTR_ID_GENRE 0x00000006
#define AVRC_MEDIA_ATTR_ID_PLAYING_TIME 0x00000007 /* in miliseconds */ #define AVRC_MEDIA_ATTR_ID_PLAYING_TIME 0x00000007 /* in milliseconds */
#define AVRC_MAX_NUM_MEDIA_ATTR_ID 7 #define AVRC_MAX_NUM_MEDIA_ATTR_ID 7
/* Define the possible values of play state /* Define the possible values of play state

View File

@ -45,6 +45,8 @@
#define HCRP_DYNAMIC_MEMORY TRUE #define HCRP_DYNAMIC_MEMORY TRUE
#define HFP_DYNAMIC_MEMORY TRUE #define HFP_DYNAMIC_MEMORY TRUE
#define HID_DYNAMIC_MEMORY TRUE #define HID_DYNAMIC_MEMORY TRUE
#define OBEX_DYNAMIC_MEMORY TRUE
#define GOEP_DYNAMIC_MEMORY TRUE
#define HSP2_DYNAMIC_MEMORY TRUE #define HSP2_DYNAMIC_MEMORY TRUE
#define ICP_DYNAMIC_MEMORY TRUE #define ICP_DYNAMIC_MEMORY TRUE
#define OPP_DYNAMIC_MEMORY TRUE #define OPP_DYNAMIC_MEMORY TRUE
@ -79,6 +81,8 @@
#define HCRP_DYNAMIC_MEMORY FALSE #define HCRP_DYNAMIC_MEMORY FALSE
#define HFP_DYNAMIC_MEMORY FALSE #define HFP_DYNAMIC_MEMORY FALSE
#define HID_DYNAMIC_MEMORY FALSE #define HID_DYNAMIC_MEMORY FALSE
#define OBEX_DYNAMIC_MEMORY FALSE
#define GOEP_DYNAMIC_MEMORY FALSE
#define HSP2_DYNAMIC_MEMORY FALSE #define HSP2_DYNAMIC_MEMORY FALSE
#define ICP_DYNAMIC_MEMORY FALSE #define ICP_DYNAMIC_MEMORY FALSE
#define OPP_DYNAMIC_MEMORY FALSE #define OPP_DYNAMIC_MEMORY FALSE
@ -191,6 +195,14 @@
#define HID_DYNAMIC_MEMORY FALSE #define HID_DYNAMIC_MEMORY FALSE
#endif #endif
#ifndef OBEX_DYNAMIC_MEMORY
#define OBEX_DYNAMIC_MEMORY FALSE
#endif
#ifndef GOEP_DYNAMIC_MEMORY
#define GOEP_DYNAMIC_MEMORY FALSE
#endif
#ifndef HSP2_DYNAMIC_MEMORY #ifndef HSP2_DYNAMIC_MEMORY
#define HSP2_DYNAMIC_MEMORY FALSE #define HSP2_DYNAMIC_MEMORY FALSE
#endif #endif

View File

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "common/bt_target.h"
#define GOEP_SUCCESS 0 /* Operation successful */
#define GOEP_FAILURE 1 /* Operation failed */
#define GOEP_NO_RESOURCES 3 /* Not enough resources */
#define GOEP_BAD_HANDLE 4 /* Bad handle */
#define GOEP_INVALID_PARAM 5 /* Invalid parameter */
#define GOEP_INVALID_STATE 6 /* Operation not allow in current state */
#define GOEP_CONGEST 7 /* Congest */
#define GOEP_TL_ERROR 8 /* Lower transport layer error */

View File

@ -0,0 +1,82 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "common/bt_target.h"
#include "stack/goep_common.h"
#include "stack/obex_api.h"
#if (GOEPC_INCLUDED == TRUE)
enum {
GOEPC_OPENED_EVT, /* connection open */
GOEPC_CLOSED_EVT, /* disconnect unexpected */
GOEPC_MTU_CHANGED_EVT, /* lower layer MTU change */
GOEPC_CONGEST_EVT, /* lower layer connection congest */
GOEPC_UNCONGEST_EVT, /* lower layer connection uncongest */
GOEPC_RESPONSE_EVT /* response from server */
};
typedef struct {
UINT16 peer_mtu; /* peer mtu of lower level connection */
UINT16 our_mtu; /* our mtu of lower level connection */
} tGOEPC_MSG_OPENED;
typedef struct {
UINT8 reason; /* connection close reason */
} tGOEPC_MSG_CLOSED;
typedef struct {
UINT16 peer_mtu; /* peer mtu of lower level connection */
UINT16 our_mtu; /* our mtu of lower level connection */
} tGOEPC_MSG_MTU_CHANGED;
typedef struct {
UINT8 opcode; /* which opcode that this packet response to */
BOOLEAN final; /* whether this is a final packet */
BOOLEAN srm_en; /* whether srm is enable */
BOOLEAN srm_wait; /* whether srm wait is set, set by peer or by us */
BT_HDR *pkt; /* pointer to response packet */
} tGOEPC_MSG_RESPONSE;
typedef union {
tGOEPC_MSG_OPENED opened;
tGOEPC_MSG_CLOSED closed;
tGOEPC_MSG_MTU_CHANGED mtu_changed;
tGOEPC_MSG_RESPONSE response;
} tGOEPC_MSG;
typedef void (tGOEPC_EVT_CBACK)(UINT16 handle, UINT8 event, tGOEPC_MSG *msg);
/*******************************************************************************
* The following APIs are called by bluetooth stack automatically
*******************************************************************************/
extern UINT16 GOEPC_Init(void);
extern void GOEPC_Deinit(void);
/*******************************************************************************
* The following APIs must be executed in btu task
*******************************************************************************/
extern UINT16 GOEPC_Open(tOBEX_SVR_INFO *p_svr, tGOEPC_EVT_CBACK callback, UINT16 *out_handle);
extern UINT16 GOEPC_Close(UINT16 handle);
extern UINT16 GOEPC_SendRequest(UINT16 handle);
extern UINT16 GOEPC_PrepareRequest(UINT16 handle, tOBEX_PARSE_INFO *info, UINT16 buff_size);
extern UINT16 GOEPC_DropRequest(UINT16 handle);
extern UINT16 GOEPC_RequestSetSRM(UINT16 handle, BOOLEAN srm_en, BOOLEAN srm_wait);
extern UINT16 GOEPC_RequestAddHeader(UINT16 handle, UINT8 header_id, const UINT8 *data, UINT16 data_len);
#endif /* #if (GOEPC_INCLUDED == TRUE) */

View File

@ -0,0 +1,264 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "common/bt_target.h"
#if (OBEX_INCLUDED == TRUE)
/* API function return value result codes. */
#define OBEX_SUCCESS 0 /* Operation successful */
#define OBEX_FAILURE 1 /* Operation failed */
#define OBEX_NO_RESOURCES 3 /* Not enough resources */
#define OBEX_BAD_HANDLE 4 /* Bad handle */
#define OBEX_INVALID_PARAM 5 /* Invalid parameter */
#define OBEX_NOT_OPEN 6 /* Connection not open */
#define OBEX_PACKET_TOO_LARGE 7 /* Packet size large than MTU */
#define OBEX_ERROR_TL 8 /* Operation failed in transport layer */
#define OBEX_TRY_AGAIN 9 /* Operation failed, connection congestion, try again */
/*
* OBEX profile definitions
*/
#define OBEX_OPCODE_CONNECT 0x80
#define OBEX_OPCODE_DISCONNECT 0x81
#define OBEX_OPCODE_PUT 0x02
#define OBEX_OPCODE_PUT_FINAL 0x82
#define OBEX_OPCODE_GET 0x03
#define OBEX_OPCODE_GET_FINAL 0x83
#define OBEX_OPCODE_SETPATH 0x85
#define OBEX_OPCODE_ACTION 0x06
#define OBEX_OPCODE_SESSION 0x87
#define OBEX_OPCODE_ABORT 0xFF
#define OBEX_RESPONSE_CODE_CONTINUE 0x10
#define OBEX_RESPONSE_CODE_OK 0x20
#define OBEX_RESPONSE_CODE_CREATED 0x21
#define OBEX_RESPONSE_CODE_ACCEPTED 0x22
#define OBEX_RESPONSE_CODE_NON_AUTHORITATIVE_INFO 0x23
#define OBEX_RESPONSE_CODE_NO_CONTENT 0x24
#define OBEX_RESPONSE_CODE_RESET_CONTENT 0x25
#define OBEX_RESPONSE_CODE_PARTIAL_CONTENT 0x26
#define OBEX_RESPONSE_CODE_MULTIPLE_CHOICES 0x30
#define OBEX_RESPONSE_CODE_MOVED_PERMANENTLY 0x31
#define OBEX_RESPONSE_CODE_MOVED_TEMPORARILY 0x31
#define OBEX_RESPONSE_CODE_SEE_OHTER 0x33
#define OBEX_RESPONSE_CODE_NOT_MODIFIED 0x34
#define OBEX_RESPONSE_CODE_USE_PROXY 0x35
#define OBEX_RESPONSE_CODE_BAD_REQUEST 0x40
#define OBEX_RESPONSE_CODE_UNAUTHORIZED 0x41
#define OBEX_RESPONSE_CODE_PAYMENT_REQUIRED 0x42
#define OBEX_RESPONSE_CODE_FORBIDDEN 0x43
#define OBEX_RESPONSE_CODE_NOT_FOUND 0x44
#define OBEX_RESPONSE_CODE_METHOD_NOT_ALLOWED 0x45
#define OBEX_RESPONSE_CODE_NOT_ACCEPTABLE 0x46
#define OBEX_RESPONSE_CODE_PROXY_AUTHENTICATION_REQUIRED 0x47
#define OBEX_RESPONSE_CODE_REQUEST_TIME_OUT 0x48
#define OBEX_RESPONSE_CODE_CONFLICT 0x49
#define OBEX_RESPONSE_CODE_GONE 0x4A
#define OBEX_RESPONSE_CODE_LENGTH_REQUIRED 0x4B
#define OBEX_RESPONSE_CODE_PRECONDITION_FAILED 0x4C
#define OBEX_RESPONSE_CODE_REQUESTED_ENTITY_TOO_LARGE 0x4D
#define OBEX_RESPONSE_CODE_REQUEST_URL_TOO_LARGE 0x4E
#define OBEX_RESPONSE_CODE_UNSUPPORTED_MEDIA_TYPE 0x4F
#define OBEX_RESPONSE_CODE_INTERNAL_SERVER_ERROR 0x50
#define OBEX_RESPONSE_CODE_NOT_IMPLEMENTED 0x51
#define OBEX_RESPONSE_CODE_BAD_GATEWAY 0x52
#define OBEX_RESPONSE_CODE_SERVICE_UNAVAILABLE 0x53
#define OBEX_RESPONSE_CODE_GATEWAY_TIMEOUT 0x54
#define OBEX_RESPONSE_CODE_HTTP_VERSION_NOT_SUPPORTED 0x55
#define OBEX_RESPONSE_CODE_DATABASE_FULL 0x60
#define OBEX_RESPONSE_CODE_DATABASE_LOCKED 0x61
#define OBEX_FINAL_BIT_MASK 0x80
#define OBEX_CONNECT_FLAGS 0x01 /* support multiple link */
#define OBEX_SETPATH_FLAGS 0x03 /* default flags */
#define OBEX_PACKET_LENGTH_MAX (0xFFFF-1)
#define OBEX_PACKET_LENGTH_MIN 255
#define OBEX_VERSION_NUMBER 0x15
/* Header identifiers */
#define OBEX_HEADER_ID_U2B_MASK 0xC0 /* upper 2 bits of header ID are user to indicate the header encoding */
#define OBEX_HEADER_ID_U2B_TYPE1 0x00 /* null terminated Unicode text, length prefixed with 2 byte unsigned integer */
#define OBEX_HEADER_ID_U2B_TYPE2 0x40 /* byte sequence, length prefixed with 2 byte unsigned integer */
#define OBEX_HEADER_ID_U2B_TYPE3 0x80 /* 1 byte quantity */
#define OBEX_HEADER_ID_U2B_TYPE4 0xC0 /* 4 byte quantity - transmitted in network byte order (high byte first) */
#define OBEX_HEADER_ID_COUNT 0xC0
#define OBEX_HEADER_ID_NAME 0x01
#define OBEX_HEADER_ID_TYPE 0x42
#define OBEX_HEADER_ID_LENGTH 0xC3
#define OBEX_HEADER_ID_TIME_ISO8601 0x44
#define OBEX_HEADER_ID_TIME_4BYTE 0xC4
#define OBEX_HEADER_ID_DESCRIPTION 0x05
#define OBEX_HEADER_ID_TARGET 0x46
#define OBEX_HEADER_ID_HTTP 0x47
#define OBEX_HEADER_ID_BODY 0x48
#define OBEX_HEADER_ID_END_OF_BODY 0x49
#define OBEX_HEADER_ID_WHO 0x4A
#define OBEX_HEADER_ID_CONNECTION_ID 0xCB
#define OBEX_HEADER_ID_APP_PARAM 0x4C
#define OBEX_HEADER_ID_AUTH_CHALLENGE 0x4D
#define OBEX_HEADER_ID_AUTH_RESPONSE 0x4E
#define OBEX_HEADER_ID_CREATOR_ID 0xCF
#define OBEX_HEADER_ID_WAN_UUID 0x50
#define OBEX_HEADER_ID_OBJECT_CLASS 0x51
#define OBEX_HEADER_ID_SESSION_PARAM 0x52
#define OBEX_HEADER_ID_SESSION_SEQ_NUM 0x93
#define OBEX_HEADER_ID_ACTION_ID 0x94
#define OBEX_HEADER_ID_DESTNAME 0x15
#define OBEX_HEADER_ID_PERMISSIONS 0xD6
#define OBEX_HEADER_ID_SRM 0x97
#define OBEX_HEADER_ID_SRM_PARAM 0x98
/* Reserved for future use: 0x19 to 0x2F */
/* User defined: 0x30 to 0x3F */
#define OBEX_ACTION_ID_COPY 0x00
#define OBEX_ACTION_ID_MOVE_RENAME 0x01
#define OBEX_ACTION_ID_SET_PERMISSIONS 0x02
#define OBEX_SRM_DISABLE 0x00
#define OBEX_SRM_ENABLE 0x01
#define OBEX_SRM_SUPPORT 0x02
#define OBEX_SRMP_ADD_PKT 0x00
#define OBEX_SRMP_WAIT 0x01
#define OBEX_SRMP_ADD_PKT_WAIT 0x02
#define OBEX_MIN_PACKET_SIZE 3
enum {
/* client event */
OBEX_CONNECT_EVT, /* connection opened */
OBEX_DISCONNECT_EVT, /* connection disconnected */
/* server event */
OBEX_CONN_INCOME_EVT, /* an incoming connection */
/* client or server event */
OBEX_MTU_CHANGE_EVT, /* connection mtu changed */
OBEX_CONGEST_EVT, /* connection congested */
OBEX_UNCONGEST_EVT, /* connection is not congested */
OBEX_DATA_EVT /* data received */
};
enum {
OBEX_OVER_L2CAP = 0,
OBEX_OVER_RFCOMM,
OBEX_NUM_TL
};
typedef struct
{
UINT16 psm; /* l2cap psm */
UINT16 sec_mask; /* security mask */
UINT16 pref_mtu; /* preferred mtu, limited by L2CAP_MTU_SIZE */
BD_ADDR addr; /* peer bluetooth device address */
} tOBEX_OVER_L2CAP_SVR;
typedef struct
{
UINT8 tl; /* transport type, OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */
union
{
tOBEX_OVER_L2CAP_SVR l2cap;
};
} tOBEX_SVR_INFO;
typedef struct {
UINT8 opcode;
UINT8 response_code;
/* Connect */
UINT8 obex_version_number;
UINT16 max_packet_length;
/* Connect or SetPath */
UINT8 flags;
/* Internal use */
UINT16 next_header_pos;
} tOBEX_PARSE_INFO;
typedef union {
struct {
UINT16 peer_mtu;
UINT16 our_mtu;
} connect;
struct {
UINT16 svr_handle;
UINT16 peer_mtu;
UINT16 our_mtu;
} conn_income;
struct {
UINT16 peer_mtu;
UINT16 our_mtu;
} mtu_change;
struct {
BT_HDR *pkt;
} data;
} tOBEX_MSG;
typedef void (tOBEX_MSG_CBACK)(UINT16 handle, UINT8 event, tOBEX_MSG *msg);
/*******************************************************************************
* The following APIs are called by bluetooth stack automatically
*******************************************************************************/
extern UINT16 OBEX_Init(void);
extern void OBEX_Deinit(void);
/*******************************************************************************
* The following APIs must be executed in btu task
*******************************************************************************/
extern UINT16 OBEX_CreateConn(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_handle);
extern UINT16 OBEX_RemoveConn(UINT16 handle);
extern UINT16 OBEX_SendPacket(UINT16 handle, BT_HDR *pkt);
extern UINT16 OBEX_RegisterServer(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_svr_handle);
extern UINT16 OBEX_DeregisterServer(UINT16 svr_handle);
/*******************************************************************************
* The following APIs are util function, can be executed in btu or btc task
*******************************************************************************/
extern UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt);
extern UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt);
extern UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header);
extern UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UINT16 data_len);
extern UINT16 OBEX_AppendHeaderSRM(BT_HDR *pkt, UINT8 value);
extern UINT16 OBEX_AppendHeaderSRMP(BT_HDR *pkt, UINT8 value);
extern UINT16 OBEX_GetPacketFreeSpace(BT_HDR *pkt);
extern UINT16 OBEX_GetPacketLength(BT_HDR *pkt);
extern UINT16 OBEX_ParseRequest(BT_HDR *pkt, tOBEX_PARSE_INFO *info);
extern UINT16 OBEX_ParseResponse(BT_HDR *pkt, UINT8 opcode, tOBEX_PARSE_INFO *info);
extern BOOLEAN OBEX_CheckFinalBit(BT_HDR *pkt);
extern BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt);
extern UINT8 *OBEX_GetNextHeader(BT_HDR *pkt, tOBEX_PARSE_INFO *info);
extern UINT16 OBEX_GetHeaderLength(UINT8 *header);
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -365,6 +365,20 @@ extern BOOLEAN SDP_FindProtocolListElemInRec (tSDP_DISC_REC *p_rec,
UINT16 layer_uuid, UINT16 layer_uuid,
tSDP_PROTOCOL_ELEM *p_elem); tSDP_PROTOCOL_ELEM *p_elem);
/*******************************************************************************
**
** Function SDP_FindProtocolListElem
**
** Description This function looks at the protocol list for a specific protocol
** list element.
**
** Returns TRUE if found, FALSE if not
** If found, the passed protocol list element is filled in.
**
*******************************************************************************/
extern BOOLEAN SDP_FindProtocolListElem (tSDP_DISC_ATTR *p_protocol_list,
UINT16 layer_uuid,
tSDP_PROTOCOL_ELEM *p_elem);
/******************************************************************************* /*******************************************************************************
** **
@ -409,7 +423,7 @@ extern BOOLEAN SDP_FindProfileVersionInRec (tSDP_DISC_REC *p_rec,
** **
** Description This function is called to create a record in the database. ** Description This function is called to create a record in the database.
** This would be through the SDP database maintenance API. The ** This would be through the SDP database maintenance API. The
** record is created empty, teh application should then call ** record is created empty, the application should then call
** "add_attribute" to add the record's attributes. ** "add_attribute" to add the record's attributes.
** **
** Returns Record handle if OK, else 0. ** Returns Record handle if OK, else 0.

View File

@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "common/bt_target.h"
#include "stack/obex_api.h"
#include "obex_tl.h"
#include "obex_tl_l2cap.h"
#if (OBEX_INCLUDED == TRUE)
#define OBEX_BT_HDR_MIN_OFFSET OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET /* should set to max value of all transport layer */
#define OBEX_ROLE_CLIENT 0x01
#define OBEX_ROLE_SERVER 0x02
/* OBEX connection state */
#define OBEX_STATE_IDLE 0 /* No connection */
#define OBEX_STATE_OPENING 1 /* Starting to open a connection */
#define OBEX_STATE_OPENED 2 /* Connection opened */
/* Store 16 bits data in big endian format, not modify the p_buf */
#define STORE16BE(p_buf, data) do { *p_buf = ((data)>>8)&0xff; \
*(p_buf+1) = (data)&0xff;} while(0)
/* OBEX Connection Control block */
typedef struct {
tOBEX_MSG_CBACK *callback; /* Connection msg callback function */
UINT16 tl_hdl; /* Transport layer non-zeros connection handle*/
UINT16 tl_peer_mtu; /* Transport layer peer mtu */
UINT16 tl_our_mtu; /* Transport layer our mtu */
UINT8 tl_cong; /* 1 if transport layer congestion, otherwise 0 */
UINT8 tl; /* OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */
UINT8 allocated; /* 0, not allocated. index+1, otherwise. equal to api handle */
UINT8 state; /* This OBEX connection state */
UINT8 role; /* This OBEX connection role */
} tOBEX_CCB;
/* OBEX Server Control block */
typedef struct {
tOBEX_MSG_CBACK *callback; /* Connection msg callback function */
UINT16 tl_hdl; /* Transport layer non-zeros server handle*/
UINT8 tl; /* OBEX_OVER_L2CAP or OBEX_OVER_RFCOMM */
UINT8 allocated; /* 0, not allocated. index+1, otherwise. */
} tOBEX_SCB;
/* OBEX Control block */
typedef struct {
tOBEX_CCB ccb[OBEX_MAX_CONNECTION]; /* connection control blocks */
tOBEX_SCB scb[OBEX_MAX_SERVER]; /* server control blocks */
tOBEX_TL_OPS *tl_ops[OBEX_NUM_TL]; /* transport operation function pointer */
UINT8 trace_level; /* trace level */
} tOBEX_CB;
#if OBEX_DYNAMIC_MEMORY == FALSE
extern tOBEX_CB obex_cb;
#else
extern tOBEX_CB *obex_cb_ptr;
#define obex_cb (*obex_cb_ptr)
#endif
void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg);
tOBEX_CCB *obex_allocate_ccb(void);
tOBEX_SCB *obex_allocate_scb(void);
void obex_free_ccb(tOBEX_CCB *p_ccb);
void obex_free_scb(tOBEX_SCB *p_scb);
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -0,0 +1,88 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "common/bt_target.h"
#if (OBEX_INCLUDED == TRUE)
/* Return code of obex_tl_send_data */
#define OBEX_TL_FAILED FALSE
#define OBEX_TL_SUCCESS TRUE
#define OBEX_TL_CONGESTED 2
typedef enum {
OBEX_TL_CONN_OPEN_EVT,
OBEX_TL_CONN_INCOME_EVT,
OBEX_TL_DIS_CONN_EVT,
OBEX_TL_CONGEST_EVT,
OBEX_TL_UNCONGEST_EVT,
OBEX_TL_MTU_CHANGE_EVT,
OBEX_TL_DATA_EVT
} tOBEX_TL_EVT;
typedef union {
/* general struct, used to retrieve handle */
struct {
UINT16 hdl;
} any;
/* struct for OBEX_TL_CONN_OPEN_EVT */
struct {
UINT16 hdl;
UINT16 peer_mtu;
UINT16 our_mtu;
} conn_open;
/* struct for OBEX_TL_CONN_INCOME_EVT */
struct {
UINT16 hdl;
UINT16 peer_mtu;
UINT16 our_mtu;
UINT16 svr_hdl;
} conn_income;
/* struct for OBEX_TL_MTU_CHANGE_EVT */
struct {
UINT16 hdl;
UINT16 peer_mtu;
UINT16 our_mtu;
} mtu_chg;
/* struct for OBEX_TL_DATA_EVT */
struct {
UINT16 hdl;
BT_HDR *p_buf;
} data;
} tOBEX_TL_MSG;
typedef struct
{
UINT16 psm; /* l2cap psm */
UINT16 sec_mask; /* security mask */
UINT16 pref_mtu; /* preferred mtu, limited by L2CAP_MTU_SIZE */
BD_ADDR addr; /* peer bluetooth device address */
} tOBEX_TL_L2CAP_SVR;
typedef union
{
tOBEX_TL_L2CAP_SVR l2cap;
} tOBEX_TL_SVR_INFO;
typedef void (tOBEX_TL_CBACK)(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg);
typedef struct {
void (*init)(tOBEX_TL_CBACK *callback);
void (*deinit)(void);
UINT16 (*connect)(tOBEX_TL_SVR_INFO *server);
void (*disconnect)(UINT16 tl_hdl);
UINT16 (*send)(UINT16 tl_hdl, BT_HDR *p_buf);
UINT16 (*bind)(tOBEX_TL_SVR_INFO *server);
void (*unbind)(UINT16 tl_hdl);
} tOBEX_TL_OPS;
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "obex_tl.h"
#if (OBEX_INCLUDED == TRUE)
#define OBEX_TL_L2CAP_BT_HDR_MIN_OFFSET 13 /* L2CAP_MIN_OFFSET */
tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void);
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -0,0 +1,764 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "osi/osi.h"
#include "osi/allocator.h"
#include "common/bt_target.h"
#include "stack/obex_api.h"
#include "obex_int.h"
#include "obex_tl.h"
#include "obex_tl_l2cap.h"
#if (OBEX_INCLUDED == TRUE)
static inline void obex_server_to_tl_server(tOBEX_SVR_INFO *server, tOBEX_TL_SVR_INFO *tl_server)
{
if (server->tl == OBEX_OVER_L2CAP) {
tl_server->l2cap.psm = server->l2cap.psm;
tl_server->l2cap.sec_mask = server->l2cap.sec_mask;
tl_server->l2cap.pref_mtu = server->l2cap.pref_mtu;
bdcpy(tl_server->l2cap.addr, server->l2cap.addr);
}
else {
OBEX_TRACE_ERROR("Unsupported OBEX transport type\n");
assert(0);
}
}
static inline void obex_updata_packet_length(BT_HDR *p_buf, UINT16 len)
{
UINT8 *p_pkt_len = (UINT8 *)(p_buf + 1) + p_buf->offset + 1;
STORE16BE(p_pkt_len, len);
}
/*******************************************************************************
**
** Function OBEX_Init
**
** Description Initialize OBEX Profile, must call before using any other
** OBEX APIs
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_Init(void)
{
#if (OBEX_DYNAMIC_MEMORY)
if (!obex_cb_ptr) {
obex_cb_ptr = (tOBEX_CB *)osi_malloc(sizeof(tOBEX_CB));
if (!obex_cb_ptr) {
return OBEX_NO_RESOURCES;
}
}
#endif /* #if (OBEX_DYNAMIC_MEMORY) */
memset(&obex_cb, 0, sizeof(tOBEX_CB));
obex_cb.tl_ops[OBEX_OVER_L2CAP] = obex_tl_l2cap_ops_get();
if (obex_cb.tl_ops[OBEX_OVER_L2CAP]->init != NULL) {
obex_cb.tl_ops[OBEX_OVER_L2CAP]->init(obex_tl_l2cap_callback);
}
/* Not implement yet */
/*
obex_cb.tl_ops[OBEX_OVER_RFCOMM] = obex_tl_rfcomm_ops_get();
if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init != NULL) {
obex_cb.tl_ops[OBEX_OVER_RFCOMM]->init(obex_tl_rfcomm_callback);
}
*/
obex_cb.trace_level = BT_TRACE_LEVEL_ERROR;
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_Deinit
**
** Description Deinit OBEX profile, once deinit, can not use any other
** APIs until call OBEX_Init again
**
*******************************************************************************/
void OBEX_Deinit(void)
{
if (obex_cb.tl_ops[OBEX_OVER_L2CAP]->deinit != NULL) {
obex_cb.tl_ops[OBEX_OVER_L2CAP]->deinit();
}
/*
if (obex_cb.tl_ops[OBEX_OVER_RFCOMM]->deinit != NULL) {
obex_cb.tl_ops[OBEX_OVER_RFCOMM]->deinit();
}
*/
#if (OBEX_DYNAMIC_MEMORY)
if (obex_cb_ptr) {
osi_free(obex_cb_ptr);
obex_cb_ptr = NULL;
}
#endif /* #if (OBEX_DYNAMIC_MEMORY) */
}
/*******************************************************************************
**
** Function OBEX_CreateConn
**
** Description Start the progress of creating an OBEX connection
**
** Returns OBEX_SUCCESS if successful, otherwise failed, when the
** connection is opened, an OBEX_CONNECT_EVT will come
**
*******************************************************************************/
UINT16 OBEX_CreateConn(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_handle)
{
UINT16 ret = OBEX_SUCCESS;
tOBEX_CCB *p_ccb = NULL;
do {
if (server->tl >= OBEX_NUM_TL) {
ret = OBEX_INVALID_PARAM;
break;
}
p_ccb = obex_allocate_ccb();
if (p_ccb == NULL) {
ret = OBEX_NO_RESOURCES;
break;
}
tOBEX_TL_SVR_INFO tl_server = {0};
obex_server_to_tl_server(server, &tl_server);
p_ccb->tl = server->tl;
p_ccb->tl_hdl = obex_cb.tl_ops[p_ccb->tl]->connect(&tl_server);
if (p_ccb->tl_hdl == 0) {
ret = OBEX_ERROR_TL;
break;
}
p_ccb->callback = callback;
p_ccb->role = OBEX_ROLE_CLIENT;
p_ccb->state = OBEX_STATE_OPENING;
*out_handle = p_ccb->allocated;
} while (0);
if (ret != OBEX_SUCCESS && p_ccb != NULL) {
obex_free_ccb(p_ccb);
}
return ret;
}
/*******************************************************************************
**
** Function OBEX_RemoveConn
**
** Description Remove an OBEX connection
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_RemoveConn(UINT16 handle)
{
tOBEX_CCB *p_ccb = NULL;
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= OBEX_MAX_CONNECTION || !obex_cb.ccb[ccb_idx].allocated) {
return OBEX_BAD_HANDLE;
}
p_ccb = &obex_cb.ccb[ccb_idx];
obex_cb.tl_ops[p_ccb->tl]->disconnect(p_ccb->tl_hdl);
obex_free_ccb(p_ccb);
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_RegisterServer
**
** Description Register an OBEX server and listen the incoming connection
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_RegisterServer(tOBEX_SVR_INFO *server, tOBEX_MSG_CBACK callback, UINT16 *out_svr_handle)
{
UINT8 ret = OBEX_SUCCESS;
tOBEX_SCB *p_scb = NULL;
do {
if (server->tl >= OBEX_NUM_TL) {
ret = OBEX_INVALID_PARAM;
break;
}
p_scb = obex_allocate_scb();
if (p_scb == NULL) {
ret = OBEX_NO_RESOURCES;
break;
}
tOBEX_TL_SVR_INFO tl_server = {0};
obex_server_to_tl_server(server, &tl_server);
p_scb->tl = server->tl;
p_scb->tl_hdl = obex_cb.tl_ops[p_scb->tl]->bind(&tl_server);
if (p_scb->tl_hdl == 0) {
ret = OBEX_ERROR_TL;
break;
}
p_scb->callback = callback;
if (out_svr_handle) {
/* To avoid confuse with connection handle, left shift 8 bit */
*out_svr_handle = p_scb->allocated << 8;
}
} while (0);
if (ret != OBEX_SUCCESS && p_scb != NULL) {
obex_free_scb(p_scb);
}
return ret;
}
/*******************************************************************************
**
** Function OBEX_DeregisterServer
**
** Description Deregister an OBEX server, if there are still a connection
** alive, the behavior depend on the implementation of transport
** layer
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_DeregisterServer(UINT16 svr_handle)
{
tOBEX_SCB *p_scb = NULL;
UINT16 scb_idx = (svr_handle >> 8) - 1;
if (scb_idx >= OBEX_MAX_SERVER || !obex_cb.scb[scb_idx].allocated) {
return OBEX_BAD_HANDLE;
}
p_scb = &obex_cb.scb[scb_idx];
obex_cb.tl_ops[p_scb->tl]->unbind(p_scb->tl_hdl);
obex_free_scb(p_scb);
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_SendPacket
**
** Description Send a packet to peer OBEX server or client, once call
** this function, the ownership of pkt is lost, do not free
** or modify the pkt any more
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_SendPacket(UINT16 handle, BT_HDR *pkt)
{
UINT16 ret = OBEX_SUCCESS;
BOOLEAN free_pkt = true;
tOBEX_CCB *p_ccb = NULL;
do {
if (pkt == NULL) {
ret = OBEX_INVALID_PARAM;
break;
}
UINT16 ccb_idx = handle - 1;
if (ccb_idx >= OBEX_MAX_CONNECTION || !obex_cb.ccb[ccb_idx].allocated) {
ret = OBEX_BAD_HANDLE;
break;
}
p_ccb = &obex_cb.ccb[ccb_idx];
if (p_ccb->state != OBEX_STATE_OPENED) {
ret = OBEX_NOT_OPEN;
break;
}
if (pkt->len > p_ccb->tl_peer_mtu) {
ret = OBEX_PACKET_TOO_LARGE;
break;
}
ret = obex_cb.tl_ops[p_ccb->tl]->send(p_ccb->tl_hdl, pkt);
/* packet has pass to lower layer, do not free it */
free_pkt = false;
if (ret == OBEX_TL_SUCCESS || ret == OBEX_TL_CONGESTED) {
ret = OBEX_SUCCESS;
}
else {
ret = OBEX_ERROR_TL;
}
} while (0);
if (free_pkt) {
osi_free(pkt);
}
return ret;
}
/*******************************************************************************
**
** Function OBEX_BuildRequest
**
** Description Build a request packet with opcode and additional info,
** packet can free by osi_free
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_BuildRequest(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt)
{
if (buff_size < OBEX_MIN_PACKET_SIZE || info == NULL || out_pkt == NULL) {
return OBEX_INVALID_PARAM;
}
buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET;
BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size);
if (p_buf == NULL) {
return OBEX_NO_RESOURCES;
}
UINT16 pkt_len = OBEX_MIN_PACKET_SIZE;
p_buf->offset = OBEX_BT_HDR_MIN_OFFSET;
/* use layer_specific to store the max data length allowed */
p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET;
UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset;
/* byte 0: opcode */
*p_data++ = info->opcode;
/* byte 1, 2: packet length, skip, we will update at last */
UINT8 *p_pkt_len = p_data;
p_data += 2;
switch (info->opcode)
{
case OBEX_OPCODE_CONNECT:
/* byte 3: OBEX version number */
*p_data++ = info->obex_version_number;
/* byte 4: flags */
*p_data++ = info->flags;
/* byte 5, 6: maximum OBEX packet length, recommend to set as our mtu*/
STORE16BE(p_data, info->max_packet_length);
pkt_len += 4;
break;
case OBEX_OPCODE_SETPATH:
/* byte 3: flags */
*p_data++ = info->flags;
/* byte 4: constants, reserved, must be zero */
*p_data++ = 0;
pkt_len += 2;
break;
default:
break;
}
STORE16BE(p_pkt_len, pkt_len);
p_buf->len = pkt_len;
*out_pkt = p_buf;
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_BuildResponse
**
** Description Build a response packet with opcode and response and additional
** info, packet can by free by osi_free
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_BuildResponse(tOBEX_PARSE_INFO *info, UINT16 buff_size, BT_HDR **out_pkt)
{
if (buff_size < OBEX_MIN_PACKET_SIZE || info == NULL || out_pkt == NULL) {
return OBEX_INVALID_PARAM;
}
buff_size += sizeof(BT_HDR) + OBEX_BT_HDR_MIN_OFFSET;
BT_HDR *p_buf= (BT_HDR *)osi_malloc(buff_size);
if (p_buf == NULL) {
return OBEX_NO_RESOURCES;
}
UINT16 pkt_len = OBEX_MIN_PACKET_SIZE;
p_buf->offset = OBEX_BT_HDR_MIN_OFFSET;
/* use layer_specific to store the max data length allowed */
p_buf->layer_specific = buff_size - sizeof(BT_HDR) - OBEX_BT_HDR_MIN_OFFSET;
UINT8 *p_data = (UINT8 *)(p_buf + 1) + p_buf->offset;
/* byte 0: response code */
*p_data++ = info->response_code;
/* byte 1, 2: packet length, skip, we will update at last */
UINT8 *p_pkt_len = p_data;
p_data += 2;
/* we need to use opcode to decide the response format */
switch (info->opcode)
{
case OBEX_OPCODE_CONNECT:
/* byte 3: OBEX version number */
*p_data++ = info->obex_version_number;
/* byte 4: flags */
*p_data++ = info->flags;
/* byte 5, 6: maximum OBEX packet length, recommend to set as our mtu */
STORE16BE(p_data, info->max_packet_length);
pkt_len += 4;
break;
default:
break;
}
STORE16BE(p_pkt_len, pkt_len);
p_buf->len = pkt_len;
*out_pkt = p_buf;
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_AppendHeader
**
** Description Append a header to specific packet, packet can be request
** or response, the format of header must be valid
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_AppendHeader(BT_HDR *pkt, const UINT8 *header)
{
if (pkt == NULL || header == NULL) {
return OBEX_INVALID_PARAM;
}
UINT16 header_len = 0;
UINT8 header_id = *header;
switch (header_id & OBEX_HEADER_ID_U2B_MASK)
{
case OBEX_HEADER_ID_U2B_TYPE1:
case OBEX_HEADER_ID_U2B_TYPE2:
header_len = (header[1] << 8) + header[2];
break;
case OBEX_HEADER_ID_U2B_TYPE3:
header_len = 2;
break;
case OBEX_HEADER_ID_U2B_TYPE4:
header_len = 5;
break;
default:
break;
}
if (header_len == 0) {
return OBEX_INVALID_PARAM;
}
if (pkt->layer_specific - pkt->len < header_len) {
/* the packet can not hold this header */
return OBEX_NO_RESOURCES;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
UINT8 *p_start = p_data + pkt->len;
memcpy(p_start, header, header_len);
pkt->len += header_len;
/* point to packet len */
p_data++;
STORE16BE(p_data, pkt->len);
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_AppendHeaderRaw
**
** Description Append a header to specific packet, packet can be request
** or response, data not include 2 byte length prefixed
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_AppendHeaderRaw(BT_HDR *pkt, UINT8 header_id, const UINT8 *data, UINT16 data_len)
{
if (pkt == NULL || data == NULL) {
return OBEX_INVALID_PARAM;
}
UINT16 header_len = 0;
BOOLEAN store_header_len = FALSE;
switch (header_id & OBEX_HEADER_ID_U2B_MASK)
{
case OBEX_HEADER_ID_U2B_TYPE1:
case OBEX_HEADER_ID_U2B_TYPE2:
/* header id + 2 byte length prefixed + data */
header_len = data_len + 3;
store_header_len = TRUE;
break;
case OBEX_HEADER_ID_U2B_TYPE3:
header_len = 2;
if (data_len != 1) {
return OBEX_INVALID_PARAM;
}
break;
case OBEX_HEADER_ID_U2B_TYPE4:
header_len = 5;
if (data_len != 4) {
return OBEX_INVALID_PARAM;
}
break;
default:
break;
}
if (header_len == 0) {
return OBEX_INVALID_PARAM;
}
if (pkt->layer_specific - pkt->len < header_len) {
/* the packet can not hold this header */
return OBEX_NO_RESOURCES;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
UINT8 *p_start = p_data + pkt->len;
/* store header id */
*p_start++ = header_id;
if (store_header_len) {
/* store header length */
STORE16BE(p_start, header_len);
p_start+= 2;
}
/* store data */
memcpy(p_start, data, data_len);
pkt->len += header_len;
/* point to packet len */
p_data++;
STORE16BE(p_data, pkt->len);
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_AppendHeaderSRM
**
** Description Append a Single Response Mode header
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_AppendHeaderSRM(BT_HDR *pkt, UINT8 value)
{
return OBEX_AppendHeaderRaw(pkt, OBEX_HEADER_ID_SRM, &value, 1);
}
/*******************************************************************************
**
** Function OBEX_AppendHeaderSRMP
**
** Description Append a Single Response Mode Parameters header
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_AppendHeaderSRMP(BT_HDR *pkt, UINT8 value)
{
return OBEX_AppendHeaderRaw(pkt, OBEX_HEADER_ID_SRM_PARAM, &value, 1);
}
/*******************************************************************************
**
** Function OBEX_GetPacketFreeSpace
**
** Description Get the current free space of a packet, use this to check
** if a packet can hold a header
**
** Returns Current free space of a packet, in bytes
**
*******************************************************************************/
UINT16 OBEX_GetPacketFreeSpace(BT_HDR *pkt)
{
if (pkt == NULL) {
return 0;
}
return pkt->layer_specific - pkt->len;
}
/*******************************************************************************
**
** Function OBEX_GetPacketLength
**
** Description Get the current packet length
**
** Returns Current packet length, in bytes
**
*******************************************************************************/
UINT16 OBEX_GetPacketLength(BT_HDR *pkt)
{
if (pkt == NULL) {
return 0;
}
return pkt->len;
}
/*******************************************************************************
**
** Function OBEX_ParseRequest
**
** Description Parse a request packet
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_ParseRequest(BT_HDR *pkt, tOBEX_PARSE_INFO *info)
{
if (pkt == NULL || info == NULL) {
return OBEX_INVALID_PARAM;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
info->opcode = *p_data;
switch (info->opcode)
{
case OBEX_OPCODE_CONNECT:
info->obex_version_number = p_data[3];
info->flags = p_data[4];
info->max_packet_length = (p_data[5] << 8) + p_data[6];
info->next_header_pos = 7;
break;
case OBEX_OPCODE_SETPATH:
info->flags = p_data[3];
info->next_header_pos = 5;
break;
default:
info->next_header_pos = 3;
break;
}
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_ParseResponse
**
** Description Parse a request response packet
**
** Returns OBEX_SUCCESS if successful, otherwise failed
**
*******************************************************************************/
UINT16 OBEX_ParseResponse(BT_HDR *pkt, UINT8 opcode, tOBEX_PARSE_INFO *info)
{
if (pkt == NULL || info == NULL) {
return OBEX_INVALID_PARAM;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
info->opcode = opcode;
info->response_code = *p_data;
switch (opcode)
{
case OBEX_OPCODE_CONNECT:
info->obex_version_number = p_data[3];
info->flags = p_data[4];
info->max_packet_length = (p_data[5] << 8) + p_data[6];
info->next_header_pos = 7;
break;
default:
info->next_header_pos = 3;
break;
}
return OBEX_SUCCESS;
}
/*******************************************************************************
**
** Function OBEX_CheckFinalBit
**
** Description Check whether a packet had set the final bit
**
** Returns TRUE if final bit set, otherwise, false
**
*******************************************************************************/
BOOLEAN OBEX_CheckFinalBit(BT_HDR *pkt)
{
if (pkt == NULL) {
return FALSE;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
return (*p_data) & OBEX_FINAL_BIT_MASK;
}
/*******************************************************************************
**
** Function OBEX_CheckContinueResponse
**
** Description Check whether a packet is continue response
**
** Returns TRUE if continue response, otherwise, false
**
*******************************************************************************/
BOOLEAN OBEX_CheckContinueResponse(BT_HDR *pkt)
{
if (pkt == NULL) {
return FALSE;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
return (*p_data == 0x90) || (*p_data == 0x10);
}
/*******************************************************************************
**
** Function OBEX_GetHeaderLength
**
** Description Get header length
**
** Returns header length
**
*******************************************************************************/
UINT16 OBEX_GetHeaderLength(UINT8 *header)
{
UINT16 header_len = 0;
UINT8 header_id = *header;
switch (header_id & OBEX_HEADER_ID_U2B_MASK)
{
case OBEX_HEADER_ID_U2B_TYPE1:
case OBEX_HEADER_ID_U2B_TYPE2:
header_len = (header[1] << 8) + header[2];
break;
case OBEX_HEADER_ID_U2B_TYPE3:
header_len = 2;
break;
case OBEX_HEADER_ID_U2B_TYPE4:
header_len = 5;
break;
default:
/* unreachable */
break;
}
return header_len;
}
/*******************************************************************************
**
** Function OBEX_GetNextHeader
**
** Description Get next header pointer from a packet
**
** Returns Pointer to start address of a header, NULL if no more header
** or failed
**
*******************************************************************************/
UINT8 *OBEX_GetNextHeader(BT_HDR *pkt, tOBEX_PARSE_INFO *info)
{
if (pkt == NULL || info == NULL) {
return NULL;
}
UINT8 *p_data = (UINT8 *)(pkt + 1) + pkt->offset;
if (info->next_header_pos == 0 || info->next_header_pos >= pkt->len) {
return NULL;
}
UINT8 *header = p_data + info->next_header_pos;
UINT16 header_len = OBEX_GetHeaderLength(header);
info->next_header_pos += header_len;
return header;
}
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -0,0 +1,211 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "osi/osi.h"
#include "osi/allocator.h"
#include "common/bt_target.h"
#include "stack/obex_api.h"
#include "obex_int.h"
#include "obex_tl.h"
#if (OBEX_INCLUDED == TRUE)
#if OBEX_DYNAMIC_MEMORY == FALSE
tOBEX_CB obex_cb;
#else
tOBEX_CB *obex_cb_ptr = NULL;
#endif
static tOBEX_CCB *obex_find_ccb_by_tl_hdl(UINT8 tl, UINT16 tl_hdl)
{
tOBEX_CCB *p_ccb = NULL;
for (int i = 0; i < OBEX_MAX_CONNECTION; ++i) {
if (obex_cb.ccb[i].allocated && obex_cb.ccb[i].tl == tl && obex_cb.ccb[i].tl_hdl == tl_hdl) {
p_ccb = &obex_cb.ccb[i];
break;
}
}
return p_ccb;
}
static tOBEX_SCB *obex_find_scb_by_tl_hdl(UINT8 tl, UINT16 tl_hdl)
{
tOBEX_SCB *p_scb = NULL;
for (int i = 0; i < OBEX_MAX_SERVER; ++i) {
if (obex_cb.scb[i].allocated && obex_cb.scb[i].tl == tl && obex_cb.scb[i].tl_hdl == tl_hdl) {
p_scb = &obex_cb.scb[i];
break;
}
}
return p_scb;
}
tOBEX_CCB *obex_allocate_ccb(void)
{
tOBEX_CCB *p_ccb = NULL;
for (int i = 0; i < OBEX_MAX_CONNECTION; ++i) {
if (!obex_cb.ccb[i].allocated) {
obex_cb.ccb[i].allocated = i + 1;
p_ccb = &obex_cb.ccb[i];
break;
}
}
return p_ccb;
}
void obex_free_ccb(tOBEX_CCB *p_ccb)
{
assert(p_ccb->allocated != 0);
memset(p_ccb, 0, sizeof(tOBEX_CCB));
}
tOBEX_SCB *obex_allocate_scb(void)
{
tOBEX_SCB *p_scb = NULL;
for (int i = 0; i < OBEX_MAX_SERVER; ++i) {
if (!obex_cb.scb[i].allocated) {
obex_cb.scb[i].allocated = i + 1;
p_scb = &obex_cb.scb[i];
break;
}
}
return p_scb;
}
void obex_free_scb(tOBEX_SCB *p_scb)
{
assert(p_scb->allocated != 0);
memset(p_scb, 0, sizeof(tOBEX_SCB));
}
/* check whether connection mtu is valid, if not, disconnect it */
static bool check_conn_mtu_valid(tOBEX_CCB *p_ccb, BOOLEAN call_cb)
{
if (p_ccb->tl_our_mtu < 255 || p_ccb->tl_peer_mtu < 255) {
if (call_cb && p_ccb->callback) {
p_ccb->callback(p_ccb->allocated, OBEX_DISCONNECT_EVT, NULL);
}
OBEX_TRACE_ERROR("Check OBEX transport layer MTU failed, disconnect");
obex_cb.tl_ops[p_ccb->tl]->disconnect(p_ccb->tl_hdl);
obex_free_ccb(p_ccb);
return false;
}
return true;
}
void obex_tl_evt_handler(UINT8 tl, tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg)
{
UINT16 tl_hdl = msg->any.hdl;
tOBEX_CCB *p_ccb = obex_find_ccb_by_tl_hdl(tl, tl_hdl);
tOBEX_SCB *p_scb = NULL;
tOBEX_MSG cb_msg = {0};
switch (evt)
{
case OBEX_TL_CONN_OPEN_EVT:
assert(p_ccb != NULL);
p_ccb->tl_peer_mtu = msg->conn_open.peer_mtu;
p_ccb->tl_our_mtu = msg->conn_open.our_mtu;
if (!check_conn_mtu_valid(p_ccb, TRUE)) {
break;
}
p_ccb->state = OBEX_STATE_OPENED;
if (p_ccb->callback) {
cb_msg.connect.peer_mtu = msg->conn_open.peer_mtu;
cb_msg.connect.our_mtu = msg->conn_open.our_mtu;
p_ccb->callback(p_ccb->allocated, OBEX_CONNECT_EVT, &cb_msg);
}
break;
case OBEX_TL_DIS_CONN_EVT:
if (p_ccb != NULL) {
if (p_ccb->callback) {
p_ccb->callback(p_ccb->allocated, OBEX_DISCONNECT_EVT, NULL);
}
obex_free_ccb(p_ccb);
}
break;
case OBEX_TL_CONGEST_EVT:
assert(p_ccb != NULL);
if (p_ccb->callback) {
p_ccb->callback(p_ccb->allocated, OBEX_CONGEST_EVT, NULL);
}
break;
case OBEX_TL_UNCONGEST_EVT:
assert(p_ccb != NULL);
if (p_ccb->callback) {
p_ccb->callback(p_ccb->allocated, OBEX_UNCONGEST_EVT, NULL);
}
break;
case OBEX_TL_MTU_CHANGE_EVT:
assert(p_ccb != NULL);
p_ccb->tl_peer_mtu = msg->mtu_chg.peer_mtu;
p_ccb->tl_our_mtu = msg->mtu_chg.our_mtu;
if (!check_conn_mtu_valid(p_ccb, TRUE)) {
break;
}
if (p_ccb->callback) {
cb_msg.mtu_change.peer_mtu = msg->mtu_chg.peer_mtu;
cb_msg.mtu_change.our_mtu = msg->mtu_chg.our_mtu;
p_ccb->callback(p_ccb->allocated, OBEX_MTU_CHANGE_EVT, &cb_msg);
}
break;
case OBEX_TL_DATA_EVT:
assert(p_ccb != NULL);
if (p_ccb->callback) {
cb_msg.data.pkt = msg->data.p_buf;
p_ccb->callback(p_ccb->allocated, OBEX_DATA_EVT, &cb_msg);
}
else {
/* No callback, just free the packet */
osi_free(msg->data.p_buf);
}
break;
case OBEX_TL_CONN_INCOME_EVT:
/* New connection, p_ccb should be NULL */
assert(p_ccb == NULL);
p_scb = obex_find_scb_by_tl_hdl(tl, msg->conn_income.svr_hdl);
if (p_scb == NULL) {
obex_cb.tl_ops[tl]->disconnect(tl_hdl);
break;
}
if ((p_ccb = obex_allocate_ccb()) == NULL) {
obex_cb.tl_ops[tl]->disconnect(tl_hdl);
break;
}
p_ccb->tl = tl;
p_ccb->tl_hdl = tl_hdl;
p_ccb->role = OBEX_ROLE_SERVER;
p_ccb->tl_peer_mtu = msg->conn_income.peer_mtu;
p_ccb->tl_our_mtu = msg->conn_income.our_mtu;
if (!check_conn_mtu_valid(p_ccb, FALSE)) {
break;
}
p_ccb->state = OBEX_STATE_OPENED;
p_ccb->callback = p_scb->callback;
if (p_ccb->callback) {
cb_msg.conn_income.svr_handle = p_scb->allocated << 8;
cb_msg.conn_income.peer_mtu = msg->conn_income.peer_mtu;
cb_msg.conn_income.our_mtu = msg->conn_income.our_mtu;
p_ccb->callback(p_ccb->allocated, OBEX_CONN_INCOME_EVT, &cb_msg);
}
break;
default:
OBEX_TRACE_ERROR("Unknown OBEX transport event: 0x%x\n", evt);
break;
}
}
void obex_tl_l2cap_callback(tOBEX_TL_EVT evt, tOBEX_TL_MSG *msg)
{
obex_tl_evt_handler(OBEX_OVER_L2CAP, evt, msg);
}
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -0,0 +1,807 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "osi/osi.h"
#include "osi/allocator.h"
#include "common/bt_target.h"
#include "stack/l2c_api.h"
#include "stack/l2cdefs.h"
#include "stack/btm_api.h"
#include "btm_int.h"
#include "obex_tl.h"
#include "obex_tl_l2cap.h"
#if (OBEX_INCLUDED == TRUE)
#define OBEX_TL_L2CAP_NUM_CONN 4
#define OBEX_TL_L2CAP_NUM_SERVER 2
#define OBEX_TL_L2CAP_STATUS_FLAG_CFG_DONE 0x01
#define OBEX_TL_L2CAP_STATUS_FLAG_PEER_CFG_DONE 0x02
#define OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED 0x80
/* ERTM Tx window size */
#define OBEX_TL_L2CAP_FCR_OPT_TX_WINDOW_SIZE 10
/* ERTM Maximum transmissions before disconnecting */
#define OBEX_TL_L2CAP_FCR_OPT_MAX_TX_B4_DISCNT 20
/* ERTM Retransmission timeout (2 secs) */
#define OBEX_TL_L2CAP_FCR_OPT_RETX_TOUT 2000
/* ERTM Monitor timeout (12 secs) */
#define OBEX_TL_L2CAP_FCR_OPT_MONITOR_TOUT 12000
/* ERTM ERTM MPS segment size */
#define OBEX_TL_L2CAP_FCR_OPT_MAX_PDU_SIZE 1010
typedef struct {
UINT16 vpsm; /* psm in our side */
UINT16 lcid; /* local channel id */
UINT16 peer_mtu; /* MTU that peer device set */
UINT16 our_mtu; /* MTU that we set to peer device */
BOOLEAN initiator; /* TRUE if is initiator, otherwise FALSE */
UINT8 id; /* remote channel id */
UINT8 status_flag; /* status flag used in config procedure */
UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, equal to handle */
BD_ADDR addr; /* peer bluetooth device address */
} tOBEX_TL_L2CAP_CCB;
typedef struct {
UINT16 psm; /* psm in our side */
UINT16 pref_mtu; /* preferred mtu */
UINT8 allocated; /* 0 if not allocated, otherwise, index + 1, handle of server will left shift 8 bits */
} tOBEX_TL_L2CAP_SCB;
typedef struct {
tOBEX_TL_CBACK *callback; /* Upper layer callback */
tOBEX_TL_L2CAP_CCB ccb[OBEX_TL_L2CAP_NUM_CONN];
tOBEX_TL_L2CAP_SCB scb[OBEX_TL_L2CAP_NUM_SERVER];
tL2CAP_APPL_INFO l2cap_reg_info; /* Default L2CAP Registration info */
UINT8 trace_level; /* trace level */
} tOBEX_TL_L2CAP_CB;
#if OBEX_DYNAMIC_MEMORY == FALSE
static tOBEX_TL_L2CAP_CB obex_tl_l2cap_cb;
#else
static tOBEX_TL_L2CAP_CB *obex_tl_l2cap_cb_ptr = NULL;
#define obex_tl_l2cap_cb (*obex_tl_l2cap_cb_ptr)
#endif
static tL2CAP_ERTM_INFO obex_tl_l2cap_etm_opts =
{
L2CAP_FCR_ERTM_MODE,
L2CAP_FCR_CHAN_OPT_ERTM | L2CAP_FCR_CHAN_OPT_BASIC, /* Some devices do not support ERTM */
L2CAP_USER_RX_BUF_SIZE,
L2CAP_USER_TX_BUF_SIZE,
L2CAP_FCR_RX_BUF_SIZE,
L2CAP_FCR_TX_BUF_SIZE
};
static tL2CAP_FCR_OPTS obex_tl_l2cap_fcr_opts =
{
L2CAP_FCR_ERTM_MODE,
OBEX_TL_L2CAP_FCR_OPT_TX_WINDOW_SIZE, /* Tx window size */
OBEX_TL_L2CAP_FCR_OPT_MAX_TX_B4_DISCNT, /* Maximum transmissions before disconnecting */
OBEX_TL_L2CAP_FCR_OPT_RETX_TOUT, /* Retransmission timeout (2 secs) */
OBEX_TL_L2CAP_FCR_OPT_MONITOR_TOUT, /* Monitor timeout (12 secs) */
OBEX_TL_L2CAP_FCR_OPT_MAX_PDU_SIZE /* MPS segment size */
};
static tOBEX_TL_L2CAP_CCB *allocate_ccb(void)
{
tOBEX_TL_L2CAP_CCB *p_ccb = NULL;
for(int i = 0; i < OBEX_TL_L2CAP_NUM_CONN; ++i) {
if (obex_tl_l2cap_cb.ccb[i].allocated == 0) {
obex_tl_l2cap_cb.ccb[i].allocated = i + 1;
p_ccb = &obex_tl_l2cap_cb.ccb[i];
break;
}
}
return p_ccb;
}
static void free_ccb(tOBEX_TL_L2CAP_CCB *p_ccb)
{
memset(p_ccb, 0, sizeof(tOBEX_TL_L2CAP_CCB));
}
static tOBEX_TL_L2CAP_CCB *find_ccb_by_lcid(UINT16 lcid)
{
tOBEX_TL_L2CAP_CCB *p_ccb = NULL;
for (int i = 0; i < OBEX_TL_L2CAP_NUM_CONN; ++i) {
if (obex_tl_l2cap_cb.ccb[i].allocated && obex_tl_l2cap_cb.ccb[i].lcid == lcid) {
p_ccb = &obex_tl_l2cap_cb.ccb[i];
break;
}
}
return p_ccb;
}
static tOBEX_TL_L2CAP_CCB *find_ccb_by_hdl(UINT16 hdl)
{
tOBEX_TL_L2CAP_CCB *p_ccb = NULL;
if (hdl > 0 && hdl <= OBEX_TL_L2CAP_NUM_CONN) {
if (obex_tl_l2cap_cb.ccb[hdl-1].allocated == hdl) {
p_ccb = &obex_tl_l2cap_cb.ccb[hdl-1];
}
}
return p_ccb;
}
static tOBEX_TL_L2CAP_CCB *find_ccb_by_psm(UINT16 psm)
{
tOBEX_TL_L2CAP_CCB *p_ccb = NULL;
for(int i = 0; i < OBEX_TL_L2CAP_NUM_CONN; ++i) {
if (obex_tl_l2cap_cb.ccb[i].allocated && obex_tl_l2cap_cb.ccb[i].vpsm == psm) {
p_ccb = &obex_tl_l2cap_cb.ccb[i];
break;
}
}
return p_ccb;
}
static tOBEX_TL_L2CAP_SCB *allocate_scb(void)
{
tOBEX_TL_L2CAP_SCB *p_scb = NULL;
for(int i = 0; i < OBEX_TL_L2CAP_NUM_SERVER; ++i) {
if (obex_tl_l2cap_cb.scb[i].allocated == 0) {
obex_tl_l2cap_cb.scb[i].allocated = i + 1;
p_scb = &obex_tl_l2cap_cb.scb[i];
break;
}
}
return p_scb;
}
static tOBEX_TL_L2CAP_SCB *find_scb_by_psm(UINT16 psm)
{
tOBEX_TL_L2CAP_SCB *p_scb = NULL;
for(int i = 0; i < OBEX_TL_L2CAP_NUM_SERVER; ++i) {
if (obex_tl_l2cap_cb.scb[i].allocated && obex_tl_l2cap_cb.scb[i].psm == psm) {
p_scb = &obex_tl_l2cap_cb.scb[i];
break;
}
}
return p_scb;
}
static void free_scb(tOBEX_TL_L2CAP_SCB *p_scb)
{
memset(p_scb, 0, sizeof(tOBEX_TL_L2CAP_SCB));
}
static tOBEX_TL_L2CAP_SCB *find_scb_by_hdl(UINT16 hdl)
{
tOBEX_TL_L2CAP_SCB *p_scb = NULL;
hdl = hdl >> 8;
if (hdl > 0 && hdl <= OBEX_TL_L2CAP_NUM_SERVER) {
if (obex_tl_l2cap_cb.scb[hdl-1].allocated == hdl) {
p_scb = &obex_tl_l2cap_cb.scb[hdl-1];
}
}
return p_scb;
}
/*******************************************************************************
*
* Function obex_tl_l2cap_sec_check_complete_term
*
* Description OBEX over L2CAP security check complete callback function
* (terminated).
*
******************************************************************************/
static void l2cap_sec_check_complete_term(BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res)
{
tOBEX_TL_L2CAP_CCB *p_ccb = (tOBEX_TL_L2CAP_CCB *)p_ref_data;
if (res == BTM_SUCCESS) {
L2CA_ErtmConnectRsp(p_ccb->addr, p_ccb->id, p_ccb->lcid, L2CAP_CONN_OK, 0, &obex_tl_l2cap_etm_opts);
tL2CAP_CFG_INFO cfg = {0};
cfg.mtu_present = TRUE;
cfg.mtu = p_ccb->our_mtu;
cfg.fcr_present = TRUE;
cfg.fcr = obex_tl_l2cap_fcr_opts;
L2CA_ConfigReq(p_ccb->lcid, &cfg);
}
else{
L2CA_ErtmConnectRsp(p_ccb->addr, p_ccb->id, p_ccb->lcid, L2CAP_CONN_SECURITY_BLOCK, 0, &obex_tl_l2cap_etm_opts);
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
free_ccb(p_ccb);
}
}
/*******************************************************************************
*
* Function obex_tl_l2cap_sec_check_complete_orig
*
* Description OBEX over L2CAP security check complete callback function
* (originated).
*
******************************************************************************/
static void l2cap_sec_check_complete_orig(BD_ADDR bd_addr, tBT_TRANSPORT transport, void *p_ref_data, UINT8 res)
{
tOBEX_TL_L2CAP_CCB *p_ccb = (tOBEX_TL_L2CAP_CCB *)p_ref_data;
if (res == BTM_SUCCESS) {
tL2CAP_CFG_INFO cfg = {0};
cfg.mtu_present = TRUE;
cfg.mtu = p_ccb->our_mtu;
cfg.fcr_present = TRUE;
cfg.fcr = obex_tl_l2cap_fcr_opts;
L2CA_ConfigReq(p_ccb->lcid, &cfg);
} else {
L2CA_DisconnectReq(p_ccb->lcid);
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
free_ccb(p_ccb);
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_connect_ind
**
** Description This is a callback function called by L2CAP when
** L2CA_ConnectInd received.
**
*******************************************************************************/
void obex_tl_l2cap_connect_ind(BD_ADDR addr, UINT16 lcid, UINT16 psm, UINT8 id)
{
tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(psm);
if (p_scb == NULL) {
/* An incoming connection, but no corresponding server found, reject */
L2CA_ErtmConnectRsp (addr, id, lcid, L2CAP_CONN_NO_PSM, 0, &obex_tl_l2cap_etm_opts);
OBEX_TL_L2CAP_TRACE_WARNING("No corresponding server found, reject incoming connection\n");
return;
}
tOBEX_TL_L2CAP_CCB *p_ccb = allocate_ccb();
if (p_ccb == NULL) {
/* No resource, can not allocate ccb, reject */
L2CA_ErtmConnectRsp (addr, id, lcid, L2CAP_CONN_NO_RESOURCES, 0, &obex_tl_l2cap_etm_opts);
OBEX_TL_L2CAP_TRACE_WARNING("Can not allocate a ccb for new connection\n");
return;
}
bdcpy(p_ccb->addr, addr);
p_ccb->initiator = FALSE;
p_ccb->lcid = lcid;
p_ccb->id = id;
p_ccb->vpsm = psm;
p_ccb->our_mtu = p_scb->pref_mtu;
if (btm_sec_mx_access_request(p_ccb->addr, p_ccb->vpsm,
FALSE, BTM_SEC_PROTO_OBEX,
OBEX_TL_L2CAP_NUM_CONN + p_scb->allocated - 1,
&l2cap_sec_check_complete_term, p_ccb) == BTM_CMD_STARTED) {
L2CA_ErtmConnectRsp(addr, id, lcid, L2CAP_CONN_PENDING, 0, &obex_tl_l2cap_etm_opts);
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_connect_cfm
**
** Description This is a callback function called by L2CAP when
** ConnectCfm received.
**
*******************************************************************************/
void obex_tl_l2cap_connect_cfm(UINT16 lcid, UINT16 result)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid);
if (p_ccb == NULL) {
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_connect_cfm but no ccb found\n");
return;
}
if (result == L2CAP_CONN_OK) {
btm_sec_mx_access_request(p_ccb->addr, p_ccb->vpsm,
TRUE, BTM_SEC_PROTO_OBEX,
p_ccb->allocated - 1,
&l2cap_sec_check_complete_orig, p_ccb);
} else {
OBEX_TL_L2CAP_TRACE_WARNING("l2cap_connect_cfm result != L2CAP_CONN_OK: result: 0x%x\n", result);
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
free_ccb(p_ccb);
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_config_ind
**
** Description This is a callback function called by L2CAP when
** L2CA_ConfigInd received.
**
*******************************************************************************/
void obex_tl_l2cap_config_ind(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid);
if (p_ccb == NULL) {
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_ind but no ccb found\n");
return;
}
tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(p_ccb->vpsm);
if (p_ccb->initiator == FALSE && p_scb == NULL) {
/* not a initiator, but can not find corresponding server */
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_ind, not a initiator, but can not find corresponding server\n");
return;
}
/* save peer mtu if present */
UINT16 peer_mtu = L2CAP_DEFAULT_MTU;
if (p_cfg->mtu_present) {
peer_mtu = p_cfg->mtu;
}
p_cfg->mtu_present = FALSE;
p_cfg->flush_to_present = FALSE;
p_cfg->qos_present = FALSE;
p_cfg->result = L2CAP_CFG_OK;
L2CA_ConfigRsp(p_ccb->lcid, p_cfg);
if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED) {
/* reconfig l2cap channel */
if (p_ccb->peer_mtu != peer_mtu) {
/* only report to upper if mtu change */
p_ccb->peer_mtu = peer_mtu;
tOBEX_TL_MSG msg = {0};
msg.mtu_chg.hdl = p_ccb->allocated;
msg.mtu_chg.peer_mtu = peer_mtu;
msg.mtu_chg.our_mtu = p_ccb->our_mtu;
obex_tl_l2cap_cb.callback(OBEX_TL_MTU_CHANGE_EVT, &msg);
}
return;
}
p_ccb->peer_mtu = peer_mtu;
p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_PEER_CFG_DONE;
if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CFG_DONE) {
p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED;
}
if ((p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED)) {
tOBEX_TL_MSG msg = {0};
if (p_ccb->initiator) {
msg.conn_open.hdl = p_ccb->allocated;
msg.conn_open.peer_mtu = p_ccb->peer_mtu;
msg.conn_open.our_mtu = p_ccb->our_mtu;
obex_tl_l2cap_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg);
}
else {
msg.conn_income.hdl = p_ccb->allocated;
msg.conn_income.peer_mtu = p_ccb->peer_mtu;
msg.conn_income.our_mtu = p_ccb->our_mtu;
msg.conn_income.svr_hdl = p_scb->allocated << 8;
obex_tl_l2cap_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg);
}
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_config_cfm
**
** Description This is a callback function called by L2CAP when
** L2CA_ConfigCnf received.
**
*******************************************************************************/
void obex_tl_l2cap_config_cfm(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid);
if (p_ccb == NULL) {
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_cfm but no ccb found\n");
return;
}
tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(p_ccb->vpsm);
if (p_ccb->initiator == FALSE && p_scb == NULL) {
/* not a initiator, but can not find corresponding server */
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_config_cfm, not a initiator, but can not find corresponding server\n");
return;
}
if (p_cfg->result != L2CAP_CFG_OK) {
OBEX_TL_L2CAP_TRACE_WARNING("l2cap_config_cfm result != L2CAP_CFG_OK: result: 0x%x\n", p_cfg->result);
L2CA_DisconnectReq(p_ccb->lcid);
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
free_ccb(p_ccb);
return;
}
p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_CFG_DONE;
if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_PEER_CFG_DONE) {
p_ccb->status_flag |= OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED;
}
if (p_ccb->status_flag & OBEX_TL_L2CAP_STATUS_FLAG_CONNECTED) {
tOBEX_TL_MSG msg = {0};
if (p_ccb->initiator) {
msg.conn_open.hdl = p_ccb->allocated;
msg.conn_open.peer_mtu = p_ccb->peer_mtu;
msg.conn_open.our_mtu = p_ccb->our_mtu;
obex_tl_l2cap_cb.callback(OBEX_TL_CONN_OPEN_EVT, &msg);
}
else {
msg.conn_income.hdl = p_ccb->allocated;
msg.conn_income.peer_mtu = p_ccb->peer_mtu;
msg.conn_income.our_mtu = p_ccb->our_mtu;
msg.conn_income.svr_hdl = p_scb->allocated << 8;
obex_tl_l2cap_cb.callback(OBEX_TL_CONN_INCOME_EVT, &msg);
}
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_qos_violation_ind
**
** Description This is a callback function called by L2CAP when
** L2CA_QoSViolationIndInd received.
**
*******************************************************************************/
void obex_tl_l2cap_qos_violation_ind(BD_ADDR addr)
{
UNUSED(addr);
}
/*******************************************************************************
**
** Function obex_tl_l2cap_disconnect_ind
**
** Description This is a callback function called by L2CAP when
** L2CA_DisconnectInd received.
**
*******************************************************************************/
void obex_tl_l2cap_disconnect_ind(UINT16 lcid, BOOLEAN is_conf_needed)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid);
if (is_conf_needed) {
L2CA_DisconnectRsp(lcid);
}
if (p_ccb == NULL) {
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_disconnect_ind but no ccb found\n");
return;
}
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
free_ccb(p_ccb);
}
/*******************************************************************************
**
** Function obex_tl_l2cap_buf_data_ind
**
** Description This is a callback function called by L2CAP when
** data frame is received.
**
*******************************************************************************/
void obex_tl_l2cap_buf_data_ind(UINT16 lcid, BT_HDR *p_buf)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid);
if (p_ccb == NULL) {
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_buf_data_ind but no ccb found\n");
osi_free(p_buf);
return;
}
tOBEX_TL_MSG msg = {0};
msg.data.hdl = p_ccb->allocated;
msg.data.p_buf = p_buf;
obex_tl_l2cap_cb.callback(OBEX_TL_DATA_EVT, &msg);
}
/*******************************************************************************
**
** Function obex_tl_l2cap_congestion_status_ind
**
** Description This is a callback function called by L2CAP when
** L2CAP channel congestion status changes
**
*******************************************************************************/
void obex_tl_l2cap_congestion_status_ind(UINT16 lcid, BOOLEAN is_congested)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_lcid(lcid);
if (p_ccb == NULL) {
OBEX_TL_L2CAP_TRACE_ERROR("l2cap_congestion_status_ind but no ccb found\n");
return;
}
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
if (is_congested) {
obex_tl_l2cap_cb.callback(OBEX_TL_CONGEST_EVT, &msg);
}
else {
obex_tl_l2cap_cb.callback(OBEX_TL_UNCONGEST_EVT, &msg);
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_init
**
** Description Initialize OBEX over L2CAP transport layer, callback
** can not be NULL, must be called once before using any
** other APIs
**
*******************************************************************************/
void obex_tl_l2cap_init(tOBEX_TL_CBACK callback)
{
assert(callback != NULL);
#if (OBEX_DYNAMIC_MEMORY)
if (!obex_tl_l2cap_cb_ptr) {
obex_tl_l2cap_cb_ptr = (tOBEX_TL_L2CAP_CB *)osi_malloc(sizeof(tOBEX_TL_L2CAP_CB));
if (!obex_tl_l2cap_cb_ptr) {
OBEX_TL_L2CAP_TRACE_ERROR("OBEX over L2CAP transport layer initialize failed, no memory\n");
assert(0);
}
}
#endif /* #if (OBEX_DYNAMIC_MEMORY) */
memset(&obex_tl_l2cap_cb, 0, sizeof(tOBEX_TL_L2CAP_CB));
obex_tl_l2cap_cb.callback = callback;
obex_tl_l2cap_cb.trace_level = BT_TRACE_LEVEL_ERROR;
tL2CAP_APPL_INFO *p_reg_info = &obex_tl_l2cap_cb.l2cap_reg_info;
p_reg_info->pL2CA_ConnectInd_Cb = NULL; /* obex_tl_l2cap_connect_ind or NULL, depend on server or not */
p_reg_info->pL2CA_ConnectCfm_Cb = obex_tl_l2cap_connect_cfm;
p_reg_info->pL2CA_ConnectPnd_Cb = NULL;
p_reg_info->pL2CA_ConfigInd_Cb = obex_tl_l2cap_config_ind;
p_reg_info->pL2CA_ConfigCfm_Cb = obex_tl_l2cap_config_cfm;
p_reg_info->pL2CA_DisconnectInd_Cb = obex_tl_l2cap_disconnect_ind;
p_reg_info->pL2CA_DisconnectCfm_Cb = NULL;
p_reg_info->pL2CA_QoSViolationInd_Cb = obex_tl_l2cap_qos_violation_ind;
p_reg_info->pL2CA_DataInd_Cb = obex_tl_l2cap_buf_data_ind;
p_reg_info->pL2CA_CongestionStatus_Cb = obex_tl_l2cap_congestion_status_ind;
p_reg_info->pL2CA_TxComplete_Cb = NULL;
}
/*******************************************************************************
**
** Function obex_tl_l2cap_init
**
** Description Deinitialize OBEX over L2CAP transport layer
**
*******************************************************************************/
void obex_tl_l2cap_deinit(void)
{
#if (OBEX_DYNAMIC_MEMORY)
if (obex_tl_l2cap_cb_ptr) {
osi_free(obex_tl_l2cap_cb_ptr);
obex_tl_l2cap_cb_ptr = NULL;
}
#endif /* #if (OBEX_DYNAMIC_MEMORY) */
}
/*******************************************************************************
**
** Function obex_tl_l2cap_connect
**
** Description Start the process of establishing a L2CAP connection
**
** Returns Non-zeros handle, 0 while failed
**
*******************************************************************************/
UINT16 obex_tl_l2cap_connect(tOBEX_TL_SVR_INFO *server)
{
tOBEX_TL_L2CAP_CCB *p_ccb = allocate_ccb();
if (p_ccb == NULL) {
/* can not allocate a ccb */
return 0;
}
if (find_scb_by_psm(server->l2cap.psm) == NULL) {
/* if the psm not register by a server, we register it as outgoing only record */
tL2CAP_APPL_INFO *p_reg_info = &obex_tl_l2cap_cb.l2cap_reg_info;
p_reg_info->pL2CA_ConnectInd_Cb = NULL;
p_ccb->vpsm = L2CA_Register(server->l2cap.psm, p_reg_info);
if (p_ccb->vpsm == 0) {
free_ccb(p_ccb);
return 0;
}
/* set security level */
BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->l2cap.sec_mask, p_ccb->vpsm, BTM_SEC_PROTO_OBEX, p_ccb->allocated - 1);
}
else {
p_ccb->vpsm = server->l2cap.psm;
}
if (server->l2cap.pref_mtu == 0 || server->l2cap.pref_mtu > L2CAP_MTU_SIZE) {
p_ccb->our_mtu = L2CAP_MTU_SIZE;
}
else {
p_ccb->our_mtu = server->l2cap.pref_mtu;
}
p_ccb->initiator = TRUE;
p_ccb->lcid = L2CA_ErtmConnectReq(p_ccb->vpsm, server->l2cap.addr, &obex_tl_l2cap_etm_opts);
if (p_ccb->lcid == 0) {
free_ccb(p_ccb);
return 0;
}
return p_ccb->allocated;
}
/*******************************************************************************
**
** Function obex_tl_l2cap_disconnect
**
** Description Disconnect a L2CAP connection
**
*******************************************************************************/
void obex_tl_l2cap_disconnect(UINT16 hdl)
{
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_hdl(hdl);
if (p_ccb != NULL) {
L2CA_DisconnectReq(p_ccb->lcid);
if (p_ccb->initiator && find_scb_by_psm(p_ccb->vpsm) == NULL) {
L2CA_Deregister(p_ccb->vpsm);
}
free_ccb(p_ccb);
}
}
/*******************************************************************************
**
** Function obex_tl_l2cap_send_data
**
** Description Start the process of establishing a L2CAP connection
**
** Returns OBEX_TL_SUCCESS, if data accepted
** OBEX_TL_CONGESTED, if data accepted and the channel is congested
** OBEX_TL_FAILED, if error
**
*******************************************************************************/
UINT16 obex_tl_l2cap_send_data(UINT16 hdl, BT_HDR *p_buf)
{
UINT16 ret = OBEX_TL_FAILED;
tOBEX_TL_L2CAP_CCB *p_ccb = find_ccb_by_hdl(hdl);
if (p_ccb == NULL) {
osi_free(p_buf);
return ret;
}
/* Can not send data size larger than peer MTU */
/* Offset should not smaller than L2CAP_MIN_OFFSET */
if (p_buf->len > p_ccb->peer_mtu || p_buf->offset < L2CAP_MIN_OFFSET) {
osi_free(p_buf);
return ret;
}
UINT16 status = L2CA_DataWrite(p_ccb->lcid, p_buf);
switch (status)
{
case L2CAP_DW_SUCCESS:
ret = OBEX_TL_SUCCESS;
break;
case L2CAP_DW_CONGESTED:
ret = OBEX_TL_CONGESTED;
break;
default:
ret = OBEX_TL_FAILED;
break;
}
return ret;
}
/*******************************************************************************
**
** Function obex_tl_l2cap_bind
**
** Description Register a server in L2CAP module
**
** Returns Non-zeros handle, 0 while failed
**
*******************************************************************************/
UINT16 obex_tl_l2cap_bind(tOBEX_TL_SVR_INFO *server)
{
tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_psm(server->l2cap.psm);
if (p_scb != NULL) {
/* psm already used */
return 0;
}
p_scb = allocate_scb();
if (p_scb == NULL) {
/* can not allocate a new scb */
return 0;
}
if (server->l2cap.pref_mtu == 0 || server->l2cap.pref_mtu > L2CAP_MTU_SIZE) {
p_scb->pref_mtu= L2CAP_MTU_SIZE;
}
else {
p_scb->pref_mtu = server->l2cap.pref_mtu;
}
tL2CAP_APPL_INFO *p_reg_info = &obex_tl_l2cap_cb.l2cap_reg_info;
p_reg_info->pL2CA_ConnectInd_Cb = obex_tl_l2cap_connect_ind;
/* Register a l2cap server, in this case, L2CA_Register always return the same psm as input */
p_scb->psm = L2CA_Register(server->l2cap.psm, p_reg_info);
if (p_scb->psm == 0) {
free_scb(p_scb);
return 0;
}
/* set security level */
BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_OBEX, server->l2cap.sec_mask, p_scb->psm, BTM_SEC_PROTO_OBEX, OBEX_TL_L2CAP_NUM_CONN + p_scb->allocated - 1);
BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_OBEX, server->l2cap.sec_mask, p_scb->psm, BTM_SEC_PROTO_OBEX, OBEX_TL_L2CAP_NUM_CONN + p_scb->allocated - 1);
/* left shift 8 bits to avoid confuse with connection handle */
return p_scb->allocated << 8;
}
/*******************************************************************************
**
** Function obex_tl_l2cap_unbind
**
** Description Deregister a server in L2CAP module
**
*******************************************************************************/
void obex_tl_l2cap_unbind(UINT16 tl_hdl)
{
tOBEX_TL_L2CAP_SCB *p_scb = find_scb_by_hdl(tl_hdl);
if (p_scb) {
tOBEX_TL_L2CAP_CCB *p_ccb = NULL;
while ((p_ccb = find_ccb_by_psm(p_scb->psm)) != NULL) {
L2CA_DisconnectReq(p_ccb->lcid);
tOBEX_TL_MSG msg = {0};
msg.any.hdl = p_ccb->allocated;
obex_tl_l2cap_cb.callback(OBEX_TL_DIS_CONN_EVT, &msg);
free_ccb(p_ccb);
}
L2CA_Deregister(p_scb->psm);
free_scb(p_scb);
}
}
static tOBEX_TL_OPS obex_tl_l2cap_ops = {
.init = obex_tl_l2cap_init,
.deinit = obex_tl_l2cap_deinit,
.connect = obex_tl_l2cap_connect,
.disconnect = obex_tl_l2cap_disconnect,
.bind = obex_tl_l2cap_bind,
.unbind = obex_tl_l2cap_unbind,
.send = obex_tl_l2cap_send_data
};
/*******************************************************************************
**
** Function obex_tl_l2cap_ops_get
**
** Description Get the operation function structure pointer of OBEX over
** L2CAP transport layer
**
** Returns Pointer to operation function structure
**
*******************************************************************************/
tOBEX_TL_OPS *obex_tl_l2cap_ops_get(void)
{
return &obex_tl_l2cap_ops;
}
#endif /* #if (OBEX_INCLUDED == TRUE) */

View File

@ -372,7 +372,7 @@ BOOLEAN SDP_FindServiceUUIDInRec(tSDP_DISC_REC *p_rec, tBT_UUID *p_uuid)
if (SDP_DISC_ATTR_TYPE(p_sattr->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE) { if (SDP_DISC_ATTR_TYPE(p_sattr->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE) {
/* Look through data element sequence until no more UUIDs */ /* Look through data element sequence until no more UUIDs */
for (p_extra_sattr = p_sattr->attr_value.v.p_sub_attr; p_extra_sattr; p_extra_sattr = p_extra_sattr->p_next_attr) { for (p_extra_sattr = p_sattr->attr_value.v.p_sub_attr; p_extra_sattr; p_extra_sattr = p_extra_sattr->p_next_attr) {
/* Increment past this to see if the next attribut is UUID */ /* Increment past this to see if the next attribute is UUID */
if ((SDP_DISC_ATTR_TYPE(p_extra_sattr->attr_len_type) == UUID_DESC_TYPE) if ((SDP_DISC_ATTR_TYPE(p_extra_sattr->attr_len_type) == UUID_DESC_TYPE)
/* only support 16 bits UUID for now */ /* only support 16 bits UUID for now */
&& (SDP_DISC_ATTR_LEN(p_extra_sattr->attr_len_type) == 2)) { && (SDP_DISC_ATTR_LEN(p_extra_sattr->attr_len_type) == 2)) {
@ -522,7 +522,7 @@ tSDP_DISC_REC *SDP_FindServiceInDb (tSDP_DISCOVERY_DB *p_db, UINT16 service_uuid
if (SDP_DISC_ATTR_TYPE(p_sattr->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE) { if (SDP_DISC_ATTR_TYPE(p_sattr->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE) {
/* Look through data element sequence until no more UUIDs */ /* Look through data element sequence until no more UUIDs */
for (p_extra_sattr = p_sattr->attr_value.v.p_sub_attr; p_extra_sattr; p_extra_sattr = p_extra_sattr->p_next_attr) { for (p_extra_sattr = p_sattr->attr_value.v.p_sub_attr; p_extra_sattr; p_extra_sattr = p_extra_sattr->p_next_attr) {
/* Increment past this to see if the next attribut is UUID */ /* Increment past this to see if the next attribute is UUID */
if ((SDP_DISC_ATTR_TYPE(p_extra_sattr->attr_len_type) == UUID_DESC_TYPE) if ((SDP_DISC_ATTR_TYPE(p_extra_sattr->attr_len_type) == UUID_DESC_TYPE)
&& (SDP_DISC_ATTR_LEN(p_extra_sattr->attr_len_type) == 2) && (SDP_DISC_ATTR_LEN(p_extra_sattr->attr_len_type) == 2)
/* for a specific uuid, or any one */ /* for a specific uuid, or any one */
@ -768,6 +768,28 @@ BOOLEAN SDP_FindProtocolListElemInRec (tSDP_DISC_REC *p_rec, UINT16 layer_uuid,
return (FALSE); return (FALSE);
} }
/*******************************************************************************
**
** Function SDP_FindProtocolListElem
**
** Description This function looks at the protocol list for a specific protocol
** list element.
**
** Returns TRUE if found, FALSE if not
** If found, the passed protocol list element is filled in.
**
*******************************************************************************/
BOOLEAN SDP_FindProtocolListElem (tSDP_DISC_ATTR *p_protocol_list, UINT16 layer_uuid, tSDP_PROTOCOL_ELEM *p_elem)
{
#if SDP_CLIENT_ENABLED == TRUE
/* don't check the input protocol descriptor list id, this api may be use in additional protocol descriptor list */
if ((SDP_DISC_ATTR_TYPE(p_protocol_list->attr_len_type) == DATA_ELE_SEQ_DESC_TYPE)) {
return sdp_fill_proto_elem(p_protocol_list, layer_uuid, p_elem);
}
#endif
/* If here, no match found */
return (FALSE);
}
/******************************************************************************* /*******************************************************************************
** **

View File

@ -36,7 +36,7 @@ idf.py menuconfig
* Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration * Choose external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration
* Enable Classic Bluetooth and A2DP under **Component config --> Bluetooth --> Bluedroid Enable** * For AVRCP CT Cover Art feature, is enabled by default, we can disable it by unselecting menuconfig option `Component config --> Bluetooth --> Bluedroid Options --> Classic Bluetooth --> AVRCP Features --> AVRCP CT Cover Art`. This example will try to use AVRCP CT Cover Art feature, get cover art image and count the image size if peer device support, this can be disable in `A2DP Example Configuration --> Use AVRCP CT Cover Art Feature`.
### Build and Flash ### Build and Flash
@ -66,7 +66,13 @@ I (122697) BT_AV: Audio packet count 100
I (124697) BT_AV: Audio packet count 200 I (124697) BT_AV: Audio packet count 200
I (126697) BT_AV: Audio packet count 300 I (126697) BT_AV: Audio packet count 300
I (128697) BT_AV: Audio packet count 400 I (128697) BT_AV: Audio packet count 400
```
The output when receiving a cover art image:
```
I (53349) RC_CT: AVRC metadata rsp: attribute id 0x80, 1000748
I (53639) RC_CT: Cover Art Client final data event, image size: 14118 bytes
``` ```
Also, the sound will be heard if a loudspeaker is connected and possible external I2S codec is correctly configured. For ESP32 A2DP source example, the sound is noise as the audio source generates the samples with a random sequence. Also, the sound will be heard if a loudspeaker is connected and possible external I2S codec is correctly configured. For ESP32 A2DP source example, the sound is noise as the audio source generates the samples with a random sequence.

View File

@ -53,4 +53,12 @@ menu "A2DP Example Configuration"
default "ESP_SPEAKER" default "ESP_SPEAKER"
help help
Use this option to set local device name. Use this option to set local device name.
config EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
bool "Use AVRCP CT Cover Art Feature"
depends on BT_CLASSIC_ENABLED
default y
help
This enables the AVRCP Cover Art feature in example and try to get cover art image from peer device.
endmenu endmenu

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@ -93,6 +93,13 @@ i2s_chan_handle_t tx_chan = NULL;
dac_continuous_handle_t tx_chan; dac_continuous_handle_t tx_chan;
#endif #endif
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
static bool cover_art_connected = false;
static bool cover_art_getting = false;
static uint32_t cover_art_image_size = 0;
static uint8_t image_handle_old[7];
#endif
/******************************** /********************************
* STATIC FUNCTION DEFINITIONS * STATIC FUNCTION DEFINITIONS
*******************************/ *******************************/
@ -107,6 +114,18 @@ static void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param)
rc->meta_rsp.attr_text = attr_text; rc->meta_rsp.attr_text = attr_text;
} }
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
static bool image_handle_check(uint8_t *image_handle, int len)
{
/* Image handle length must be 7 */
if (len == 7 && memcmp(image_handle_old, image_handle, 7) != 0) {
memcpy(image_handle_old, image_handle, 7);
return true;
}
return false;
}
#endif
static void bt_av_new_track(void) static void bt_av_new_track(void)
{ {
/* request metadata */ /* request metadata */
@ -114,6 +133,11 @@ static void bt_av_new_track(void)
ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_ARTIST |
ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_ALBUM |
ESP_AVRC_MD_ATTR_GENRE; ESP_AVRC_MD_ATTR_GENRE;
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
if (cover_art_connected) {
attr_mask |= ESP_AVRC_MD_ATTR_COVER_ART;
}
#endif
esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask); esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask);
/* register notification if peer support the event_id */ /* register notification if peer support the event_id */
@ -364,7 +388,7 @@ static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param)
if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) { if (a2d->a2d_psc_cfg_stat.psc_mask & ESP_A2D_PSC_DELAY_RPT) {
ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting"); ESP_LOGI(BT_AV_TAG, "Peer device support delay reporting");
} else { } else {
ESP_LOGI(BT_AV_TAG, "Peer device unsupport delay reporting"); ESP_LOGI(BT_AV_TAG, "Peer device unsupported delay reporting");
} }
break; break;
} }
@ -415,15 +439,24 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
} }
break; break;
} }
/* when passthrough responsed, this event comes */ /* when passthrough response, this event comes */
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code, ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d, rsp_code %d", rc->psth_rsp.key_code,
rc->psth_rsp.key_state, rc->psth_rsp.rsp_code); rc->psth_rsp.key_state, rc->psth_rsp.rsp_code);
break; break;
} }
/* when metadata responsed, this event comes */ /* when metadata response, this event comes */
case ESP_AVRC_CT_METADATA_RSP_EVT: { case ESP_AVRC_CT_METADATA_RSP_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text);
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
if(rc->meta_rsp.attr_id == 0x80 && cover_art_connected && cover_art_getting == false) {
/* check image handle is valid and different with last one, wo dont want to get an image repeatedly */
if(image_handle_check(rc->meta_rsp.attr_text, rc->meta_rsp.attr_length)) {
esp_avrc_ct_cover_art_get_linked_thumbnail(rc->meta_rsp.attr_text);
cover_art_getting = true;
}
}
#endif
free(rc->meta_rsp.attr_text); free(rc->meta_rsp.attr_text);
break; break;
} }
@ -436,6 +469,13 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
/* when feature of remote device indicated, this event comes */ /* when feature of remote device indicated, this event comes */
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { case ESP_AVRC_CT_REMOTE_FEATURES_EVT: {
ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %"PRIx32", TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag);
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
if ((rc->rmt_feats.tg_feat_flag & ESP_AVRC_FEAT_FLAG_TG_COVER_ART) && !cover_art_connected) {
ESP_LOGW(BT_RC_CT_TAG, "Peer support Cover Art feature, start connection...");
/* set mtu to zero to use a default value */
esp_avrc_ct_cover_art_connect(0);
}
#endif
break; break;
} }
/* when notification capability of peer device got, this event comes */ /* when notification capability of peer device got, this event comes */
@ -448,6 +488,36 @@ static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param)
bt_av_play_pos_changed(); bt_av_play_pos_changed();
break; break;
} }
case ESP_AVRC_CT_COVER_ART_STATE_EVT: {
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
if (rc->cover_art_state.state == ESP_AVRC_COVER_ART_CONNECTED) {
cover_art_connected = true;
ESP_LOGW(BT_RC_CT_TAG, "Cover Art Client connected");
}
else {
cover_art_connected = false;
ESP_LOGW(BT_RC_CT_TAG, "Cover Art Client disconnected, reason:%d", rc->cover_art_state.reason);
}
#endif
break;
}
case ESP_AVRC_CT_COVER_ART_DATA_EVT: {
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
/* when rc->cover_art_data.final is true, it means we have received the entire image or get operation failed */
if (rc->cover_art_data.final) {
if(rc->cover_art_data.status == ESP_BT_STATUS_SUCCESS) {
ESP_LOGI(BT_RC_CT_TAG, "Cover Art Client final data event, image size: %lu bytes", cover_art_image_size);
}
else {
ESP_LOGE(BT_RC_CT_TAG, "Cover Art Client get operation failed");
}
cover_art_image_size = 0;
/* set the getting state to false, we can get next image now */
cover_art_getting = false;
}
#endif
break;
}
/* others */ /* others */
default: default:
ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event); ESP_LOGE(BT_RC_CT_TAG, "%s unhandled event: %d", __func__, event);
@ -545,6 +615,14 @@ void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len)
void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param)
{ {
#if CONFIG_EXAMPLE_AVRCP_CT_COVER_ART_ENABLE
/* we must handle ESP_AVRC_CT_COVER_ART_DATA_EVT in this callback, copy image data to other buff before return if need */
if (event == ESP_AVRC_CT_COVER_ART_DATA_EVT && param->cover_art_data.status == ESP_BT_STATUS_SUCCESS) {
cover_art_image_size += param->cover_art_data.data_len;
/* copy image data to other place */
/* memcpy(p_buf, param->cover_art_data.p_data, param->cover_art_data.data_len); */
}
#endif
switch (event) { switch (event) {
case ESP_AVRC_CT_METADATA_RSP_EVT: case ESP_AVRC_CT_METADATA_RSP_EVT:
bt_app_alloc_meta_buffer(param); bt_app_alloc_meta_buffer(param);
@ -553,7 +631,9 @@ void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param
case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT:
case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: case ESP_AVRC_CT_CHANGE_NOTIFY_EVT:
case ESP_AVRC_CT_REMOTE_FEATURES_EVT: case ESP_AVRC_CT_REMOTE_FEATURES_EVT:
case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT:
case ESP_AVRC_CT_COVER_ART_STATE_EVT:
case ESP_AVRC_CT_COVER_ART_DATA_EVT: {
bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL);
break; break;
} }

View File

@ -7,4 +7,5 @@ CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=y CONFIG_BT_BLUEDROID_ENABLED=y
CONFIG_BT_CLASSIC_ENABLED=y CONFIG_BT_CLASSIC_ENABLED=y
CONFIG_BT_A2DP_ENABLE=y CONFIG_BT_A2DP_ENABLE=y
CONFIG_BT_AVRCP_CT_COVER_ART_ENABLED=y
CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=n