Headset
1. Function Overview
This project show how headset work with main function:
1.Receive music data transmitted by the peer as a2dp sink2.Control the peer as avrcp ct/tg (play pause,etc.)3.Receive voice data transmitted by the peer as hfp hf4.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 <xx:xx:xx:xx:xx:xx>
connect
headset disconnect <xx:xx:xx:xx:xx:xx>
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 1. software module architecture
3.2 Flowchart
A2DP workflow and HFP workflow are shown in the figure below:
Figure 2. a2dp flow chart
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 demoCONFIG_A2DP_SINK_DEMO=y//enable HFP demoCONFIG_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<frame_num;i++)
{
if (ring_buffer_node_get_free_nodes(&s_a2dp_frame_nodes))
{
uint8_t *node = ring_buffer_node_get_write_node(&s_a2dp_frame_nodes);
os_memcpy(node, fb, frame_length);
fb += frame_length;
}
else
{
LOGI("A2DP frame nodes buffer(sbc) is full\n");
//os_free(msg.data);
break;
}
}
}
#if CONFIG_AAC_DECODER
//First parse the frame length of the AAC format data, and then store it in the ring buffer according to the frame length
else if(CODEC_AUDIO_AAC == bt_audio_a2dp_sink_codec.type)
{
// LOGI("-> %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<bt_audio_sink_sbc_decoder.pcm_length * 2; i++)
{
dst[i] = dst[i*2];
}
w_len = bt_audio_sink_sbc_decoder.pcm_length * 2;
}
//send decoded data to the speaker
int size = raw_stream_write(raw_write, (char *)dst, w_len);
if (size <= 0)
{
LOGE("raw_stream_write size fail: %d \n", size);
break;
}
else
{
//LOGI("raw_stream_write size: %d \n", size);
}
}
#if (CONFIG_AAC_DECODER)
else if(CODEC_AUDIO_AAC == bt_audio_a2dp_sink_codec.type)
{
uint32_t len = *(uint32_t *)inbuf;
// LOGI("<- %d \n", len);
uint8_t *out_buf = 0;
uint32_t out_len = 0;
//perform aac decoding
ret = bk_aac_decoder_decode(&bt_audio_sink_aac_decoder, (inbuf+4), len, &out_buf, &out_len);
if(ret == 0)
{
int16_t *dst = (int16_t*)out_buf;
int16_t w_len = out_len;
if(CONFIG_BOARD_AUDIO_CHANNLE_NUM == 1)
{
for(uint16_t i=0;i<out_len/2;i++)
{
dst[i] = dst[i*2];
}
w_len = out_len/2;
}
//send decoded data to the speaker
int size = raw_stream_write(raw_write, (char *)dst, w_len);
if (size <= 0)
{
LOGE("raw_stream_write size fail: %d \n", size);
break;
}
else
{
// LOGI("raw_stream_write size: %d \n", size);
}
}else
{
LOGE("sbc_decoder_decode error <%d>\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;
}
}
...
}