// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /***************************************************************************** * * Filename: btc_avrc.c * * Description: Bluetooth AVRC implementation * *****************************************************************************/ #include "common/bt_target.h" #include #include "bta/bta_api.h" #include "bta/bta_av_api.h" #include "stack/avrc_defs.h" #include "btc/btc_common.h" #include "btc/btc_util.h" #include "btc_av.h" #include "btc_avrc.h" #include "btc/btc_manage.h" #include "esp_avrc_api.h" #include "osi/mutex.h" #include "osi/allocator.h" #if BTC_AV_INCLUDED /***************************************************************************** ** Constants & Macros ******************************************************************************/ /* for AVRC 1.4 need to change this */ #define MAX_RC_NOTIFICATIONS AVRC_EVT_APP_SETTING_CHANGE #define MAX_VOLUME 128 #define MAX_LABEL 16 #define MAX_TRANSACTIONS_PER_SESSION 16 #define MAX_CMD_QUEUE_LEN 8 #define CHECK_ESP_RC_CONNECTED do { \ BTC_TRACE_DEBUG("## %s ##", __FUNCTION__); \ if (btc_rc_vb.rc_connected == FALSE) { \ BTC_TRACE_WARNING("Function %s() called when RC is not connected", __FUNCTION__); \ return ESP_ERR_INVALID_STATE; \ } \ } while (0) /***************************************************************************** ** Local type definitions ******************************************************************************/ typedef struct { UINT8 bNotify; UINT8 label; } btc_rc_reg_notifications_t; typedef struct { UINT8 label; UINT8 ctype; BOOLEAN is_rsp_pending; } btc_rc_cmd_ctxt_t; /* TODO : Merge btc_rc_reg_notifications_t and btc_rc_cmd_ctxt_t to a single struct */ typedef struct { BOOLEAN rc_connected; UINT8 rc_handle; tBTA_AV_FEAT rc_features; BD_ADDR rc_addr; UINT16 rc_pending_play; btc_rc_cmd_ctxt_t rc_pdu_info[MAX_CMD_QUEUE_LEN]; btc_rc_reg_notifications_t rc_notif[MAX_RC_NOTIFICATIONS]; unsigned int rc_volume; uint8_t rc_vol_label; } btc_rc_cb_t; typedef struct { BOOLEAN in_use; UINT8 lbl; UINT8 handle; } rc_transaction_t; typedef struct { osi_mutex_t lbllock; rc_transaction_t transaction[MAX_TRANSACTIONS_PER_SESSION]; } rc_device_t; rc_device_t device; static void handle_rc_connect (tBTA_AV_RC_OPEN *p_rc_open); static void handle_rc_disconnect (tBTA_AV_RC_CLOSE *p_rc_close); static void handle_rc_passthrough_rsp ( tBTA_AV_REMOTE_RSP *p_remote_rsp); static void handle_rc_metadata_rsp ( tBTA_AV_META_MSG *p_remote_rsp); /***************************************************************************** ** Static variables ******************************************************************************/ static btc_rc_cb_t btc_rc_vb; /***************************************************************************** ** Externs ******************************************************************************/ /***************************************************************************** ** Static functions ******************************************************************************/ static inline void btc_avrc_ct_cb_to_app(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) { esp_avrc_ct_cb_t btc_avrc_cb = (esp_avrc_ct_cb_t)btc_profile_cb_get(BTC_PID_AVRC); if (btc_avrc_cb) { btc_avrc_cb(event, param); } } static void handle_rc_features(void) { esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.rmt_feats.feat_mask = btc_rc_vb.rc_features; memcpy(param.rmt_feats.remote_bda, btc_rc_vb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_REMOTE_FEATURES_EVT, ¶m); } /*************************************************************************** * Function handle_rc_connect * * - Argument: tBTA_AV_RC_OPEN RC open data structure * * - Description: RC connection event handler * ***************************************************************************/ static void handle_rc_connect (tBTA_AV_RC_OPEN *p_rc_open) { BTC_TRACE_DEBUG("%s: rc_handle: %d", __FUNCTION__, p_rc_open->rc_handle); bt_bdaddr_t rc_addr; if (p_rc_open->status == BTA_AV_SUCCESS) { //check if already some RC is connected if (btc_rc_vb.rc_connected) { BTC_TRACE_ERROR("Got RC OPEN in connected state, Connected RC: %d \ and Current RC: %d", btc_rc_vb.rc_handle, p_rc_open->rc_handle ); if ((btc_rc_vb.rc_handle != p_rc_open->rc_handle) && (bdcmp(btc_rc_vb.rc_addr, p_rc_open->peer_addr))) { BTC_TRACE_DEBUG("Got RC connected for some other handle"); BTA_AvCloseRc(p_rc_open->rc_handle); return; } } memcpy(btc_rc_vb.rc_addr, p_rc_open->peer_addr, sizeof(BD_ADDR)); btc_rc_vb.rc_features = p_rc_open->peer_features; btc_rc_vb.rc_vol_label = MAX_LABEL; btc_rc_vb.rc_volume = MAX_VOLUME; btc_rc_vb.rc_connected = TRUE; btc_rc_vb.rc_handle = p_rc_open->rc_handle; bdcpy(rc_addr.address, btc_rc_vb.rc_addr); esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.conn_stat.connected = true; memcpy(param.conn_stat.remote_bda, &rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); /* on locally initiated connection we will get remote features as part of connect */ if (p_rc_open->sdp_disc_done == TRUE) { handle_rc_features(); } } else { BTC_TRACE_ERROR("%s Connect failed with error code: %d", __FUNCTION__, p_rc_open->status); btc_rc_vb.rc_connected = FALSE; } } /*************************************************************************** * Function handle_rc_disconnect * * - Argument: tBTA_AV_RC_CLOSE RC close data structure * * - Description: RC disconnection event handler * ***************************************************************************/ static void handle_rc_disconnect (tBTA_AV_RC_CLOSE *p_rc_close) { bt_bdaddr_t rc_addr; BTC_TRACE_DEBUG("%s: rc_handle: %d", __FUNCTION__, p_rc_close->rc_handle); if ((p_rc_close->rc_handle != btc_rc_vb.rc_handle) && (bdcmp(btc_rc_vb.rc_addr, p_rc_close->peer_addr))) { BTC_TRACE_ERROR("Got disconnect of unknown device"); return; } btc_rc_vb.rc_handle = 0; btc_rc_vb.rc_connected = FALSE; memcpy(btc_rc_vb.rc_addr, p_rc_close->peer_addr, sizeof(BD_ADDR)); memset(btc_rc_vb.rc_notif, 0, sizeof(btc_rc_vb.rc_notif)); btc_rc_vb.rc_features = 0; btc_rc_vb.rc_vol_label = MAX_LABEL; btc_rc_vb.rc_volume = MAX_VOLUME; bdcpy(rc_addr.address, btc_rc_vb.rc_addr); memset(btc_rc_vb.rc_addr, 0, sizeof(BD_ADDR)); /* report connection state */ esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.conn_stat.connected = false; memcpy(param.conn_stat.remote_bda, &rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } static void handle_rc_attributes_rsp ( tAVRC_MSG_VENDOR *vendor_msg) { uint8_t attr_count = vendor_msg->p_vendor_data[4]; int attr_index = 5; int attr_length = 0; uint32_t attr_id = 0; //Check if there are any attributes if (attr_count < 1) { return; } esp_avrc_ct_cb_param_t param[attr_count]; memset(¶m[0], 0, sizeof(esp_avrc_ct_cb_param_t) * attr_count); for (int i = 0; i < attr_count; i++) { attr_length = (int) vendor_msg->p_vendor_data[7 + attr_index] | vendor_msg->p_vendor_data[6 + attr_index] << 8; //Received attribute text is not null terminated, so it's useful to know it's length param[i].meta_rsp.attr_length = attr_length; param[i].meta_rsp.attr_text = &vendor_msg->p_vendor_data[8 + attr_index]; attr_id = vendor_msg->p_vendor_data[3 + attr_index] | vendor_msg->p_vendor_data[2 + attr_index] << 8 | vendor_msg->p_vendor_data[1 + attr_index] << 16 | vendor_msg->p_vendor_data[attr_index] << 24; //Convert to mask id param[i].meta_rsp.attr_id = (1 << (attr_id - 1)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_METADATA_RSP_EVT, ¶m[i]); attr_index += attr_length + 8; } } static void handle_rc_notification_rsp ( tAVRC_MSG_VENDOR *vendor_msg) { esp_avrc_ct_cb_param_t param; param.change_ntf.event_id = vendor_msg->p_vendor_data[4]; param.change_ntf.event_parameter = vendor_msg->p_vendor_data[5] << 24 | vendor_msg->p_vendor_data[6] << 16 | vendor_msg->p_vendor_data[7] << 8 | vendor_msg->p_vendor_data[8]; btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CHANGE_NOTIFY_EVT, ¶m); } /*************************************************************************** * Function handle_rc_metadata_rsp * * - Argument: tBTA_AV_META_MSG metadata command response * * - Description: Vendor metadata response handler * ***************************************************************************/ static void handle_rc_metadata_rsp ( tBTA_AV_META_MSG *p_remote_rsp) { #if (AVRC_METADATA_INCLUDED == TRUE) tAVRC_MSG *avrc_msg = p_remote_rsp->p_msg; tAVRC_MSG_VENDOR *vendor_msg = &avrc_msg->vendor; //Check what type of metadata was received switch (vendor_msg->hdr.ctype) { case AVRC_RSP_CHANGED: if (vendor_msg->p_vendor_data[0] == AVRC_PDU_REGISTER_NOTIFICATION) { handle_rc_notification_rsp(vendor_msg); } break; case AVRC_RSP_IMPL_STBL: if (vendor_msg->p_vendor_data[0] == AVRC_PDU_GET_ELEMENT_ATTR) { handle_rc_attributes_rsp(vendor_msg); } break; } #else BTC_TRACE_ERROR("%s AVRCP metadata is not enabled", __FUNCTION__); #endif } /*************************************************************************** * Function handle_rc_passthrough_rsp * * - Argument: tBTA_AV_REMOTE_RSP passthrough command response * * - Description: Remote control passthrough response handler * ***************************************************************************/ static void handle_rc_passthrough_rsp ( tBTA_AV_REMOTE_RSP *p_remote_rsp) { #if (AVRC_CTLR_INCLUDED == TRUE) const char *status; if (btc_rc_vb.rc_features & BTA_AV_FEAT_RCTG) { int key_state; if (p_remote_rsp->key_state == AVRC_STATE_RELEASE) { status = "released"; key_state = 1; } else { status = "pressed"; key_state = 0; } BTC_TRACE_DEBUG("%s: rc_id=%d status=%s", __FUNCTION__, p_remote_rsp->rc_id, status); do { esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.psth_rsp.tl = p_remote_rsp->label; param.psth_rsp.key_code = p_remote_rsp->rc_id; param.psth_rsp.key_state = key_state; btc_avrc_ct_cb_to_app(ESP_AVRC_CT_PASSTHROUGH_RSP_EVT, ¶m); } while (0); } else { BTC_TRACE_ERROR("%s DUT does not support AVRCP controller role", __FUNCTION__); } UNUSED(status); #else BTC_TRACE_ERROR("%s AVRCP controller role is not enabled", __FUNCTION__); #endif } /*************************************************************************** ** ** Function btc_rc_handler ** ** Description RC event handler ** ***************************************************************************/ void btc_rc_handler(tBTA_AV_EVT event, tBTA_AV *p_data) { BTC_TRACE_DEBUG ("%s event:%s", __FUNCTION__, dump_rc_event(event)); switch (event) { case BTA_AV_RC_OPEN_EVT: { BTC_TRACE_DEBUG("Peer_features:%x", p_data->rc_open.peer_features); handle_rc_connect( &(p_data->rc_open) ); } break; case BTA_AV_RC_CLOSE_EVT: { handle_rc_disconnect( &(p_data->rc_close) ); } break; #if (AVRC_CTLR_INCLUDED == TRUE) case BTA_AV_REMOTE_RSP_EVT: { BTC_TRACE_DEBUG("RSP: rc_id:0x%x key_state:%d", p_data->remote_rsp.rc_id, p_data->remote_rsp.key_state); handle_rc_passthrough_rsp( (&p_data->remote_rsp) ); } break; #endif case BTA_AV_RC_FEAT_EVT: { BTC_TRACE_DEBUG("Peer_features:%x", p_data->rc_feat.peer_features); btc_rc_vb.rc_features = p_data->rc_feat.peer_features; handle_rc_features(); } break; case BTA_AV_META_MSG_EVT: { handle_rc_metadata_rsp(&(p_data->meta_msg)); } break; // below events are not handled for now case BTA_AV_REMOTE_CMD_EVT: default: BTC_TRACE_DEBUG("Unhandled RC event : 0x%x", event); } } /*************************************************************************** ** ** Function btc_rc_get_connected_peer ** ** Description Fetches the connected headset's BD_ADDR if any ** ***************************************************************************/ BOOLEAN btc_rc_get_connected_peer(BD_ADDR peer_addr) { if (btc_rc_vb.rc_connected == TRUE) { bdcpy(peer_addr, btc_rc_vb.rc_addr); return TRUE; } return FALSE; } /************************************************************************************ ** AVRCP API Functions ************************************************************************************/ /******************************************************************************* ** ** Function btc_avrc_ct_init ** ** Description Initializes the AVRC interface ** ** Returns esp_err_t ** *******************************************************************************/ static void btc_avrc_ct_init(void) { BTC_TRACE_DEBUG("## %s ##", __FUNCTION__); memset (&btc_rc_vb, 0, sizeof(btc_rc_vb)); if (!g_av_with_rc) { g_av_with_rc = true; } if (g_a2dp_on_init) { BTC_TRACE_WARNING("AVRC Controller is expected to be initialized in advance of A2DP !!!"); } btc_rc_vb.rc_vol_label = MAX_LABEL; btc_rc_vb.rc_volume = MAX_VOLUME; } /*************************************************************************** ** ** Function cleanup_ctrl ** ** Description Closes the AVRC Controller interface ** ** Returns void ** ***************************************************************************/ static void btc_avrc_ct_deinit(void) { BTC_TRACE_API("## %s ##", __FUNCTION__); if (g_a2dp_on_deinit) { BTC_TRACE_WARNING("A2DP already deinit, AVRC CT shuold deinit in advance of A2DP !!!"); } if (g_av_with_rc) { g_av_with_rc = false; } memset(&btc_rc_vb, 0, sizeof(btc_rc_cb_t)); BTC_TRACE_API("## %s ## completed", __FUNCTION__); } static bt_status_t btc_avrc_ct_send_set_player_value_cmd(uint8_t tl, uint8_t attr_id, uint8_t value_id) { tAVRC_STS status = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; tAVRC_APP_SETTING values = {0}; values.attr_id = attr_id; values.attr_val = value_id; avrc_cmd.set_app_val.opcode = AVRC_OP_VENDOR; avrc_cmd.set_app_val.status = AVRC_STS_NO_ERROR; avrc_cmd.set_app_val.num_val = 1; avrc_cmd.set_app_val.p_vals = &values; avrc_cmd.set_app_val.pdu = AVRC_PDU_SET_PLAYER_APP_VALUE; status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { if (btc_rc_vb.rc_features & BTA_AV_FEAT_METADATA) { BTA_AvMetaCmd(btc_rc_vb.rc_handle, tl, BTA_AV_CMD_CTRL, p_msg); status = BT_STATUS_SUCCESS; } else { status = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } } #else BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif return status; } static bt_status_t btc_avrc_ct_send_register_notification_cmd(uint8_t tl, uint8_t event_id, uint32_t event_parameter) { tAVRC_STS status = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; avrc_cmd.reg_notif.opcode = AVRC_OP_VENDOR; avrc_cmd.reg_notif.status = AVRC_STS_NO_ERROR; avrc_cmd.reg_notif.event_id = event_id; avrc_cmd.reg_notif.param = event_parameter; avrc_cmd.reg_notif.pdu = AVRC_PDU_REGISTER_NOTIFICATION; status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { if (btc_rc_vb.rc_features & BTA_AV_FEAT_METADATA) { BTA_AvMetaCmd(btc_rc_vb.rc_handle, tl, AVRC_CMD_NOTIF, p_msg); status = BT_STATUS_SUCCESS; } else { status = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } } #else BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif return status; } static bt_status_t btc_avrc_ct_send_metadata_cmd (uint8_t tl, uint8_t attr_mask) { tAVRC_STS status = BT_STATUS_UNSUPPORTED; #if (AVRC_METADATA_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; uint32_t index = 0; tAVRC_COMMAND avrc_cmd = {0}; BT_HDR *p_msg = NULL; avrc_cmd.get_elem_attrs.opcode = AVRC_OP_VENDOR; avrc_cmd.get_elem_attrs.status = AVRC_STS_NO_ERROR; avrc_cmd.get_elem_attrs.pdu = AVRC_PDU_GET_ELEMENT_ATTR; for (int count = 0; count < AVRC_MAX_ELEM_ATTR_SIZE; count++) { if ((attr_mask & (1 << count)) > 0) { avrc_cmd.get_elem_attrs.attrs[index] = count + 1; index++; } } avrc_cmd.get_elem_attrs.num_attr = index; status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { if (btc_rc_vb.rc_features & BTA_AV_FEAT_METADATA) { BTA_AvMetaCmd(btc_rc_vb.rc_handle, tl, AVRC_CMD_STATUS, p_msg); status = BT_STATUS_SUCCESS; } else { status = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } } #else BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif return status; } static bt_status_t btc_avrc_ct_send_passthrough_cmd(uint8_t tl, uint8_t key_code, uint8_t key_state) { tAVRC_STS status = BT_STATUS_UNSUPPORTED; #if (AVRC_CTLR_INCLUDED == TRUE) CHECK_ESP_RC_CONNECTED; BTC_TRACE_DEBUG("%s: key-code: %d, key-state: %d", __FUNCTION__, key_code, key_state); if (btc_rc_vb.rc_features & BTA_AV_FEAT_RCTG) { BTA_AvRemoteCmd(btc_rc_vb.rc_handle, tl, (tBTA_AV_RC)key_code, (tBTA_AV_STATE)key_state); status = BT_STATUS_SUCCESS; BTC_TRACE_API("%s: succesfully sent passthrough command to BTA", __FUNCTION__); } else { status = BT_STATUS_FAIL; BTC_TRACE_DEBUG("%s: feature not supported", __FUNCTION__); } #else BTC_TRACE_DEBUG("%s: feature not enabled", __FUNCTION__); #endif return status; } void btc_avrc_call_handler(btc_msg_t *msg) { btc_avrc_args_t *arg = (btc_avrc_args_t *)(msg->arg); switch (msg->act) { case BTC_AVRC_CTRL_API_INIT_EVT: { btc_avrc_ct_init(); // todo: callback to application break; } case BTC_AVRC_CTRL_API_DEINIT_EVT: { btc_avrc_ct_deinit(); // todo: callback to application break; } case BTC_AVRC_CTRL_API_SND_PTCMD_EVT: { btc_avrc_ct_send_passthrough_cmd(arg->pt_cmd.tl, arg->pt_cmd.key_code, arg->pt_cmd.key_state); // todo: callback to application break; } case BTC_AVRC_STATUS_API_SND_META_EVT: { btc_avrc_ct_send_metadata_cmd(arg->md_cmd.tl, arg->md_cmd.attr_mask); break; } case BTC_AVRC_NOTIFY_API_SND_REG_NOTIFY_EVT: { btc_avrc_ct_send_register_notification_cmd(arg->rn_cmd.tl, arg->rn_cmd.event_id, arg->rn_cmd.event_parameter); break; } case BTC_AVRC_CTRL_API_SET_PLAYER_SETTING_EVT: { btc_avrc_ct_send_set_player_value_cmd(arg->ps_cmd.tl, arg->ps_cmd.attr_id, arg->ps_cmd.value_id); break; } default: BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } } #endif /* #if BTC_AV_INCLUDED */