HFP
The purpose of this document is to provide an overview of the HFP HF and HSP HS applications based on the HFP/HSP profile. HFP defines how two devices supporting the Hands-Free Profile shall interact with each other on a point-to-point basis. The most common examples of such devices are in-car Hands-Free units used together with cellular phones, or wearable wireless headsets.
Terminology and Concepts
HFP/HSP Roles
Audio Gateway (AG) - This is the device that is the gateway of the audio, both for input and output. Typical devices acting as Audio Gateways are cellular phones.
Hands-Free unit (HF) - This is the device acting as the Audio Gateway’s remote audio input and output mechanism. It also provides some remote control means.
Audio Gateway (AG) - This device acts as either the end point, or a relay, for telephony quality audio, which is typically two-way. Exemplary devices acting as Audio Gateways are cellular phones and personal computers.
Headset (HS) - This device provides the Audio Gateway’s remote audio input and output mechanism.
HFP/HSP Profile Stack
As indicated below, the Hands-Free Profile is dependent upon both the RFCOMM and the Generic Access Profile.
Figure below shows the protocols and entities used in HSP profile.
The Controller, HCI, and L2CAP are the OSI layer 1 and 2 Bluetooth protocols. RFCOMM is the Bluetooth adaptation of GSM TS 07.10. SDP is the Bluetooth Service Discovery Protocol. Headset Profile and AT CMD are the entities responsible for headset specific control signaling, which is AT command based.
HFP/HSP UUID
Universally Unique Identifier(UUID) is a universally unique identifier that is guaranteed to be unique across all space and all time. UUIDs can be independently created in a distributed fashion. No central registry of assigned UUIDs is required. A UUID is a 128-bit value.
The full 128-bit value of a 16-bit or 32-bit UUID may be computed by a simple arithmetic operation:
128_bit_value = 16_bit_value * 296 + Bluetooth_Base_UUID
128_bit_value = 32_bit_value * 296 + Bluetooth_Base_UUID
The Base UUID(Bluetooth_Base_UUID) is used for calculating 128-bit UUIDs from ‘short UUIDs’ (uuid16 and uuid32) as described in the SDP Specification: 00000000-0000-1000-8000-00805F9B34FB.
A 16-bit UUID may be converted to 32-bit UUID format by zero-extending the 16-bit value to 32-bits. An equivalent method is to add the 16-bit UUID value toa zero-valued 32-bit UUID.
Blutetooth SDP UUID numbers are defined in https://www.bluetooth.com/specifications/assigned-numbers/service-discovery/.
HFP HF and HSP HS UUID registration is referred in
hfp_sdp_record
andhsp_sdp_record
.
HFP/HSP Features
In the initialization procedure, the HF shall send the AT+BRSF command to the AG to both notify the AG of the supported features in the HF, as well as to retrieve the supported features in the AG using the +BRSF result code.
Framework supports codec negotiation and CVSD, mSBC codec types.
Meanwhile, HFP HF features should be registered in HFP HF SDP record
hfp_sdp_record
as well, shown as HFP spec https://www.bluetooth.com/specifications/specs/hands-free-profile/.HSP HS features should be registered in HSP HS SDP record
hsp_sdp_record
as well, shown as HSP spec https://www.bluetooth.com/specifications/specs/headset-profile-1-2/.
HFP/HSP Service Channel
HFP HF and HSP HS service channels should be registered by
bt_hfp_init
.Meanwhile, HFP HF and HSP HS service channel should be initialized in
hfp_sdp_record
andhsp_sdp_record
as well.
Feature Set
The functions provided by HFP APIs are as below.
Connecting and Disconnecting: Including functions to connect/disconnect HFP.
Operating Call: Including functions to answer/reject call, etc.
Notifying Call: Including functions to notify call event.
Provided APIs
The table below shows a simple description of HFP APIs. HFP related APIs are provided in the sdk\inc\framework\bt\bt_hfp.h
and sdk\inc\framework\bt\bt_hfp_ag.h
.
Header File |
Description |
API Reference |
---|---|---|
bt_hfp.h |
Connect HFP, operate and notify Call, HF role. |
|
bt_hfp_ag.h |
Connect HFP, operate and notify Call, AG role. |
Functions
HFP Init
Initialize HFP profile, and set HFP/HSP RFCOMM channel number. Configure HFP supported features.
void app_hfp_init(void)
{
if (app_cfg_const.supported_profile_mask & (HFP_PROFILE_MASK | HSP_PROFILE_MASK))
{
bt_hfp_init(aRFC_HFP_CHANN_NUM,
RFC_HSP_CHANN_NUM, app_cfg_const.hfp_brsf_capability);
audio_mgr_cback_register(app_hfp_audio_cback);
bt_mgr_cback_register(app_hfp_bt_cback);
}
}
HFP Connect
Initiate HFP connection to a remote device, as shown below.
Request HFP disconnection, as shown below.
Accept or reject the incoming HFP connection from the remote device, as shown below.
HFP Audio Connect
bool bt_hfp_audio_connect_req()
Send a request to create audio connection.
HFP Call Control
bool bt_hfp_send_vnd_at_cmd_req()
Send HFP vendor AT command, as shown below.
bool bt_hfp_dial_with_number_req()
Send a command to dial a specific phone number, as shown below.
bool bt_hfp_dial_last_number_req()
Send a command to recalling the last number dialed by the remote device, as shown below.
Send a command to answer the incoming call.
bool bt_hfp_call_terminate_req()
Send a command to terminate the phone call.
bool bt_hfp_current_call_list_req()
Send a command to query current call list.
bool bt_hfp_release_held_or_waiting_call_req()
Send a command to release held or waiting call.
bool bt_hfp_release_active_call_accept_held_or_waiting_call_req()
Send a command to release active call and accept held or waiting call.
bool bt_hfp_hold_active_call_accept_held_or_waiting_call_req()
Send a command to hold active call and accept held or waiting call.
bool bt_hfp_join_two_calls_req()
Send a command to join two calls.
bool bt_hfp_dtmf_code_transmit()
Send a specific DTMF code to its network connection.
bool bt_hfp_network_operator_name_query()
Send a command to query the current network operator name.
HFP Voice_Recognition Control
bool bt_hfp_voice_recognition_enable_req()
Send a command to enable voice recognition.
HFP Audio Level Control
bool bt_hfp_speaker_gain_level_report()
Send a command to synchronize speaker gain level.
Source Code Overview
The following sections will describe the important source code of the HFP HF and HSP HS applications.
HFP/HSP SDP Record
HFP HF SDP local record and HSP HS SDP local record should be registered by
bt_sdp_record_add
.bt_sdp_record_add((void *)hfp_sdp_record); bt_sdp_record_add((void *)hsp_sdp_record);
For HFP HF, the 16-bit UUID 0x111E is reserved, meanwhile, for HSP HS, the 16-bit UUID 0x1108 is reserved.
static const uint8_t hfp_sdp_record[] = { //total length SDP_DATA_ELEM_SEQ_HDR, 0x37,//0x59, //attribute SDP_ATTR_SRV_CLASS_ID_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_SRV_CLASS_ID_LIST >> 8), (uint8_t)SDP_ATTR_SRV_CLASS_ID_LIST, SDP_DATA_ELEM_SEQ_HDR, //0x35 0x06, //6bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_HANDSFREE >> 8), //0x111E (uint8_t)(UUID_HANDSFREE), SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_GENERIC_AUDIO >> 8), //0x1203 (uint8_t)(UUID_GENERIC_AUDIO), //attribute SDP_ATTR_PROTO_DESC_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_PROTO_DESC_LIST >> 8), (uint8_t)SDP_ATTR_PROTO_DESC_LIST, SDP_DATA_ELEM_SEQ_HDR, //0x35 0x0C, //12bytes SDP_DATA_ELEM_SEQ_HDR, //0x35 0x03, //3bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_L2CAP >> 8), //0x0100 (uint8_t)(UUID_L2CAP), SDP_DATA_ELEM_SEQ_HDR, //0x35 0x05, //5bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_RFCOMM >> 8), //0x0003 (uint8_t)(UUID_RFCOMM), SDP_UNSIGNED_ONE_BYTE, //0x08 RFC_HFP_CHANN_NUM, //0x02 //attribute SDP_ATTR_PROFILE_DESC_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_PROFILE_DESC_LIST >> 8), (uint8_t)SDP_ATTR_PROFILE_DESC_LIST, SDP_DATA_ELEM_SEQ_HDR, //0x35 0x08, //8 bytes SDP_DATA_ELEM_SEQ_HDR, //0x35 0x06, //6 bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_HANDSFREE >> 8), //0x111E (uint8_t)(UUID_HANDSFREE), SDP_UNSIGNED_TWO_BYTE, //0x09 (uint8_t)(0x0107 >> 8), //version number default hf1.7 (uint8_t)(0x0107), //attribute SDP_ATTR_BROWSE_GROUP_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_BROWSE_GROUP_LIST >> 8), (uint8_t)SDP_ATTR_BROWSE_GROUP_LIST, SDP_DATA_ELEM_SEQ_HDR, 0x03, SDP_UUID16_HDR, (uint8_t)(UUID_PUBLIC_BROWSE_GROUP >> 8), (uint8_t)UUID_PUBLIC_BROWSE_GROUP, /* //attribute SDP_ATTR_LANG_BASE_ATTR_ID_LIST...it is used for SDP_ATTR_SRV_NAME SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_LANG_BASE_ATTR_ID_LIST >> 8), (uint8_t)SDP_ATTR_LANG_BASE_ATTR_ID_LIST, SDP_DATA_ELEM_SEQ_HDR, 0x09, SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_LANG_ENGLISH >> 8), (uint8_t)SDP_LANG_ENGLISH, SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_CHARACTER_UTF8 >> 8), (uint8_t)SDP_CHARACTER_UTF8, SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_BASE_LANG_OFFSET >> 8), (uint8_t)SDP_BASE_LANG_OFFSET, //attribute SDP_ATTR_SRV_NAME SDP_UNSIGNED_TWO_BYTE, (uint8_t)((SDP_ATTR_SRV_NAME + SDP_BASE_LANG_OFFSET) >> 8), (uint8_t)(SDP_ATTR_SRV_NAME + SDP_BASE_LANG_OFFSET), SDP_STRING_HDR, //0x25 text string 0x0F, //15 bytes 0x48, 0x61, 0x6e, 0x64, 0x73, 0x2d, 0x66, 0x72, 0x65, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x74, //"Hands-free unit" */ //attribute SDP_ATTR_SUPPORTED_FEATURES SDP_UNSIGNED_TWO_BYTE, (uint8_t)((SDP_ATTR_SUPPORTED_FEATURES) >> 8), (uint8_t)(SDP_ATTR_SUPPORTED_FEATURES), SDP_UNSIGNED_TWO_BYTE, (uint8_t)(0x003F >> 8), (uint8_t)(0x003F) }; static const uint8_t hsp_sdp_record[] = { //total length SDP_DATA_ELEM_SEQ_HDR, 0x39,//0x58, //attribute SDP_ATTR_SRV_CLASS_ID_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_SRV_CLASS_ID_LIST >> 8), (uint8_t)SDP_ATTR_SRV_CLASS_ID_LIST, SDP_DATA_ELEM_SEQ_HDR, //0x35 0x09, //6bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_HEADSET >> 8), //0x1108 (uint8_t)(UUID_HEADSET), SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_HEADSET_HS >> 8), //0x1131 (uint8_t)(UUID_HEADSET_HS), SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_GENERIC_AUDIO >> 8), //0x1203 (uint8_t)(UUID_GENERIC_AUDIO), //attribute SDP_ATTR_PROTO_DESC_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_PROTO_DESC_LIST >> 8), (uint8_t)SDP_ATTR_PROTO_DESC_LIST, SDP_DATA_ELEM_SEQ_HDR, //0x35 0x0C, //12 bytes SDP_DATA_ELEM_SEQ_HDR, //0x35 0x03, ///3 bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_L2CAP >> 8), //0x0100 (uint8_t)(UUID_L2CAP), SDP_DATA_ELEM_SEQ_HDR, 0x05, //5 bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_RFCOMM >> 8), //0x0003 (uint8_t)(UUID_RFCOMM), SDP_UNSIGNED_ONE_BYTE, //0x08 RFC_HSP_CHANN_NUM, //0x01 //attribute SDP_ATTR_PROFILE_DESC_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_PROFILE_DESC_LIST >> 8), (uint8_t)SDP_ATTR_PROFILE_DESC_LIST, SDP_DATA_ELEM_SEQ_HDR, //0x35 0x08, //8 bytes SDP_DATA_ELEM_SEQ_HDR, //0x35 0x06, //6 bytes SDP_UUID16_HDR, //0x19 (uint8_t)(UUID_HEADSET >> 8), //0x1108 (uint8_t)(UUID_HEADSET), SDP_UNSIGNED_TWO_BYTE, //0x09 (uint8_t)(0x0102 >> 8), //version number default hs1.2 (uint8_t)(0x0102), //attribute SDP_ATTR_BROWSE_GROUP_LIST SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_BROWSE_GROUP_LIST >> 8), (uint8_t)SDP_ATTR_BROWSE_GROUP_LIST, SDP_DATA_ELEM_SEQ_HDR, 0x03, SDP_UUID16_HDR, (uint8_t)(UUID_PUBLIC_BROWSE_GROUP >> 8), (uint8_t)UUID_PUBLIC_BROWSE_GROUP, /* //attribute SDP_ATTR_LANG_BASE_ATTR_ID_LIST...it is used for SDP_ATTR_SRV_NAME SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_LANG_BASE_ATTR_ID_LIST >> 8), (uint8_t)SDP_ATTR_LANG_BASE_ATTR_ID_LIST, SDP_DATA_ELEM_SEQ_HDR, 0x09, SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_LANG_ENGLISH >> 8), (uint8_t)SDP_LANG_ENGLISH, SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_CHARACTER_UTF8 >> 8), (uint8_t)SDP_CHARACTER_UTF8, SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_BASE_LANG_OFFSET >> 8), (uint8_t)SDP_BASE_LANG_OFFSET, //attribute SDP_ATTR_SRV_NAME SDP_UNSIGNED_TWO_BYTE, (uint8_t)((SDP_ATTR_SRV_NAME + SDP_BASE_LANG_OFFSET) >> 8), (uint8_t)(SDP_ATTR_SRV_NAME + SDP_BASE_LANG_OFFSET), SDP_STRING_HDR, //0x25 text string 0x0C, //12 bytes 0x48, 0x65, 0x61, 0x64, 0x73, 0x65, 0x74, 0x20, 0x75, 0x6e, 0x69, 0x74, //"Headset unit" */ //attribute SDP_ATTR_RMT_AUDIO_VOL_CTRL SDP_UNSIGNED_TWO_BYTE, (uint8_t)(SDP_ATTR_RMT_AUDIO_VOL_CTRL >> 8), (uint8_t)SDP_ATTR_RMT_AUDIO_VOL_CTRL, SDP_BOOL_ONE_BYTE, //0x28 0x01 //True };
HFP/HSP Profile Initialization
HFP HF and HSP HS could be initialized as follows.
void app_hfp_init(void) { if (app_cfg_const.supported_profile_mask & (HFP_PROFILE_MASK | HSP_PROFILE_MASK)) { bt_hfp_init(app_cfg_const.hfp_link_number, RFC_HFP_CHANN_NUM, RFC_HSP_CHANN_NUM, app_cfg_const.hfp_brsf_capability); bt_mgr_cback_register(app_hfp_bt_cback); gap_reg_timer_cb(app_hfp_timeout_cb, &hfp_timer_queue_id); } }
HFP/HSP Profile Link Back
Firstly, HFP HF and HSP HS will discover the HFP and HSP SDP record in AG.
bool linkback_profile_search_start(uint8_t *bd_addr, uint32_t prof, bool is_special, T_LINKBACK_SEARCH_PARAM *param) { ...... case HFP_PROFILE_MASK: { uuid.uuid_16 = UUID_HANDSFREE_AUDIO_GATEWAY; } break; case HSP_PROFILE_MASK: { uuid.uuid_16 = UUID_HEADSET_AUDIO_GATEWAY; } break; ...... if (ret) { if (legacy_start_sdp_discov(bd_addr, uuid_type, uuid) != GAP_CAUSE_SUCCESS) { ret = false; } } ...... }
Then, HFP HF and HSP HS will initiate profile connection to AG.
Note
If both devices initiate a connection at the same time, both channels shall be closed and each device shall wait a random time (not more than 1s and not less than 100ms) and then try to initiate the connection again. If it is known which device is the master, that device can re-try at once.
bool linkback_profile_connect_start(uint8_t *bd_addr, uint32_t prof, T_LINKBACK_CONN_PARAM *param) { ...... case HFP_PROFILE_MASK: ret = bt_hfp_connect_req(bd_addr, param->server_channel, true); break; case HSP_PROFILE_MASK: ret = bt_hfp_connect_req(bd_addr, param->server_channel, false); break; ...... }
HFP/HSP Profile Callback Handler
For HFP HF and HSP HS, callback function is used to handle BT HFP HF and HSP HS related events.
static void app_hfp_bt_cback(T_BT_EVENT event_type, void *event_buf, uint16_t buf_len) { T_BT_EVENT_PARAM *param = event_buf; bool handle = true; switch (event_type) { case BT_EVENT_HFP_CONN_IND: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_conn_ind.bd_addr); if (p_link == NULL) { APP_PRINT_ERROR0("app_hfp_bt_cback: no acl link found"); return; } bt_hfp_connect_cfm(p_link->bd_addr, true); } break; case BT_EVENT_HFP_CONN_CMPL: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_conn_cmpl.bd_addr); if (p_link != NULL) { uint8_t link_number; uint8_t pair_idx; p_link->call_id_type_check = true; p_link->call_id_type_num = false; bt_bond_index_get(p_link->bd_addr, &pair_idx); bt_hfp_speaker_gain_level_report(p_link->bd_addr, app_cfg_nv.voice_gain_level[pair_idx]); bt_hfp_microphone_gain_level_report(p_link->bd_addr, 0x0a); if (app_src_status_hook == NULL) { app_hfp_batt_level_report(p_link->bd_addr); } link_number = app_connected_profile_link_num(HFP_PROFILE_MASK | HSP_PROFILE_MASK); if (link_number == 1) { app_hfp_set_active_hf_index(p_link->bd_addr); app_bond_set_priority(p_link->bd_addr); } if (app_db.br_link[app_db.first_hf_index].app_hf_state == APP_HF_STATE_STANDBY) { app_db.first_hf_index = p_link->id; } else { app_db.last_hf_index = p_link->id; } p_link->app_hf_state = APP_HF_STATE_CONNECTED; } } break; case BT_EVENT_HFP_CALL_STATUS: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_call_status.bd_addr); if (p_link != NULL) { p_link->call_status = param->bt_hfp_call_status.curr_status; if ((app_cfg_const.enable_auto_answer_incoming_call == 1) && (p_link->call_status == BT_HFP_INCOMING_CALL_ONGOING)) { gap_start_timer(&hfp_auto_answer_call, "auto_answer_call", hfp_timer_queue_id, APP_HFP_TIMER_AUTO_ANSWER_CALL, p_link->id, app_cfg_const.timer_hfp_auto_answer_call); } if (p_link->call_status == BT_HFP_CALL_IDLE) { p_link->call_id_type_check = true; p_link->call_id_type_num = false; app_hfp_disallow_enter_sniff_mode(false); } app_hfp_update_call_status(); } if (app_cfg_nv.bud_role == REMOTE_SESSION_ROLE_PRIMARY || app_cfg_nv.bud_role == REMOTE_SESSION_ROLE_SINGLE) { if (param->bt_hfp_call_status.prev_status == BT_HFP_INCOMING_CALL_ONGOING && param->bt_hfp_call_status.curr_status == BT_HFP_CALL_IDLE) { if (app_db.reject_call_by_key) { app_db.reject_call_by_key = false; app_audio_tone_play(app_cfg_const.tone_hf_call_reject, false, true); } } if (param->bt_hfp_call_status.prev_status == BT_HFP_CALL_ACTIVE && param->bt_hfp_call_status.curr_status == BT_HFP_CALL_IDLE) { app_audio_tone_play(app_cfg_const.tone_hf_call_end, false, true); } if (param->bt_hfp_call_status.prev_status != BT_HFP_CALL_ACTIVE && param->bt_hfp_call_status.curr_status == BT_HFP_CALL_ACTIVE) { app_audio_tone_play(app_cfg_const.tone_hf_call_active, false, true); } if (param->bt_hfp_call_status.prev_status != BT_HFP_OUTGOING_CALL_ONGOING && param->bt_hfp_call_status.curr_status == BT_HFP_OUTGOING_CALL_ONGOING) { app_audio_tone_play(app_cfg_const.tone_hf_outgoing_call, false, true); } } if (param->bt_hfp_call_status.curr_status == BT_HFP_CALL_ACTIVE || param->bt_hfp_call_status.curr_status == BT_HFP_INCOMING_CALL_ONGOING || param->bt_hfp_call_status.curr_status == BT_HFP_OUTGOING_CALL_ONGOING) { if (app_cfg_const.timer_auto_power_off_while_phone_connected_and_anc_apt_off) { app_auto_power_off_disable(AUTO_POWER_OFF_MASK_SOURCE_LINK); } app_roleswap_state_machine(ROLESWAP_EVENT_STREAM_CALL_START, 0, 0); } else if (param->bt_hfp_call_status.curr_status == BT_HFP_CALL_IDLE) { if (app_cfg_const.timer_auto_power_off_while_phone_connected_and_anc_apt_off) { app_auto_power_off_enable(AUTO_POWER_OFF_MASK_SOURCE_LINK, app_cfg_const.timer_auto_power_off_while_phone_connected_and_anc_apt_off); } app_roleswap_state_machine(ROLESWAP_EVENT_STREAM_CALL_STOP, 0, 0); } } break; case BT_EVENT_HFP_SERVICE_STATUS: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_service_status.bd_addr); if (p_link != NULL) { p_link->service_status = param->bt_hfp_service_status.status; app_hfp_check_service_status(); } } break; case BT_EVENT_HFP_CALL_WAITING_IND: case BT_EVENT_HFP_CALLER_ID_IND: { if (app_cfg_nv.bud_role == REMOTE_SESSION_ROLE_SINGLE) { T_APP_BR_LINK *br_link; T_APP_LE_LINK *le_link; char *number; if (event_type == BT_EVENT_HFP_CALLER_ID_IND) { br_link = app_find_br_link(param->bt_hfp_caller_id_ind.bd_addr); number = (char *)param->bt_hfp_caller_id_ind.number; } else { br_link = app_find_br_link(param->bt_hfp_call_waiting_ind.bd_addr); number = (char *)param->bt_hfp_call_waiting_ind.number; } le_link = app_find_le_link_by_addr(param->bt_pbap_caller_id_name.bd_addr); if (br_link != NULL) { /* Sanity check if BR/EDR TTS session is ongoing */ if (br_link->cmd_set_enable == true && br_link->tts_handle != NULL) { break; } /* Sanity check if BLE TTS session is ongoing */ if (le_link != NULL && le_link->cmd_set_enable == true && le_link->tts_handle != NULL) { break; } if (br_link->call_id_type_check == true) { if (br_link->connected_profile & PBAP_PROFILE_MASK) { if (bt_pbap_vcard_listing_by_number_pull(br_link->bd_addr, number) == false) { br_link->call_id_type_check = false; br_link->call_id_type_num = true; } } else { br_link->call_id_type_check = false; br_link->call_id_type_num = true; } } if (br_link->call_id_type_check == false) { if (br_link->call_id_type_num == true) { uint8_t *data; uint16_t len; len = param->bt_pbap_caller_id_name.name_len + 3; data = os_mem_alloc(RAM_TYPE_DATA_ON, len); if (data != NULL) { data[1] = CALLER_ID_TYPE_NUMBER; data[2] = param->bt_pbap_caller_id_name.name_len; memcpy(data + 3, param->bt_pbap_caller_id_name.name_ptr, param->bt_pbap_caller_id_name.name_len); if (br_link->connected_profile & (SPP_PROFILE_MASK | IAP_PROFILE_MASK)) { data[0] = br_link->id; app_report_event(CMD_PATH_SPP, EVENT_CALLER_ID, br_link->id, data, len); } else if (le_link != NULL) { data[0] = le_link->id; app_report_event(CMD_PATH_LE, EVENT_CALLER_ID, le_link->id, data, len); } os_mem_free(data); } } } } } } break; case BT_EVENT_HFP_RING_ALERT: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_ring_alert.bd_addr); if (p_link != NULL) { p_link->is_inband_ring = param->bt_hfp_ring_alert.is_inband; if ((app_cfg_const.always_play_hf_incoming_tone_when_incoming_call == false) && ((p_link->is_inband_ring == false) || (p_link->id != active_hf_index))) /* TODO check active sco link */ { if (hf_ring_active == false && p_link->call_status == BT_HFP_INCOMING_CALL_ONGOING) { hf_ring_active = true; app_hfp_ring_alert(p_link); } } } } break; case BT_EVENT_HFP_SPK_VOLUME_CHANGED: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_spk_volume_changed.bd_addr); if (p_link != NULL) { if (app_src_profile_hook) { if ((app_hfp_get_call_status() == BT_HFP_INCOMING_CALL_ONGOING) && app_src_profile_hook()) { break; } } if (app_db.remote_session_state == REMOTE_SESSION_STATE_DISCONNECTED) { uint8_t pair_idx; bt_bond_index_get(p_link->bd_addr, &pair_idx); app_cfg_nv.voice_gain_level[pair_idx] = (param->bt_hfp_spk_volume_changed.volume * app_cfg_const.voice_out_volume_max + 0x0f / 2) / 0x0f; if (app_audio_vol_set_hook) { app_audio_vol_set_hook(p_link->sco_track_handle, app_cfg_nv.voice_gain_level[pair_idx]); } else { if (app_cfg_const.enable_rtk_charging_box) { if (app_db.local_in_case) { app_adp_volume_mute(); } else { audio_track_volume_set(p_link->sco_track_handle, app_cfg_nv.voice_gain_level[pair_idx]); if (app_db.voice_muted) { app_db.voice_muted = false; } } } else { audio_track_volume_set(p_link->sco_track_handle, app_cfg_nv.voice_gain_level[pair_idx]); if (app_db.voice_muted) { app_db.voice_muted = false; } } } } else { uint8_t cmd_ptr[8]; uint8_t pair_idx; bt_bond_index_get(p_link->bd_addr, &pair_idx); app_cfg_nv.voice_gain_level[pair_idx] = (param->bt_hfp_spk_volume_changed.volume * app_cfg_const.voice_out_volume_max + 0x0f / 2) / 0x0f; memcpy(&cmd_ptr[0], p_link->bd_addr, 6); cmd_ptr[6] = AUDIO_STREAM_TYPE_VOICE; cmd_ptr[7] = app_cfg_nv.voice_gain_level[pair_idx]; remote_sync_msg_relay(app_db.relay_handle, APP_REMOTE_MSG_HFP_VOLUME_SYNC, cmd_ptr, 8, REMOTE_TIMER_HIGH_PRECISION, 0, false); } } } break; case BT_EVENT_HFP_MIC_VOLUME_CHANGED: { } break; case BT_EVENT_HFP_DISCONN_CMPL: { T_APP_BR_LINK *p_link; p_link = app_find_br_link(param->bt_hfp_disconn_cmpl.bd_addr); if (p_link != NULL) { if (param->bt_hfp_disconn_cmpl.cause == (HCI_ERR | HCI_ERR_CONN_ROLESWAP)) { //do nothing } else { uint8_t i; p_link->call_status = BT_HFP_CALL_IDLE; p_link->app_hf_state = APP_HF_STATE_STANDBY; if (app_db.first_hf_index == p_link->id) { app_db.first_hf_index = app_db.last_hf_index; } for (i = 0; i < MAX_BR_LINK_NUM; i++) { if (app_db.br_link[i].connected_profile & (HFP_PROFILE_MASK | HSP_PROFILE_MASK)) { app_hfp_set_active_hf_index(app_db.br_link[i].bd_addr); app_bond_set_priority(app_db.br_link[i].bd_addr); break; } } if (app_hfp_get_call_status() != BT_HFP_CALL_IDLE) { app_hfp_update_call_status(); } } if ((param->bt_hfp_disconn_cmpl.cause & ~HCI_ERR) != HCI_ERR_CONN_ROLESWAP) { app_hfp_check_service_status(); } } } break; ...... default: handle = false; break; } if (handle == true) { APP_PRINT_INFO1("app_hfp_bt_cback: event_type 0x%04x", event_type); } }
Sample Projects
SDK provides a corresponding demo application for developers’ reference in development. The BR/EDR Audio gives a simple example of how to use HFP.