2025-10-10 16:07:00 +08:00

541 lines
14 KiB
ReStructuredText
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

耳机(Headset)
======================================
:link_to_translation:`en:[English]`
1. 功能概述
-------------------------------------
本工程用于耳机等设备场景,主要功能有
| 1.作为a2dp sink接收对端传输的音乐数据
| 2.作为avrcp ct/tg对端进行控制播放暂停等
| 3.作为hfp hf接收对端传输的语音数据
| 4.ble gatt server/gatt client可以详见Central工程相关介绍
1.1 软件规格
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
* a2dp:
* avdtp sink
* avrcp:
* tg
* ct
* hfp:
* hf
* ble:
* gap
* gatt server
* gatt client
* smp legacy pair/secure connection pair
1.2 代码路径及编译命令
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Demo路径`./projects/bluetooth/headset <https://gitlab.bekencorp.com/wifi/armino/-/tree/main/projects/bluetooth/headset>`_
编译命令:``make bk7258 PROJECT=bluetooth/headset``
2. cmd命令简介
-------------------------------------
2.1 a2dp/avrcp
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+-------------------------------------------+---------------------+
| headset connect <xx:xx:xx:xx:xx:xx> | 主动连接对端设备 |
+-------------------------------------------+---------------------+
| headset disconnect <xx:xx:xx:xx:xx:xx> | 断开连接 |
+-------------------------------------------+---------------------+
| headset play | 播放 |
+-------------------------------------------+---------------------+
| headset pause | 暂停 |
+-------------------------------------------+---------------------+
| headset next | 下一曲 |
+-------------------------------------------+---------------------+
| headset prev | 上一曲 |
+-------------------------------------------+---------------------+
| headset rewind [msec] | 快退,可指定时间 |
+-------------------------------------------+---------------------+
| headset fast_forward [msec] | 快进,可指定时间 |
+-------------------------------------------+---------------------+
| headset vol_up | 音量增加 |
+-------------------------------------------+---------------------+
| headset vol_down | 音量减少 |
+-------------------------------------------+---------------------+
| headset pair_mode | 进入配对模式 |
+-------------------------------------------+---------------------+
3. 框架图
---------------------------------
3.1 软件模块架构图
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
如下图所示bluetooth负责音乐数据接收以及语音数据接收和发送audio负责对音乐和语音数据解码播放以及采集MIC数据并编码storage负责蓝牙关键信息存储比如加密key
.. figure:: ../../../../_static/bluetooth_headset_arch.png
:align: center
:alt: module architecture Overview
:figclass: align-center
Figure 1. software module architecture
3.2 流程图
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
A2DP工作流程和HFP工作流程见下图
.. 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. 配置
---------------------------------
4.1 demo开启配置
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
在工程路径下config/bk7258/config修改宏来配置是否开启A2DP和HFP demo当前默认开启A2DP demo
| //开启A2DP demo
| CONFIG_A2DP_SINK_DEMO=y
| //开启HFP demo
| CONFIG_HFP_HF_DEMO=y
4.2 codec方式配置
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
4.2.1 A2DP
.................................
+-----------+--------------+----------------------+-----------------------------+
| | CONFIG_SBC | CONFIG_AAC_DECODER | a2dp_sink_demo_init的参数 |
+-----------+--------------+----------------------+-----------------------------+
| 支持SBC | Y | N | 0 |
+-----------+--------------+----------------------+-----------------------------+
| 支持AAC | Y | Y | 1 |
+-----------+--------------+----------------------+-----------------------------+
4.2.1 HFP
.................................
+-----------+--------------+--------------------------+
| | CONFIG_SBC | hfp_hf_demo_init的参数 |
+-----------+--------------+--------------------------+
| 支持CVSD | N | 0 |
+-----------+--------------+--------------------------+
| 支持mSBC | Y | 1 |
+-----------+--------------+--------------------------+
5. 代码讲解
---------------------------------
5.1 A2DP demo
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
5.1.1 获得蓝牙上报的音乐数据
.................................
::
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;
//发送给audio_sink_demo task
rc = rtos_push_to_queue(&bt_audio_sink_demo_msg_que, &demo_msg, BEKEN_NO_WAIT);
...
}
5.1.2 把音乐数据按帧暂存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)
{
//对sbc格式数据按帧存入ring buffer
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
//先解析出AAC格式数据的帧长度再按帧长度存入ring buffer
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 解码音乐数据
.................................
::
static void speaker_task(void *arg)
{
...
for (; i < s_frame; i++)
{
//从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)
{
//进行sbc解码
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;
}
//发送解码数据给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;
//进行aac解码
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;
}
//发送解码数据给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 获得蓝牙上报的语音数据
.................................
::
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;
//发送给audio_hf_demo task
rc = rtos_push_to_queue(&bt_audio_hf_demo_msg_que, &demo_msg, BEKEN_NO_WAIT);
...
}
5.2.2 解码语音数据
............................................
::
void bt_audio_hf_demo_main(void *arg)
{
...
case BT_AUDIO_VOICE_IND_MSG:
{
...
//对msbc格式语音数据进行解码
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)
{
...
//把原生语音数据存入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 播放语音数据
............................................
::
static void speaker_task(void *arg)
{
...
while (hf_auido_start)
{
rtos_get_semaphore(&hf_speaker_sema, BEKEN_WAIT_FOREVER);
//把hf_speaker_buffer中的语音数据发给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 获取上行MIC数据
.................................
::
static void mic_task(void *arg)
{
...
while (hf_auido_start)
{
if (hf_mic_data_count+read_size < sizeof(hf_mic_sco_data))
{
//获取MIC数据
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)
{
//根据选用的编码格式决定是否进行msbc编码
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);
//发送编码后的语音数据给蓝牙
bk_bt_hf_client_voice_out_write(hfp_peer_addr, (uint8_t *)&bt_audio_hf_sbc_encoder.stream [ -2 ], produced + 2);
}else
{
//发送未编码的语音数据给蓝牙
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;
}
}
...
}