Headset ====================================== :link_to_translation:`zh_CN:[中文]` 1. Function Overview ------------------------------------- This project show how headset work with main function: | 1.Receive music data transmitted by the peer as a2dp sink | 2.Control the peer as avrcp ct/tg (play pause,etc.) | 3.Receive voice data transmitted by the peer as hfp hf | 4.ble gatt server/gatt client(see the introduction of Central project for details) 1.1 Specification ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, * a2dp: * avdtp sink * avrcp: * tg * ct * hfp: * hf * ble: * gap * gatt server * gatt client * smp legacy pair/secure connection pair 1.2 Code Path and build cmd ------------------------------------- demo:`./projects/bluetooth/headset `_ build cmd:``make bk7258 PROJECT=bluetooth/headset`` 2. Introduction to cli commands ------------------------------------- 2.1 a2dp/avrcp ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +-------------------------------------------+---------------------+ | headset connect | connect | +-------------------------------------------+---------------------+ | headset disconnect | disconnect | +-------------------------------------------+---------------------+ | headset play | play | +-------------------------------------------+---------------------+ | headset pause | pause | +-------------------------------------------+---------------------+ | headset next | next song | +-------------------------------------------+---------------------+ | headset prev | prev song | +-------------------------------------------+---------------------+ | headset rewind [msec] | rewind | +-------------------------------------------+---------------------+ | headset fast_forward [msec] | fast forward | +-------------------------------------------+---------------------+ | headset vol_up | volume up | +-------------------------------------------+---------------------+ | headset vol_down | volume down | +-------------------------------------------+---------------------+ | headset pair_mode | enter pairing mode | +-------------------------------------------+---------------------+ 3. Frame Diagram --------------------------------- 3.1 Software Module Architecture Diagram ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, As shown in the figure below,bluetooth is responsible for receiving music data, receiving and sending voice data; audio is responsible for decoding and data,playing music and voice data, and collecting and encoding MIC data; storage is responsible for storing bluetooth information,such as encryption keys. .. figure:: ../../../../_static/bluetooth_headset_arch.png :align: center :alt: module architecture Overview :figclass: align-center Figure 1. software module architecture 3.2 Flowchart ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, A2DP workflow and HFP workflow are shown in the figure below: .. figure:: ../../../../_static/bluetooth_headset_a2dp_flow.png :align: center :alt: a2dp flow Overview :figclass: align-center Figure 2. a2dp flow chart .. figure:: ../../../../_static/bluetooth_headset_hfp_flow.png :align: center :alt: hfp flow Overview :figclass: align-center Figure 3. hfp flow chart 4. Configuration --------------------------------- 4.1 Demo enable configuration ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, In the project path config/bk7258/config,modify the macro to configure whether to enable A2DP and HFP demo.Currently,A2DP demo is enabled by default; | //enable A2DP demo | CONFIG_A2DP_SINK_DEMO=y | //enable HFP demo | CONFIG_HFP_HF_DEMO=y 4.2 Codec configuration ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 4.2.1 A2DP ................................. +---------------+--------------+----------------------+------------------------------------+ | | CONFIG_SBC | CONFIG_AAC_DECODER | parameter of a2dp_sink_demo_init | +---------------+--------------+----------------------+------------------------------------+ | support SBC | Y | N | 0 | +---------------+--------------+----------------------+------------------------------------+ | support AAC | Y | Y | 1 | +---------------+--------------+----------------------+------------------------------------+ 4.2.1 HFP ................................. +---------------+--------------+---------------------------------+ | | CONFIG_SBC | parameter of hfp_hf_demo_init | +---------------+--------------+---------------------------------+ | support CVSD | N | 0 | +---------------+--------------+---------------------------------+ | support mSBC | Y | 1 | +---------------+--------------+---------------------------------+ 5. Code Explanation --------------------------------- 5.1 A2DP demo ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 5.1.1 Get music data reported by BT ....................................... :: void bt_audio_sink_media_data_ind(const uint8_t *data, uint16_t data_len) { bt_audio_sink_demo_msg_t demo_msg; int rc = -1; os_memset(&demo_msg, 0x0, sizeof(bt_audio_sink_demo_msg_t)); if (bt_audio_sink_demo_msg_que == NULL) { return; } demo_msg.data = (char *) os_malloc(data_len); if (demo_msg.data == NULL) { LOGE("%s, malloc failed\r\n", __func__); return; } os_memcpy(demo_msg.data, data, data_len); demo_msg.type = BT_AUDIO_D2DP_DATA_IND_MSG; demo_msg.len = data_len; //send to audio_sink_demo task rc = rtos_push_to_queue(&bt_audio_sink_demo_msg_que, &demo_msg, BEKEN_NO_WAIT); ... } 5.1.2 Store music data in ring buffer ............................................ :: void bt_audio_sink_demo_main(void *arg) { ... case BT_AUDIO_D2DP_DATA_IND_MSG: { ... uint8 *fb = (uint8_t *)msg.data; if(s_spk_is_started) { //store sbc format data into the ring buffer frame by frame if (CODEC_AUDIO_SBC == bt_audio_a2dp_sink_codec.type) { uint8_t payload_header = *fb++; uint8_t frame_num = payload_header&0xF; if(msg.len - 1 != frame_length*frame_num) { LOGI("recv undef sbc, payload_header %d, payload_len: %d, frame_num:%d \r\n", payload_header, msg.len - 1, frame_num); } for(uint8_t i=0;i %d \n", msg.len); uint8_t *inbuf = &fb[9]; uint32_t inlen = 0; uint8_t len = 255; do { inlen += len = *inbuf++; } while (len == 255); { if (ring_buffer_node_get_free_nodes(&s_a2dp_frame_nodes)) { uint8_t *node = ring_buffer_node_get_write_node(&s_a2dp_frame_nodes); *((uint32_t *)node) = inlen; os_memcpy(node + 4, inbuf, inlen); } else { LOGI("A2DP frame nodes buffer(aac) is full\n"); os_free(msg.data); break; } } } #endif else { LOGE("%s, Unsupported a2dp codec %d \r\n", __func__, bt_audio_a2dp_sink_codec.type); } if(a2dp_speaker_sema) { rtos_set_semaphore(&a2dp_speaker_sema); } } ... } break; ... } 5.1.3 Decode music data ................................. :: static void speaker_task(void *arg) { ... for (; i < s_frame; i++) { //get data from ring buffer uint8_t *inbuf = ring_buffer_node_get_read_node(&s_a2dp_frame_nodes); bk_err_t ret; if (CODEC_AUDIO_SBC == bt_audio_a2dp_sink_codec.type) { //perform sbc decoding ret = bk_sbc_decoder_frame_decode(&bt_audio_sink_sbc_decoder, inbuf, frame_length); if (ret < 0) { LOGE("sbc_decoder_decode error <%d>\n", ret); continue; } int16_t *dst = (int16_t*)bt_audio_sink_sbc_decoder.pcm_sample; int16_t w_len = bt_audio_sink_sbc_decoder.pcm_length * 4; if(CONFIG_BOARD_AUDIO_CHANNLE_NUM == 1) { for(int i=0; i\n", ret); } } #endif else { LOGE("unsupported codec!! /n"); } } ... } 5.2 HFP demo ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 5.2.1 Get voice data reported by BT ....................................... :: void bt_audio_hfp_client_voice_data_ind(const uint8_t *data, uint16_t data_len) { bt_audio_hf_demo_msg_t demo_msg; int rc = -1; os_memset(&demo_msg, 0x0, sizeof(bt_audio_hf_demo_msg_t)); if (bt_audio_hf_demo_msg_que == NULL) return; demo_msg.data = (char *) os_malloc(data_len); if (demo_msg.data == NULL) { LOGI("%s, malloc failed\r\n", __func__); return; } os_memcpy(demo_msg.data, data, data_len); demo_msg.type = BT_AUDIO_VOICE_IND_MSG; demo_msg.len = data_len; //send to audio_hf_demo task rc = rtos_push_to_queue(&bt_audio_hf_demo_msg_que, &demo_msg, BEKEN_NO_WAIT); ... } 5.2.2 Decode voice data ............................................ :: void bt_audio_hf_demo_main(void *arg) { ... case BT_AUDIO_VOICE_IND_MSG: { ... //decode msbc format voice data if (CODEC_VOICE_MSBC == bt_audio_hfp_hf_codec) { fb += 2; //Skip Synchronization Header ret = bk_sbc_decoder_frame_decode(&bt_audio_hf_sbc_decoder, fb, msg.len - 2); // LOGI("sbc decod %d \n", ret); if (ret < 0) { LOGE("msbc decode fail, ret:%d\n", ret); } else { ret = BK_OK; fb = (uint8_t*)bt_audio_hf_sbc_decoder.pcm_sample; packet_len = r_len = SCO_MSBC_SAMPLES_PER_FRAME*2; packet_num = 4; } } else { packet_len = r_len = SCO_CVSD_SAMPLES_PER_FRAME * 2; packet_num = 8; } if(ret == BK_OK) { ... //store the original voice data into hf_speaker_buffer os_memcpy(hf_speaker_buffer + hf_speaker_data_count, fb, r_len); hf_speaker_data_count += r_len; if (hf_speaker_data_count >= packet_len * packet_num) { if (hf_speaker_sema) { rtos_set_semaphore(&hf_speaker_sema); } hf_speaker_data_count -= packet_len * packet_num; } }else { //LOGE("write spk data fail \r\n"); } os_free(msg.data); } break; ... } 5.2.3 Play voice data ............................................ :: static void speaker_task(void *arg) { ... while (hf_auido_start) { rtos_get_semaphore(&hf_speaker_sema, BEKEN_WAIT_FOREVER); //send the voice data in hf_speaker_buffer to SPEAKER int size = raw_stream_write(raw_write, (char *)hf_speaker_buffer, packet_len); if (size <= 0) { LOGE("raw_stream_write size: %d \n", size); break; } else { //LOGI("raw_stream_write size: %d \n", size); } } ... } 5.2.4 Get uplink MIC data ................................. :: static void mic_task(void *arg) { ... while (hf_auido_start) { if (hf_mic_data_count+read_size < sizeof(hf_mic_sco_data)) { //get MIC data int size = raw_stream_read(raw_read, (char *)(hf_mic_sco_data + hf_mic_data_count), read_size); if (size > 0) { ... while (hf_mic_data_count >= send_len) { //decide whether to perform msbc encoding based on the selected encoding format if (CODEC_VOICE_MSBC == bt_audio_hfp_hf_codec) { int32_t produced = sbc_encoder_encode(&bt_audio_hf_sbc_encoder, (int16_t *)(hf_mic_sco_data + send_len * i)); // LOGI("[send_mic_data_to_air_msbc] %d \r\n",produced); //send the encoded voice data to BT bk_bt_hf_client_voice_out_write(hfp_peer_addr, (uint8_t *)&bt_audio_hf_sbc_encoder.stream [ -2 ], produced + 2); }else { //send uncoded voice data to BT bk_bt_hf_client_voice_out_write(hfp_peer_addr, hf_mic_sco_data + send_len * i, send_len); } i++; hf_mic_data_count -= send_len; } if (hf_mic_data_count) { os_memmove(hf_mic_sco_data, hf_mic_sco_data + send_len * i, hf_mic_data_count); } } } else { LOGE("MIC BUFFER FULL \r\n"); hf_mic_data_count = 0; } } ... }