// 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 static UINT8 opcode_from_pdu(UINT8 pdu); static void send_reject_response (UINT8 rc_handle, UINT8 label, UINT8 pdu, UINT8 status); 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_metamsg_cmd ( tBTA_AV_META_MSG *p_meta_msg); static void handle_rc_metamsg_rsp ( tBTA_AV_META_MSG *p_remote_rsp); static void btc_rc_upstreams_evt(UINT16 event, tAVRC_COMMAND *pavrc_cmd, UINT8 ctype, UINT8 label); /***************************************************************************** ** Static variables ******************************************************************************/ /* flag indicating wheter TG/CT is initialized */ static uint32_t s_rc_ct_init; static uint32_t s_rc_tg_init; #if AVRC_DYNAMIC_MEMORY == FALSE static btc_rc_cb_t btc_rc_cb; #else btc_rc_cb_t *btc_rc_cb_ptr; #endif ///AVRC_DYNAMIC_MEMORY == FALSE const static uint16_t cs_psth_allowed_cmd[8] = { 0x0000, /* bit mask: 0=SELECT, 1=UP, 2=DOWN, 3=LEFT, 4=RIGHT, 5=RIGHT_UP, 6=RIGHT_DOWN, 7=LEFT_UP, 8=LEFT_DOWN, 9=ROOT_MENU, 10=SETUP_MENU, 11=CONT_MENU, 12=FAV_MENU, 13=EXIT */ 0x0000, /* not used */ 0x1FFF, /* bit mask: 0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9, 10=DOT, 11=ENTER, 12=CLEAR */ 0x0078, /* bit mask: 0=CHAN_UP, 1=CHAN_DOWN, 2=PREV_CHAN, 3=SOUND_SEL, 4=INPUT_SEL, 5=DISP_INFO, 6=HELP, 7=PAGE_UP, 8=PAGE_DOWN */ 0x1b7F, /* bit mask: 0=POWER, 1=VOL_UP, 2=VOL_DOWN, 3=MUTE, 4=PLAY, 5=STOP, 6=PAUSE, 7=RECORD, 8=REWIND, 9=FAST_FOR, 10=EJECT, 11=FORWARD, 12=BACKWARD */ 0x0000, /* bit mask: 0=ANGLE, 1=SUBPICT */ 0x0000, /* not used */ 0x003E /* bit mask: 0=not used, 1=F1, 2=F2, 3=F3, 4=F4, 5=F5 */ }; const static uint16_t cs_psth_dft_supported_cmd[8] = {0}; static uint16_t s_psth_supported_cmd[8]; const static uint16_t cs_rn_allowed_evt = \ 0x2000; /* bit mask: 0=rsvd, 1=play_status, 2=track_chg, 3=reached_end, 4=reached_start, 5=pos_chg, 6=batt_status, 7=sys_status_chg, 8=player_app_setting, 9=now_playing_content, 10=avail_players, 11=addressed_player, 12=UID, 13=volume_chg */ const static uint16_t cs_rn_dft_supported_evt = 0; static uint16_t s_rn_supported_evt; // event ids supported to register as CT const static uint16_t cs_ct_rn_supported_evt = \ 0x2026; /* bit mask: 0=rsvd, 1=play_status, 2=track_chg, 3=reached_end, 4=reached_start, 5=pos_chg, 6=batt_status, 7=sys_status_chg, 8=player_app_setting, 9=now_playing_content, 10=avail_players, 11=addressed_player, 12=UID, 13=volume_chg */ /***************************************************************************** ** Externs ******************************************************************************/ const uint16_t *btc_avrc_tg_get_allowed_command(void) { return cs_psth_allowed_cmd; } const uint16_t *btc_avrc_tg_get_supported_command(void) { return s_psth_supported_cmd; } bool btc_avrc_tg_check_supported_command(const uint16_t *cmd_set) { if (cmd_set == NULL) { return false; } // check if cmd_set is a subset of allowed command set bool ret = true; for (int i = 0; i < 8; ++i) { if (cs_psth_allowed_cmd[i] != (cmd_set[i] | cs_psth_allowed_cmd[i])) { ret = false; break; } } return ret; } uint16_t btc_avrc_tg_get_rn_allowed_evt(void) { return cs_rn_allowed_evt; } uint16_t btc_avrc_tg_get_rn_supported_evt(void) { return s_rn_supported_evt; } bool btc_avrc_tg_check_rn_supported_evt(uint16_t evt_set) { uint16_t evt_super_set = evt_set | cs_rn_allowed_evt; if (evt_super_set == cs_rn_allowed_evt) { return true; } else { return false; } } bool btc_avrc_tg_rn_evt_supported(uint8_t event_id) { uint16_t event_bits = s_rn_supported_evt; return ((event_bits >> event_id) & 0x0001) ? true : false; } bool btc_avrc_ct_rn_evt_supported(uint8_t event_id) { uint16_t event_bits = cs_ct_rn_supported_evt; return ((event_bits >> event_id) & 0x0001) ? true : false; } bool btc_avrc_tg_init_p(void) { return (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC); } bool btc_avrc_ct_init_p(void) { return (s_rc_ct_init == BTC_RC_CT_INIT_MAGIC); } bool btc_avrc_tg_connected_p(void) { return (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC) && (btc_rc_cb.rc_connected == TRUE) && (btc_rc_cb.rc_features & BTA_AV_FEAT_RCCT); } bool btc_avrc_ct_connected_p(void) { return (s_rc_ct_init == BTC_RC_CT_INIT_MAGIC) && (btc_rc_cb.rc_connected == TRUE) && (btc_rc_cb.rc_features & BTA_AV_FEAT_RCTG); } 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 *src = (btc_avrc_tg_args_t *)p_src; size_t len; switch (msg->act) { case BTC_AVRC_TG_API_SET_PSTH_SUPPORTED_CMD_EVT: len = 8 * sizeof(uint16_t); dst->set_psth_cmd = (uint16_t *)osi_malloc(len); if (dst->set_psth_cmd) { memcpy(dst->set_psth_cmd, src->set_psth_cmd, len); } else { BTC_TRACE_ERROR("%s %d no mem\n", __FUNCTION__, msg->act); } break; default: BTC_TRACE_DEBUG("%s Unhandled deep copy %d\n", __FUNCTION__, msg->act); break; } } /***************************************************************************** ** Static functions ******************************************************************************/ static void btc_avrc_tg_arg_deep_free(btc_msg_t *msg) { btc_avrc_tg_args_t *arg = (btc_avrc_tg_args_t *)msg->arg; switch (msg->act) { case BTC_AVRC_TG_API_SET_PSTH_SUPPORTED_CMD_EVT: if (arg->set_psth_cmd) { osi_free(arg->set_psth_cmd); } break; default: BTC_TRACE_DEBUG("%s Unhandled deep free %d\n", __FUNCTION__, msg->act); break; } } static bool btc_avrc_tg_set_supported_command(const uint16_t *cmd_set) { if (!btc_avrc_tg_init_p()) { BTC_TRACE_WARNING("%s failed: AVRC TG not yet initialized\n", __FUNCTION__); return false; } if (!btc_avrc_tg_check_supported_command(cmd_set)) { return false; } else { memcpy(s_psth_supported_cmd, cmd_set, sizeof(s_psth_supported_cmd)); return true; } } static bool btc_avrc_tg_set_rn_supported_evt(uint16_t evt_set) { if (!btc_avrc_tg_init_p()) { BTC_TRACE_WARNING("%s failed: AVRC TG not yet initialized\n", __FUNCTION__); return false; } if (btc_avrc_tg_check_rn_supported_evt(evt_set)) { s_rn_supported_evt = evt_set; return true; } else { return false; } } static inline void btc_avrc_ct_cb_to_app(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) { if (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) { return; } esp_avrc_ct_cb_t btc_avrc_ct_cb = (esp_avrc_ct_cb_t)btc_profile_cb_get(BTC_PID_AVRC_CT); if (btc_avrc_ct_cb) { btc_avrc_ct_cb(event, param); } } static inline void btc_avrc_tg_cb_to_app(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) { if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) { return; } esp_avrc_tg_cb_t btc_avrc_tg_cb = (esp_avrc_tg_cb_t)btc_profile_cb_get(BTC_PID_AVRC_TG); if (btc_avrc_tg_cb) { btc_avrc_tg_cb(event, param); } } /*************************************************************************** * Function send_metamsg_rsp * * - Argument: * rc_handle RC handle corresponding to the connected RC * label Label of the RC response * code Response type * p_meta_rsp Vendor response * * - Description: Remote control metamsg response handler (AVRCP 1.3) * ***************************************************************************/ static void send_metamsg_rsp (UINT8 rc_handle, UINT8 label, tBTA_AV_CODE code, tAVRC_RESPONSE *p_meta_rsp) { UINT8 ctype; if (!p_meta_rsp) { BTC_TRACE_WARNING("%s: Invalid response received from application", __FUNCTION__); return; } BTC_TRACE_EVENT("%s: rc_handle: %d, label: %d, code: 0x%02x, pdu: %d", __FUNCTION__, rc_handle, label, code, p_meta_rsp->rsp.pdu); if (p_meta_rsp->rsp.status != AVRC_STS_NO_ERROR) { ctype = AVRC_RSP_REJ; } else { if (code < AVRC_RSP_NOT_IMPL) { if (code == AVRC_CMD_NOTIF) { ctype = AVRC_RSP_INTERIM; } else if (code == AVRC_CMD_STATUS) { ctype = AVRC_RSP_IMPL_STBL; } else { ctype = AVRC_RSP_ACCEPT; } } else { ctype = code; } } /* if response is for register_notification, make sure the rc has actually registered for this */ if ((p_meta_rsp->rsp.pdu == AVRC_PDU_REGISTER_NOTIFICATION) && (code == AVRC_RSP_CHANGED)) { UINT8 event_id = p_meta_rsp->reg_notif.event_id; BOOLEAN notify = (btc_rc_cb.rc_connected) && (btc_rc_cb.rc_ntf[event_id - 1].registered); /* de-register this notification for a CHANGED response */ btc_rc_cb.rc_ntf[event_id - 1].registered = FALSE; BTC_TRACE_DEBUG("%s rc_handle: %d. event_id: 0x%02d deregistered", __FUNCTION__, btc_rc_cb.rc_handle, event_id); if (notify) { BT_HDR *p_msg = NULL; tAVRC_STS status = AVRC_BldResponse(btc_rc_cb.rc_handle, p_meta_rsp, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTC_TRACE_DEBUG("%s Sending notification to rc_handle: %d. event_id: 0x%02d", __FUNCTION__, btc_rc_cb.rc_handle, event_id); BTA_AvMetaRsp(btc_rc_cb.rc_handle, btc_rc_cb.rc_ntf[event_id - 1].label, ctype, p_msg); } else { BTC_TRACE_WARNING("%s failed to build metamsg response. status: 0x%02x", __FUNCTION__, status); } } } else { // send response BT_HDR *p_msg = NULL; tAVRC_STS status; status = AVRC_BldResponse(rc_handle, p_meta_rsp, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTA_AvMetaRsp(rc_handle, label, ctype, p_msg); } else { BTC_TRACE_ERROR("%s: failed to build metamsg response. status: 0x%02x", __FUNCTION__, status); } } } static UINT8 opcode_from_pdu(UINT8 pdu) { UINT8 opcode = 0; switch (pdu) { case AVRC_PDU_NEXT_GROUP: case AVRC_PDU_PREV_GROUP: /* pass thru */ opcode = AVRC_OP_PASS_THRU; break; default: /* vendor */ opcode = AVRC_OP_VENDOR; break; } return opcode; } /* Generic reject response */ static void send_reject_response (UINT8 rc_handle, UINT8 label, UINT8 pdu, UINT8 status) { UINT8 ctype = AVRC_RSP_REJ; tAVRC_RESPONSE avrc_rsp; BT_HDR *p_msg = NULL; memset (&avrc_rsp, 0, sizeof(tAVRC_RESPONSE)); avrc_rsp.rsp.opcode = opcode_from_pdu(pdu); avrc_rsp.rsp.pdu = pdu; avrc_rsp.rsp.status = status; if (AVRC_STS_NO_ERROR == (status = AVRC_BldResponse(rc_handle, &avrc_rsp, &p_msg)) ) { BTC_TRACE_DEBUG("%s: Sending error notification to handle:%d. pdu:%s,status:0x%02x", __FUNCTION__, rc_handle, dump_rc_pdu(pdu), status); BTA_AvMetaRsp(rc_handle, label, ctype, p_msg); } } static void handle_rc_features(void) { if ((btc_rc_cb.rc_features & BTA_AV_FEAT_RCTG) && (btc_rc_cb.rc_tg_features != 0)) { esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.rmt_feats.feat_mask = btc_rc_cb.rc_features; param.rmt_feats.tg_feat_flag = btc_rc_cb.rc_tg_features; memcpy(param.rmt_feats.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_REMOTE_FEATURES_EVT, ¶m); } if ((btc_rc_cb.rc_features & BTA_AV_FEAT_RCCT) && (btc_rc_cb.rc_ct_features != 0)) { esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_tg_cb_param_t)); param.rmt_feats.feat_mask = btc_rc_cb.rc_features; param.rmt_feats.ct_feat_flag = btc_rc_cb.rc_ct_features; memcpy(param.rmt_feats.remote_bda, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_tg_cb_to_app(ESP_AVRC_TG_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_cb.rc_connected) { BTC_TRACE_ERROR("Got RC OPEN in connected state, Connected RC: %d \ and Current RC: %d", btc_rc_cb.rc_handle, p_rc_open->rc_handle ); if ((btc_rc_cb.rc_handle != p_rc_open->rc_handle) && (bdcmp(btc_rc_cb.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_cb.rc_addr, p_rc_open->peer_addr, sizeof(BD_ADDR)); btc_rc_cb.rc_features = p_rc_open->peer_features; btc_rc_cb.rc_connected = TRUE; btc_rc_cb.rc_handle = p_rc_open->rc_handle; bdcpy(rc_addr.address, btc_rc_cb.rc_addr); // callback to application if (p_rc_open->peer_features & BTA_AV_FEAT_RCTG) { 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); } if (p_rc_open->peer_features & BTA_AV_FEAT_RCCT) { esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_tg_cb_param_t)); param.conn_stat.connected = true; memcpy(param.conn_stat.remote_bda, &rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_tg_cb_to_app(ESP_AVRC_TG_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) { btc_rc_cb.rc_ct_features = p_rc_open->peer_ct_features; btc_rc_cb.rc_tg_features = p_rc_open->peer_tg_features; handle_rc_features(); } } else { BTC_TRACE_ERROR("%s Connect failed with error code: %d", __FUNCTION__, p_rc_open->status); btc_rc_cb.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) { BTC_TRACE_DEBUG("%s: rc_handle: %d", __FUNCTION__, p_rc_close->rc_handle); if ((p_rc_close->rc_handle != btc_rc_cb.rc_handle) && (bdcmp(btc_rc_cb.rc_addr, p_rc_close->peer_addr))) { BTC_TRACE_ERROR("Got disconnect of unknown device"); return; } tBTA_AV_FEAT rc_features = btc_rc_cb.rc_features; // clean up the state btc_rc_cb.rc_handle = 0; btc_rc_cb.rc_connected = FALSE; btc_rc_cb.rc_features = 0; btc_rc_cb.rc_ct_features = 0; btc_rc_cb.rc_tg_features = 0; memset(btc_rc_cb.rc_addr, 0, sizeof(BD_ADDR)); memset(btc_rc_cb.rc_ntf, 0, sizeof(btc_rc_cb.rc_ntf)); /* report connection state */ if (rc_features & BTA_AV_FEAT_RCTG) { 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, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } if (rc_features & BTA_AV_FEAT_RCCT) { esp_avrc_tg_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, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_tg_cb_to_app(ESP_AVRC_TG_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; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.change_ntf.event_id = vendor_msg->p_vendor_data[4]; uint8_t *data = &vendor_msg->p_vendor_data[5]; if (!btc_avrc_ct_rn_evt_supported(param.change_ntf.event_id)) { BTC_TRACE_WARNING("%s unsupported notification on CT, event id 0x%x", __FUNCTION__, param.change_ntf.event_id); return; } switch (param.change_ntf.event_id) { case ESP_AVRC_RN_PLAY_STATUS_CHANGE: BE_STREAM_TO_UINT8(param.change_ntf.event_parameter.playback, data); break; case ESP_AVRC_RN_TRACK_CHANGE: memcpy(param.change_ntf.event_parameter.elm_id, data, 8); break; case ESP_AVRC_RN_PLAY_POS_CHANGED: BE_STREAM_TO_UINT32(param.change_ntf.event_parameter.play_pos, data); break; case ESP_AVRC_RN_BATTERY_STATUS_CHANGE: BE_STREAM_TO_UINT8(param.change_ntf.event_parameter.batt, data); break; case ESP_AVRC_RN_VOLUME_CHANGE: BE_STREAM_TO_UINT8(param.change_ntf.event_parameter.volume, data); break; // for non-parameter event response case ESP_AVRC_RN_TRACK_REACHED_END: case ESP_AVRC_RN_TRACK_REACHED_START: break; // for other unsupported event: case ESP_AVRC_RN_SYSTEM_STATUS_CHANGE: case ESP_AVRC_RN_APP_SETTING_CHANGE: case ESP_AVRC_RN_NOW_PLAYING_CHANGE: case ESP_AVRC_RN_AVAILABLE_PLAYERS_CHANGE: case ESP_AVRC_RN_ADDRESSED_PLAYER_CHANGE: case ESP_AVRC_RN_UIDS_CHANGE: default: BTC_TRACE_WARNING("%s RC unhandled notification response, event id 0x%x", __FUNCTION__, param.change_ntf.event_id); break; } btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CHANGE_NOTIFY_EVT, ¶m); } static void handle_rc_get_caps_rsp (tAVRC_GET_CAPS_RSP *rsp) { if (rsp->capability_id == AVRC_CAP_EVENTS_SUPPORTED) { esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.get_rn_caps_rsp.cap_count = rsp->count; for (int i = 0; i < rsp->count; ++i) { esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, ¶m.get_rn_caps_rsp.evt_set, rsp->param.event_id[i]); } btc_avrc_ct_cb_to_app(ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT, ¶m); } else { // ignore other capability_id BTC_TRACE_WARNING("AVRC unhandled event, CapabilityID: 0x%x", rsp->capability_id); return; } } static void handle_rc_set_absolute_volume_rsp(tAVRC_SET_VOLUME_RSP *rsp) { esp_avrc_ct_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_ct_cb_param_t)); param.set_volume_rsp.volume = rsp->volume; btc_avrc_ct_cb_to_app(ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT, ¶m); } /*************************************************************************** * Function handle_rc_metamsg_cmd * * - Argument: tBTA_AV_VENDOR Structure containing the received * metamsg command * * - Description: Remote control metamsg command handler (AVRCP 1.3) * ***************************************************************************/ static void handle_rc_metamsg_cmd (tBTA_AV_META_MSG *p_meta_msg) { BTC_TRACE_DEBUG("%s, opcode 0x%x, len %d, code %d", __FUNCTION__, p_meta_msg->p_msg->hdr.opcode, p_meta_msg->len, p_meta_msg->code); if (p_meta_msg->p_msg->hdr.opcode != AVRC_OP_VENDOR) { BTC_TRACE_WARNING("Invalid opcode: %x", p_meta_msg->p_msg->hdr.opcode); return; } if (p_meta_msg->len < 3) { BTC_TRACE_WARNING("Invalid length.Opcode: 0x%x, len: 0x%x", p_meta_msg->p_msg->hdr.opcode, p_meta_msg->len); return; } if (p_meta_msg->code >= AVRC_RSP_NOT_IMPL) { #if (AVRC_ADV_CTRL_INCLUDED == TRUE) handle_rc_metamsg_rsp(p_meta_msg); #else BTC_TRACE_WARNING("%s:Received vendor dependent rsp. code: %d len: %d. Not processing it.", __FUNCTION__, p_meta_msg->code, p_meta_msg->len); #endif return; } #define RC_CMD_PRS_BUF_LEN (512) uint8_t *buf = (uint8_t *)osi_calloc(RC_CMD_PRS_BUF_LEN); tAVRC_COMMAND avrc_command = {0}; tAVRC_STS status; status = AVRC_ParsCommand(p_meta_msg->p_msg, &avrc_command, buf, RC_CMD_PRS_BUF_LEN); BTC_TRACE_DEBUG("Rcv vendor cmd: code %d, PDU 0x%x, label %d", p_meta_msg->code, avrc_command.cmd.pdu, p_meta_msg->label); if (status != AVRC_STS_NO_ERROR) { /* return error */ BTC_TRACE_WARNING("%s: Error in parsing vendor command. status: 0x%02x", __FUNCTION__, status); send_reject_response(p_meta_msg->rc_handle, p_meta_msg->label, avrc_command.pdu, status); } else { btc_rc_upstreams_evt(avrc_command.cmd.pdu, &avrc_command, p_meta_msg->code, p_meta_msg->label); } osi_free(buf); } /******************************************************************************* ** ** Function btc_rc_upstreams_evt ** ** Description Executes AVRC UPSTREAMS events in btc context. ** ** Returns void ** *******************************************************************************/ static void btc_rc_upstreams_evt(UINT16 event, tAVRC_COMMAND *pavrc_cmd, UINT8 ctype, UINT8 label) { BTC_TRACE_EVENT("%s pdu: 0x%x handle: 0x%x ctype:%x label:%x", __FUNCTION__, pavrc_cmd->pdu, btc_rc_cb.rc_handle, ctype, label); switch (event) { case AVRC_PDU_SET_ABSOLUTE_VOLUME: { // set up response tAVRC_RESPONSE avrc_rsp; BTC_TRACE_EVENT("%s() AVRC_PDU_SET_ABSOLUTE_VOLUME", __FUNCTION__); memset(&(avrc_rsp.volume), 0, sizeof(tAVRC_RSP)); avrc_rsp.volume.opcode = opcode_from_pdu(AVRC_PDU_SET_ABSOLUTE_VOLUME); avrc_rsp.volume.pdu = AVRC_PDU_SET_ABSOLUTE_VOLUME; avrc_rsp.volume.status = AVRC_STS_NO_ERROR; avrc_rsp.volume.volume = pavrc_cmd->volume.volume; send_metamsg_rsp(btc_rc_cb.rc_handle, label, ctype, &avrc_rsp); // set up callback esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_tg_cb_param_t)); param.set_abs_vol.volume = pavrc_cmd->volume.volume; btc_avrc_tg_cb_to_app(ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT, ¶m); } break; case AVRC_PDU_SET_PLAYER_APP_VALUE: { // set up callback esp_avrc_tg_cb_param_t param; param.set_app_value.num_val = pavrc_cmd->set_app_val.num_val; param.set_app_value.p_vals = (esp_avrc_set_app_value_param_t *)pavrc_cmd->set_app_val.p_vals; btc_avrc_tg_cb_to_app(ESP_AVRC_TG_SET_PLAYER_APP_VALUE_EVT, ¶m); } break; case AVRC_PDU_GET_PLAY_STATUS: case AVRC_PDU_GET_ELEMENT_ATTR: case AVRC_PDU_INFORM_DISPLAY_CHARSET: //todo: check the valid response for these PDUs case AVRC_PDU_LIST_PLAYER_APP_ATTR: case AVRC_PDU_LIST_PLAYER_APP_VALUES: case AVRC_PDU_GET_CUR_PLAYER_APP_VALUE: case AVRC_PDU_GET_PLAYER_APP_ATTR_TEXT: case AVRC_PDU_GET_PLAYER_APP_VALUE_TEXT: { send_reject_response (btc_rc_cb.rc_handle, label, pavrc_cmd->pdu, AVRC_STS_BAD_CMD); } break; case AVRC_PDU_REGISTER_NOTIFICATION: { UINT8 event_id = pavrc_cmd->reg_notif.event_id; if (event_id > MAX_RC_NOTIFICATIONS) { BTC_TRACE_WARNING("Invalid event_id: 0x%x", event_id); break; } btc_rc_cb.rc_ntf[event_id - 1].registered = TRUE; btc_rc_cb.rc_ntf[event_id - 1].label = label; BTC_TRACE_EVENT("%s: New registerd notification: event_id:0x%x, label:0x%x", __FUNCTION__, event_id, label); // set up callback esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_tg_cb_param_t)); param.reg_ntf.event_id = pavrc_cmd->reg_notif.event_id; param.reg_ntf.event_parameter = pavrc_cmd->reg_notif.param; btc_avrc_tg_cb_to_app(ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT, ¶m); } break; default: { send_reject_response (btc_rc_cb.rc_handle, label, pavrc_cmd->pdu, (pavrc_cmd->pdu == AVRC_PDU_SEARCH) ? AVRC_STS_SEARCH_NOT_SUP : AVRC_STS_BAD_CMD); } break; } return; } /*************************************************************************** * Function handle_rc_metamsg_rsp * * - Argument: tBTA_AV_META_MSG metadata command response * * - Description: Vendor metadata response handler * ***************************************************************************/ static void handle_rc_metamsg_rsp (tBTA_AV_META_MSG *p_meta_msg) { tAVRC_RESPONSE avrc_response = {0}; tAVRC_STS status; tAVRC_MSG_VENDOR *vendor_msg = &p_meta_msg->p_msg->vendor; BTC_TRACE_DEBUG("%s: opcode %d, pdu 0x%x, code %d", __FUNCTION__, p_meta_msg->p_msg->hdr.opcode, vendor_msg->p_vendor_data[0], p_meta_msg->code); if ( p_meta_msg->p_msg->hdr.opcode != AVRC_OP_VENDOR) { return; } if (p_meta_msg->code != AVRC_RSP_CHANGED && p_meta_msg->code != AVRC_RSP_INTERIM && p_meta_msg->code != AVRC_RSP_ACCEPT && p_meta_msg->code != AVRC_RSP_REJ && p_meta_msg->code != AVRC_RSP_NOT_IMPL && p_meta_msg->code != AVRC_RSP_IMPL_STBL) { return; } // handle GET_ELEMENT_ATTR response if (p_meta_msg->code == AVRC_RSP_IMPL_STBL && vendor_msg->p_vendor_data[0] == AVRC_PDU_GET_ELEMENT_ATTR) { handle_rc_attributes_rsp(vendor_msg); return; } status = AVRC_ParsResponse(p_meta_msg->p_msg, &avrc_response); if (status != AVRC_STS_NO_ERROR) { BTC_TRACE_WARNING("%s: code %d error 0x%x", __FUNCTION__, p_meta_msg->code, status); return; } tAVRC_MSG *avrc_msg = p_meta_msg->p_msg; vendor_msg = &avrc_msg->vendor; switch (avrc_response.rsp.pdu) { case AVRC_PDU_REGISTER_NOTIFICATION: if (vendor_msg->hdr.ctype == AVRC_RSP_CHANGED) { handle_rc_notification_rsp(vendor_msg); } else if (vendor_msg->hdr.ctype == AVRC_RSP_INTERIM) { // ignore this response } break; case AVRC_PDU_GET_ELEMENT_ATTR: // todo: handle this PDU here break; case AVRC_PDU_GET_CAPABILITIES: if (vendor_msg->hdr.ctype == AVRC_RSP_IMPL_STBL) { handle_rc_get_caps_rsp(&avrc_response.get_caps); } break; case AVRC_PDU_SET_ABSOLUTE_VOLUME: if (vendor_msg->hdr.ctype == AVRC_RSP_ACCEPT) { handle_rc_set_absolute_volume_rsp(&avrc_response.volume); } break; default: BTC_TRACE_WARNING("%s: unhandled meta rsp: pdu 0x%x", __FUNCTION__, avrc_response.rsp.pdu); } } /*************************************************************************** * 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_cb.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 handle_rc_passthrough_cmd * * - Argument: tBTA_AV_RC rc_id remote control command ID * tBTA_AV_STATE key_state status of key press * * - Description: Remote control command handler * ***************************************************************************/ void handle_rc_passthrough_cmd ( tBTA_AV_REMOTE_CMD *p_remote_cmd) { esp_avrc_tg_cb_param_t param; memset(¶m, 0, sizeof(esp_avrc_tg_cb_param_t)); param.psth_cmd.key_code = p_remote_cmd->rc_id; param.psth_cmd.key_state = p_remote_cmd->key_state; btc_avrc_tg_cb_to_app(ESP_AVRC_TG_PASSTHROUGH_CMD_EVT, ¶m); } /*************************************************************************** ** ** 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("RC open, 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); do { // report connection state if connection state wasn't reported on BTA_AV_RC_OPEN_EVT tBTA_AV_FEAT old_feats = btc_rc_cb.rc_features; if ((p_data->rc_feat.peer_features & BTA_AV_FEAT_RCTG) && !(old_feats & BTA_AV_FEAT_RCTG)) { 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, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_ct_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } if ((p_data->rc_feat.peer_features & BTA_AV_FEAT_RCCT) && !(old_feats & BTA_AV_FEAT_RCCT)) { esp_avrc_tg_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, btc_rc_cb.rc_addr, sizeof(esp_bd_addr_t)); btc_avrc_tg_cb_to_app(ESP_AVRC_CT_CONNECTION_STATE_EVT, ¶m); } } while (0); btc_rc_cb.rc_features = p_data->rc_feat.peer_features; btc_rc_cb.rc_ct_features = p_data->rc_feat.peer_ct_features; btc_rc_cb.rc_tg_features = p_data->rc_feat.peer_tg_features; handle_rc_features(); } break; case BTA_AV_META_MSG_EVT: { handle_rc_metamsg_cmd(&(p_data->meta_msg)); } break; case BTA_AV_REMOTE_CMD_EVT: { BTC_TRACE_DEBUG("rc_id:0x%x key_state:%d", p_data->remote_cmd.rc_id, p_data->remote_cmd.key_state); handle_rc_passthrough_cmd(&p_data->remote_cmd); } break; 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_cb.rc_connected == TRUE) { bdcpy(peer_addr, btc_rc_cb.rc_addr); return TRUE; } return FALSE; } /************************************************************************************ ** AVRCP API Functions ************************************************************************************/ /******************************************************************************* ** ** Function btc_avrc_ct_init ** ** Description Initializes the AVRC Controller interface ** ** Returns esp_err_t ** *******************************************************************************/ static void btc_avrc_ct_init(void) { BTC_TRACE_DEBUG("## %s ##", __FUNCTION__); if (s_rc_ct_init == BTC_RC_CT_INIT_MAGIC) { BTC_TRACE_WARNING("%s already initialized", __FUNCTION__); return; } /// initialize CT-specific resources s_rc_ct_init = BTC_RC_CT_INIT_MAGIC; /// initialize CT-TG shared resources if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) { memset (&btc_rc_cb, 0, sizeof(btc_rc_cb_t)); 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 !!!"); } } } /*************************************************************************** ** ** Function btc_avrc_ct_deinit ** ** 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 (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) { BTC_TRACE_WARNING("%s not initialized", __FUNCTION__); return; } /// deinit CT-specific resources s_rc_ct_init = 0; /// deinit CT-TG shared resources if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) { memset (&btc_rc_cb, 0, sizeof(btc_rc_cb_t)); if (g_av_with_rc) { g_av_with_rc = false; } } 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; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.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_get_rn_caps_cmd(uint8_t tl) { 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.get_caps.opcode = AVRC_OP_VENDOR; avrc_cmd.get_caps.status = AVRC_STS_NO_ERROR; avrc_cmd.get_caps.pdu = AVRC_PDU_GET_CAPABILITIES; avrc_cmd.get_caps.capability_id = AVRC_CAP_EVENTS_SUPPORTED; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.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_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; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.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_set_absolute_volume_cmd(uint8_t tl, uint8_t volume) { 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.volume.opcode = AVRC_OP_VENDOR; avrc_cmd.volume.status = AVRC_STS_NO_ERROR; avrc_cmd.volume.volume = volume; avrc_cmd.volume.pdu = AVRC_PDU_SET_ABSOLUTE_VOLUME; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.rc_handle, tl, AVRC_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_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; if (btc_rc_cb.rc_features & BTA_AV_FEAT_METADATA) { status = AVRC_BldCommand(&avrc_cmd, &p_msg); if (status == AVRC_STS_NO_ERROR) { BTA_AvMetaCmd(btc_rc_cb.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_cb.rc_features & BTA_AV_FEAT_RCTG) { BTA_AvRemoteCmd(btc_rc_cb.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; } /******************************************************************************* ** ** Function btc_avrc_tg_init ** ** Description Initializes the AVRC Target interface ** ** Returns esp_err_t ** *******************************************************************************/ static void btc_avrc_tg_init(void) { BTC_TRACE_DEBUG("## %s ##", __FUNCTION__); if (s_rc_tg_init == BTC_RC_TG_INIT_MAGIC) { BTC_TRACE_WARNING("%s already initialized", __FUNCTION__); return; } /// initialize TG-specific resources memcpy(s_psth_supported_cmd, cs_psth_dft_supported_cmd, sizeof(s_psth_supported_cmd)); s_rn_supported_evt = cs_rn_dft_supported_evt; /// initialize CT-TG shared resources if (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) { memset (&btc_rc_cb, 0, sizeof(btc_rc_cb)); if (!g_av_with_rc) { g_av_with_rc = true; } if (g_a2dp_on_init) { BTC_TRACE_WARNING("AVRC Taget is expected to be initialized in advance of A2DP !!!"); } } s_rc_tg_init = BTC_RC_TG_INIT_MAGIC; } /*************************************************************************** ** ** Function btc_avrc_tg_deinit ** ** Description Closes the AVRC Target interface ** ** Returns void ** ***************************************************************************/ static void btc_avrc_tg_deinit(void) { BTC_TRACE_API("## %s ##", __FUNCTION__); if (g_a2dp_on_deinit) { BTC_TRACE_WARNING("A2DP already deinit, AVRC TG shuold deinit in advance of A2DP !!!"); } if (s_rc_tg_init != BTC_RC_TG_INIT_MAGIC) { BTC_TRACE_WARNING("%s not initialized", __FUNCTION__); return; } /// deinit TG-specific resources memset(s_psth_supported_cmd, 0, sizeof(s_psth_supported_cmd)); s_rn_supported_evt = 0; s_rc_tg_init = 0; /// deinit CT-TG shared resources if (s_rc_ct_init != BTC_RC_CT_INIT_MAGIC) { memset (&btc_rc_cb, 0, sizeof(btc_rc_cb)); if (g_av_with_rc) { g_av_with_rc = false; } } BTC_TRACE_API("## %s ## completed", __FUNCTION__); } static void btc_avrc_tg_send_rn_rsp(esp_avrc_rn_event_ids_t event_id, esp_avrc_rn_rsp_t rsp, const esp_avrc_rn_param_t *param) { tAVRC_RESPONSE avrc_rsp; if (! btc_avrc_tg_connected_p()) { BTC_TRACE_WARNING("%s, RC unconnected, operation fail, event_id 0x%x", __FUNCTION__, event_id); return; } if (btc_rc_cb.rc_ntf[event_id - 1].registered == FALSE) { BTC_TRACE_ERROR("Event id not registered: event_id = %x", event_id); return; } memset(&(avrc_rsp.reg_notif), 0, sizeof(tAVRC_REG_NOTIF_RSP)); avrc_rsp.reg_notif.event_id = event_id; switch (event_id) { case ESP_AVRC_RN_VOLUME_CHANGE: avrc_rsp.reg_notif.param.volume = param->volume; break; // todo: implement other event notifications default: BTC_TRACE_WARNING("%s : Unhandled event ID : 0x%x", __FUNCTION__, event_id); return; } avrc_rsp.reg_notif.pdu = AVRC_PDU_REGISTER_NOTIFICATION; avrc_rsp.reg_notif.opcode = opcode_from_pdu(AVRC_PDU_REGISTER_NOTIFICATION); avrc_rsp.get_play_status.status = AVRC_STS_NO_ERROR; /* Send the response. */ send_metamsg_rsp(btc_rc_cb.rc_handle, btc_rc_cb.rc_ntf[event_id - 1].label, ((rsp == ESP_AVRC_RN_RSP_INTERIM) ? AVRC_CMD_NOTIF : AVRC_RSP_CHANGED), &avrc_rsp); return; } void btc_avrc_ct_call_handler(btc_msg_t *msg) { btc_avrc_args_t *arg = (btc_avrc_args_t *)(msg->arg); switch (msg->act) { case BTC_AVRC_CT_API_INIT_EVT: { btc_avrc_ct_init(); // todo: callback to application break; } case BTC_AVRC_CT_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_STATUS_API_SND_GET_RN_CAPS_EVT: { btc_avrc_ct_send_get_rn_caps_cmd(arg->get_caps_cmd.tl); 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_SND_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; } case BTC_AVRC_CTRL_API_SND_SET_ABSOLUTE_VOLUME_EVT: { btc_avrc_ct_send_set_absolute_volume_cmd(arg->set_abs_vol_cmd.tl, arg->set_abs_vol_cmd.volume); break; } default: BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } } void btc_avrc_tg_call_handler(btc_msg_t *msg) { btc_avrc_tg_args_t *arg = (btc_avrc_tg_args_t *)(msg->arg); switch (msg->act) { case BTC_AVRC_TG_API_INIT_EVT: { btc_avrc_tg_init(); break; } case BTC_AVRC_TG_API_DEINIT_EVT: { btc_avrc_tg_deinit(); break; } case BTC_AVRC_TG_API_SET_RN_SUPPORTED_EVT: { btc_avrc_tg_set_rn_supported_evt(arg->set_rn_evt); break; } case BTC_AVRC_TG_API_SET_PSTH_SUPPORTED_CMD_EVT: { btc_avrc_tg_set_supported_command(arg->set_psth_cmd); break; } case BTC_AVRC_TG_API_SEND_RN_RSP_EVT: { btc_avrc_tg_send_rn_rsp(arg->rn_rsp.event_id, arg->rn_rsp.rsp, &arg->rn_rsp.param); break; } default: BTC_TRACE_WARNING("%s : unhandled event: %d\n", __FUNCTION__, msg->act); } btc_avrc_tg_arg_deep_free(msg); } #endif /* #if BTC_AV_INCLUDED */