Bluetooth: Audio Client¶
2 环境要求¶
board: pan1080a_afld_evb
uart (option): 显示串口log
ES8316模块:带喇叭或耳机
需要搭配一个运行 audio_server 的板子一起使用。
3 编译和烧录¶
项目位置:zephyr\samples_panchip\bluetooth\audio_client
统一的配置、编译、下载工具正在开发中,当前可以使用脚本进行编译和下载。
脚本位置:quick_build_samples\bluetooth\audio_client.bat
。
打开脚本后默认会编译项目,编译完成时,可输入字符进行后续下载等操作:
Input the keyword to continue:
'b' build 编译项目
'r' make clean and rebuild 重新编译项目
'f' flash download 下载
'e' erase chip 擦除芯片
'o' open project by VS Code 打开 `VS Code`,可查看源码,执行编译下载等
others exit 退出
wait input:
4 演示说明¶
端口连接
IIC端口连接:
PAN1080 EVB板端口
ES8316模块端口
P0_0(I2C0_SCL)
IIC:CLK
P0_1(I2C0_SDA)
IIC:DAT
IIS端口连接:
PAN1080 EVB板端口
ES8316模块端口
P4_3(I2SMCLK)
MCLK
P4_4(I2SM_WS)
DLRCLK
P4_0(I2SM_CLK)
SCLK
P4_1(I2SM_DI)
Useless
P4_2(I2SM_DO)
DSDIN
配置语音发送端(参考audio_server)。
上电,等待蓝牙连接。
蓝牙连接成功后,喇叭或耳机就能听到对端的声音。
5 开发说明¶
5.1 音频数据采集¶
从机端的CODEC芯片将MIC的数据通过I2S传输给PAN1080,此时PAN1080作I2S的SLAVE,CODEC芯片作I2S的MASTER,PAN1080接收从CODEC芯片采集的数据。
CODEC芯片初始化的接口:
void I2S_HW_Init(void)
{
uint32_t data_len,cycle_cnt = 0;
printk("\nCPU @ %dHz,\n", SystemCoreClock);
I2S_GPIO_Init();
I2C_InitRole(I2C0,I2C_MODE_MASTER,ADR10BIT_DISABLE);
I2C_SetTxTirggerLevel(I2C0, I2C_TX_TL_1);
data_len = sizeof(codec8316_startup);
while(cycle_cnt < data_len)
{
I2C_Write(I2C0,codec8316_startup+cycle_cnt,2);
cycle_cnt += 2;
if((codec8316_startup[cycle_cnt-2] == 0x0) &&
(codec8316_startup[cycle_cnt-1] == 0xc0))
{
k_sleep(50);
}
if(cycle_cnt == 2)
{
k_sleep(5);
}
if(cycle_cnt == 6)
{
k_sleep(30);
}
}
I2S_DMA_Init();
}
PAN1080 DMA方式接收音频数据初始化接口:
void I2S_DMA_Init(void)
{
DMAC_ChannelConfigTypeDef RxConfigTmp = {0,};
CLK_APB1PeriphClockCmd(CLK_APB1Periph_I2SS,ENABLE);
I2S_ModduleInit();
I2S_SysInit();
DMAC_Init(DMA);
IRQ_DIRECT_CONNECT(DMA_IRQn, 0, DMA_IRQHandler, 0);
irq_enable(DMA_IRQn);
I2S_EnableI2s(TGT_IIS);
I2S_EnableRecBlock(TGT_IIS);
RxConfigTmp.AddrChangeDst = DMAC_AddrChange_Increment;
RxConfigTmp.AddrChangeSrc = DMAC_AddrChange_NoChange;
RxConfigTmp.BurstLenDst = DMAC_BurstLen_4;
RxConfigTmp.BurstLenSrc = DMAC_BurstLen_4;
RxConfigTmp.DataWidthDst = DMAC_DataWidth_16;
RxConfigTmp.DataWidthSrc = DMAC_DataWidth_16;
RxConfigTmp.TransferType = DMAC_TransferType_Per2Mem;
RxConfigTmp.FlowControl = DMAC_FlowControl_DMA;
RxConfigTmp.HandshakeDst = DMAC_Handshake_Hardware;
RxConfigTmp.HandshakeSrc = DMAC_Handshake_Hardware;
RxConfigTmp.PeripheralSrc = DMAC_Peripheral_IIS_Rx;
RxConfigTmp.IntEnable = ENABLE;
/*get free dma channel;*/
gDMARxChNum = DMAC_AcquireChannel(DMA);
/*enable dma transfer interrupt*/
DMAC_ClrIntFlagMsk(DMA,gDMARxChNum,DMAC_FLAG_INDEX_TFR);
DMAC_SetChannelConfig(DMA,gDMARxChNum,&RxConfigTmp);
I2S_Recv_Enable();
printk("dma init\n");
}
CODEC采样频率可设置成8khz或16khz,采样频率设置接口:
void I2S_SetI2sClkDivider(uint16_t u32Div)
{
CLK->APB1_CLK_CTRL1 &= ~0xFFFF;
//CLK->APB1_CLK_CTRL1 |= u32Div;
CLK->APB1_CLK_CTRL1 |= (3 << 16) | (CODEC_FREQ << 18); //lrclk
}
注:DMA中断优先级设成0,BLE中断优先级设成1。DMA中断优先级比BLE中断优先级高是为了防止BLE影响音频数据的采集从而导致音频数据采集不连续。
5.2 音频数据编解码¶
本系统音频编解码采用的是ADPCM编解码方式。
ADPCM (ADPCM Adaptive Differential Pulse Code Modulation),是一种针对16bit (或者更高) 声音波形数据的一种有损压缩算法,它将声音流中每次采样的 16bit 数据以 4bit 存储,所以压缩比1:4。而且编解码算法简单,所以是一种低空间消耗,高质量声音获得的好途径。
音频数据编码接口:
int adpcm_encoder(short *indata, unsigned char *outdata, int len)
{
short *inp; /* Input buffer pointer */
unsigned char *outp;/* output buffer pointer */
int val; /* Current input sample value */
int sign; /* Current adpcm sign bit */
unsigned int delta; /* Current adpcm output value */
int diff; /* Difference between val and valprev */
unsigned int udiff; /* unsigned value of diff */
unsigned int step; /* Stepsize */
int valpred; /* Predicted output value */
unsigned int vpdiff;/* Current change to valpred */
int index; /* Current step change index */
unsigned int outputbuffer = 0;/* place to keep previous 4-bit value */
int bufferstep; /* toggle between outputbuffer/output */
int count = 0; /* the number of bytes encoded */
outp = outdata;
inp = indata;
valpred = s_valprev;
index = s_index;
step = stepsizeTable[index];
bufferstep = 1;
while (len-- > 0 ) {
val = *inp++;
/* Step 1 - compute difference with previous value */
diff = val - valpred;
if(diff < 0)
{
sign = 8;
diff = (-diff);
}
else
{
sign = 0;
}
/* diff will be positive at this point */
udiff = (unsigned int)diff;
/* Step 2 - Divide and clamp */
/* Note:
** This code *approximately* computes:
** delta = diff*4/step;
** vpdiff = (delta+0.5)*step/4;
** but in shift step bits are dropped. The net result of this is
** that even if you have fast mul/div hardware you cannot put it to
** good use since the fixup would be too expensive.
*/
delta = 0;
vpdiff = (step >> 3);
if ( udiff >= step ) {
delta = 4;
udiff -= step;
vpdiff += step;
}
step >>= 1;
if ( udiff >= step ) {
delta |= 2;
udiff -= step;
vpdiff += step;
}
step >>= 1;
if ( udiff >= step ) {
delta |= 1;
vpdiff += step;
}
/* Phil Frisbie combined steps 3 and 4 */
/* Step 3 - Update previous value */
/* Step 4 - Clamp previous value to 16 bits */
if ( sign != 0 )
{
valpred -= vpdiff;
if ( valpred < -32768 )
valpred = -32768;
}
else
{
valpred += vpdiff;
if ( valpred > 32767 )
valpred = 32767;
}
/* Step 5 - Assemble value, update index and step values */
delta |= sign;
index += indexTable[delta];
if ( index < 0 ) index = 0;
if ( index > 88 ) index = 88;
step = stepsizeTable[index];
/* Step 6 - Output value */
if ( bufferstep != 0 ) {
outputbuffer = (delta << 4);
} else {
*outp++ = (char)(delta | outputbuffer);
count++;
}
bufferstep = !bufferstep;
}
/* Output last step, if needed */
if ( bufferstep == 0 )
{
*outp++ = (char)outputbuffer;
count++;
}
*outp++=s_valprev & 0xff; //save value prev
*outp++=(s_valprev >> 8) & 0xff;
*outp=s_index; //save index
count = count + 3;
s_valprev = (short)valpred;
s_index = (char)index;
return count;
}
音频数据解码接口:
int adpcm_decoder(unsigned char *indata, short *outdata, int len)
{
unsigned char *inp; /* Input buffer pointer */
short *outp; /* output buffer pointer */
unsigned int sign; /* Current adpcm sign bit */
unsigned int delta; /* Current adpcm output value */
unsigned int step; /* Stepsize */
int valpred; /* Predicted value */
unsigned int vpdiff;/* Current change to valpred */
int index; /* Current step change index */
unsigned int inputbuffer = 0;/* place to keep next 4-bit value */
int bufferstep; /* toggle between inputbuffer/input */
int count = 0;
short value_tmp;
char index_tmp;
outp = outdata;
inp = indata;
value_tmp = inp[len-3] | (inp[len-2] << 8); //recover value
index_tmp = inp[len - 1]; //recover index
len = len - 3;
valpred = value_tmp;
index = index_tmp;
step = stepsizeTable[index];
bufferstep = 0;
len *= 2;
while ( len-- > 0 ) {
/* Step 1 - get the delta value */
if ( bufferstep != 0 ) {
delta = inputbuffer & 0xf;
} else {
inputbuffer = (unsigned int)*inp++;
delta = (inputbuffer >> 4);
}
bufferstep = !bufferstep;
/* Step 2 - Find new index value (for later) */
index += indexTable[delta];
if ( index < 0 ) index = 0;
if ( index > 88 ) index = 88;
/* Step 3 - Separate sign and magnitude */
sign = delta & 8;
delta = delta & 7;
/* Phil Frisbie combined steps 4 and 5 */
/* Step 4 - Compute difference and new predicted value */
/* Step 5 - clamp output value */
/*
** Computes 'vpdiff = (delta+0.5)*step/4', but see comment
** in adpcm_coder.
*/
vpdiff = step >> 3;
if ( (delta & 4) != 0 ) vpdiff += step;
if ( (delta & 2) != 0 ) vpdiff += step>>1;
if ( (delta & 1) != 0 ) vpdiff += step>>2;
if ( sign != 0 )
{
valpred -= vpdiff;
if ( valpred < -32768 )
valpred = -32768;
}
else
{
valpred += vpdiff;
if ( valpred > 32767 )
valpred = 32767;
}
/* Step 6 - Update step value */
step = stepsizeTable[index];
/* Step 7 - Output value */
*outp++ = (short)valpred;
count++;
}
return count;
}
5.3 音频数据传输¶
PAN1080从机将采集的音频数据编码后通过BLE传输给PAN1080主机。
BLE从机上传数据的接口:
int proj_template_s2c_notify(const u8_t *data,u16_t len)
{
return bt_gatt_notify(NULL, &proj_temp.attrs[1], data, len);
}
BLE主机接收数据的接口:
static u8_t notify_func(struct bt_conn *conn,
struct bt_gatt_subscribe_params *params,
const void *data, u16_t length)
{
if (!data) {
printk("[UNSUBSCRIBED]\n");
params->value_handle = 0U;
return BT_GATT_ITER_STOP;
}
P20 = 1;
uint8_t *p_buf = (uint8_t *)decode_data;
decode_data_len = adpcm_decoder(data,decode_data,length);
ring_buf_put(&ringbuf_raw, p_buf, (decode_data_len * 2));
if((I2S_data_flag == 0) && (buf_count >= 2))
{
I2S_data_flag = 1;
I2S_EnableClk(TGT_IIS);
}
else
{
buf_count++;
}
P20 = 0;
return BT_GATT_ITER_CONTINUE;
}
5.4 音频数据播放¶
主机端PAN1080将解码后的音频数据通过I2S传输给CODEC芯片,此时PAN1080作I2S的MASTER,CODEC芯片作I2S的SLAVE,PAN1080将音频数据发送给CODEC芯片播放。
CODEC芯片初始化接口:
void I2S_HW_Init(void)
{
uint32_t data_len,cycle_cnt = 0;
printk("\nCPU @ %dHz,\n", SystemCoreClock);
I2S_GPIO_Init();
I2C_InitRole(I2C0,I2C_MODE_MASTER,ADR10BIT_DISABLE);
I2C_SetTxTirggerLevel(I2C0, I2C_TX_TL_1);
data_len = sizeof(codec8316_startup);
while(cycle_cnt < data_len)
{
I2C_Write(I2C0,codec8316_startup+cycle_cnt,2);
cycle_cnt += 2;
if((codec8316_startup[cycle_cnt-2] == 0x0)
&& (codec8316_startup[cycle_cnt-1] == 0xc0))
{
k_sleep(50);
}
if(cycle_cnt == 2)
{
k_sleep(5);
}
if(cycle_cnt == 6)
{
k_sleep(30);
}
}
I2S_DMA_Init();
}
主机端CODEC芯片初始化流程和从机端CODEC芯片初始化流程一样,不同的地方是CODEC芯片的配置,从机端配置成音频数据采集,主机端配置成音频数据播放。
PAN1080 DMA方式发送音频数据初始化接口:
void I2S_DMA_Init(void)
{
DMAC_ChannelConfigTypeDef TxConfigTmp = {0,};
CLK_APB1PeriphClockCmd(CLK_APB1Periph_I2SM,ENABLE);
CLK_APB1PeriphClockCmd(CLK_APB1Periph_I2SS,DISABLE);
I2S_ModduleInit();
I2S_SysInit();
DMAC_Init(DMA);
IRQ_DIRECT_CONNECT(DMA_IRQn, 0, DMA_IRQHandler, 0);
irq_enable(DMA_IRQn);
I2S_EnableI2s(TGT_IIS);
I2S_SetTxTrigLevel(TGT_IIS,0,4);
I2S_EnableTransmitCh(TGT_IIS,0);
I2S_EnableTransmitBlock(TGT_IIS);
/*set tx dma channel control&config register*/
TxConfigTmp.AddrChangeDst = DMAC_AddrChange_NoChange;
TxConfigTmp.AddrChangeSrc = DMAC_AddrChange_Increment;
TxConfigTmp.BurstLenDst = DMAC_BurstLen_8;
TxConfigTmp.BurstLenSrc = DMAC_BurstLen_8;
TxConfigTmp.DataWidthDst = DMAC_DataWidth_16;
TxConfigTmp.DataWidthSrc = DMAC_DataWidth_16;
TxConfigTmp.TransferType = DMAC_TransferType_Mem2Per;
TxConfigTmp.FlowControl = DMAC_FlowControl_DMA;
TxConfigTmp.HandshakeDst = DMAC_Handshake_Hardware;
TxConfigTmp.HandshakeSrc = DMAC_Handshake_Hardware;
TxConfigTmp.PeripheralDst = DMAC_Peripheral_IIS_Tx;
TxConfigTmp.IntEnable = ENABLE;
/*get free dma channel;*/
gDMATxChNum = DMAC_AcquireChannel(DMA);
/*enable dma transfer interrupt*/
DMAC_ClrIntFlagMsk(DMA,gDMATxChNum,DMAC_FLAG_INDEX_TFR);
DMAC_SetChannelConfig(DMA,gDMATxChNum,&TxConfigTmp);
//I2S_EnableClk(TGT_IIS);
I2S_Send_Enable();
}
CODEC播放频率可设置成8khz或16khz,播放频率设置接口:
void I2S_SetI2sClkDivider(uint16_t codec_freq)
{
CLK->APB1_CLK_CTRL1 &= ~0xFFFF;
CLK->APB1_CLK_CTRL1 |= codec_freq;
CLK->APB1_CLK_CTRL1 |= (3 << 16) | (3 << 18);
}
注:DMA中断优先级设成0,BLE中断优先级设成1。DMA中断优先级比BLE中断优先级高是为了防止BLE影响音频数据的播放从而导致音频数据播放不连续从而产生噪音。