Headset

[中文]

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 <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.

module architecture Overview

Figure 1. software module architecture

3.2 Flowchart

A2DP workflow and HFP workflow are shown in the figure below:

a2dp flow Overview

Figure 2. a2dp flow chart

hfp flow Overview

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<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);
                }
        }

   ...
}