为了让CSR867x的开发更容易,现与思度科技联合推出CSR867x学习板【淘宝链接:思度科技CSR开发板】。
技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠PPT、项目源码、视频教程)
——————————正文分割线———————————–
最近公司准备向客户推荐我们的soundbar+subwoofer整体解决方案。方案中bar和sub之间通过TWS无线蓝牙连接,soundbar作为master,subwoofer作为slave。
Demo试听过程中发现如下问题:
咨询了原厂FAE,答复是TWS模式不适用于soundbar类型的产品,建议我们改用SWAT协议。
今天用运行ADK4.1的soundbar和ADK2.5.1的subwoofer体验了一把SWAT:
我们知道TWS模式下,master和slave之间的音频数据走的是A2DP协议,控制命令走的是AVRCP协议。同样是音频传输协议,为何SWAT能够显著缩短音频延迟呢?相信答案就在swat库的源码中,让我们一探究竟吧。
swat库源码在…\src\lib\swat路径下,乍一看文件还挺多:
swat.h文件包含了所有swat库中允许应用层访问的api函数声明、变量声明、消息ID声明、结构体声明。swat.h也给出了SWAT协议用到的L2CAP通道编号。
#define SWAT_SIGNALLING_PSM 0x8001 /* L2CAP PSM for the signalling channel */
#define SWAT_MEDIA_PSM 0x8003 /* L2CAP PSM for the media channel */
参考蓝牙官网的Logic Link Control页面,可见SWAT的两个PSM不在predefined L2CAP channel ID列表中。在谷歌搜索SWAT,找不到相关信息,基本上可以坐实SWAT是CSR的自家协议。
此头文件给出了swat协议的服务描述,截取最重要的信息:
/* Protocol Descriptor List */
0x09, 0x00, 0x04, /* AttrID ProtocolDescriptorList */
0x35, 0x08, /* "Data element sequence" (Sequence length 0x08) */
0x35, 0x06, /* "Data element sequence" (Sequence length = 0x06) */
0x19, 0x01, 0x00, /* "UUID" (UUID 0x0100 = L2CAP)*/
0x09, ((SWAT_SIGNALLING_PSM >> 8) & 0xff), (SWAT_SIGNALLING_PSM & 0xff), /* "Unsigned Integer" (Describes the L2CAP SIGNALLING PSM) */
/* Additional Protocol Descriptor List */
0x09, 0x00, 0x0d, /* AttrID Additional Protocol Descriptor List */
0x35, 0x08, /* "Data element sequence" (Sequence length = 0x08) */
0x35, 0x06, /* "Data element sequence" (Sequence length = 0x06) */
0x19, 0x01, 0x00, /* "UUID" (UUID 0x0100 = L2CAP)*/
0x09, ((SWAT_MEDIA_PSM >> 8) & 0xff), (SWAT_MEDIA_PSM & 0xff), /* "Unsigned Integer" Describes the L2CAP MEDIA PSM) */
可见SWAT协议需要用到2个L2CAP连接,一个用来传输控制信号,另一个用来传输媒体信号,这里即是低音通道的音频信号。
swat_state_manager.c/.h给出两个函数,用以设置signalling和media的状态。
signalling的状态:
/* Enum defining the SWAT signalling channel states */
typedef enum
{
/*00*/ swat_signalling_idle,
/*01*/ swat_signalling_remote_connecting,
/*02*/ swat_signalling_local_connecting,
/*03*/ swat_signalling_local_connecting_xover,
/*04*/ swat_signalling_connected,
/*05*/ swat_signalling_disconnecting
} swatSignallingState;
media的状态:
typedef enum
{
/*00*/ swat_media_closed = 0, /*< The media channel is closed (not active) */
/*01*/ swat_media_opening, /*< The media channel is in the process of opening */
/*02*/ swat_media_open, /*< The media chaneel is open, but not streaming */
/*03*/ swat_media_starting, /*< The media channel is in the process of starting to stream */
/*04*/ swat_media_streaming, /*< The media channel is open and contains streaming audio data */
/*05*/ swat_media_suspending, /*< The media channel is in the process of suspending a stream */
/*06*/ swat_media_closing, /*< The media channel is in the process of closing down */
/*ff*/ swat_media_invalid = 0xff /*< invalid value for swat media channel state */
} swatMediaState;
media的类型有两种:
typedef enum
{
SWAT_MEDIA_NONE = 0x0, /*< no media type */
SWAT_MEDIA_STANDARD = 0x1, /*< Standard latency mode */
SWAT_MEDIA_LOW_LATENCY = 0x2 /*< Low latency mode */
} swatMediaType;
STANDARD类型对应BT源,LOW_LATENCY类型对应AUX等有线源。
swat_profile_handler.c只定义了一个钩子函数swatProfileHandler。这个钩子函数在初始化swat时被挂在profile_task.handler上。
// swat_init.c
// SwatInit
swat->l2cap_task.handler = swatL2capHandler;
swat->profile_task.handler = swatProfileHandler;
swat->command_task.handler = swatCommandHandler;
应用层调用swat_api.c文件里的接口函数向swat_profile_handler发送消息,swat_profile_handler调用swat_command_handler.c、swat_l2cap_handler.c和swat_audio_handler.c里的函数执行消息处理函数。
swat_packet_handler.c只定义了swatSendData和swatHandleSwatSignallingData两个函数。
swatSendData只被swat_command_handler.c文件里的函数调用,作用是向l2cap sink stream中写入数据,写入的数据通过l2cap连接发送给另一端。在swat_l2cap_handler.c中有一个handleL2capConnectCfm函数,此函数在l2cap连接成功后将sink保存下来。
/* Which PSM was the message recieved on? */
if (cfm->psm_local == SWAT_SIGNALLING_PSM)
{
handleL2capConnectCfmSignalling(cfm->status, cfm->sink, device);
}
else if (cfm->psm_local == SWAT_MEDIA_PSM)
{
handleL2capConnectCfmMedia(cfm->status, cfm->sink, device);
}
swatHandleSwatSignallingData处理BlueCore层向swat库层提交的l2cap数据,数据被解析成SWAT_COMMAND发送给swat_command_handler.c处理。
swat_l2cap_handler.h声明了8个函数,这8个函数被swat_profile_handler.c、swat_api.c、swat_audio_manager.c、swat_command_handler.c、swat_init.c调用,可以看出swat_l2cap_handler.c处在swat代码架构的较低层。
那swat_l2cap_handler的下一层是哪里呢?答案就在swatL2capSignallingConnectResponse函数中:
void swatL2capSignallingConnectResponse(uint16 device_id, uint16 connection_id, uint8 identifier, bool accept)
{
/* If rejecting the signalling connection, also remove the device */
/* Don't remove the device if we are in local_connecting state as means it's the crossover connection from the SINK device that's being rejected */
if ( !accept && (swat->remote_devs[device_id].signalling_state != swat_signalling_local_connecting) )
{
/* Remove the device */
swatRemoveDevice(device_id);
}
ConnectionL2capConnectResponse(&swat->l2cap_task, accept, SWAT_SIGNALLING_PSM, connection_id, identifier, sizeof(swat_signalling_conftab), (uint16 *)swat_signalling_conftab);
}
ConnectionL2capConnectResponse函数在.src\lib\connection\l2cap.c文件中,connection库负责管理较低层级的蓝牙连接,例如RFCOMM、L2CAP、Bluetooth Smart等。
swat_init.c里只有一个函数SwatInit。这个函数主要做了以下几件事:
swat_device_manager.c中最主要的函数是swatAddDevice,其是对端设备的实例化。SWAT协议只支持一个对端设备。
soundbar在搜索到subwoofer后,尝试与其建立signalling通道的连接。此时soundbar和subwoofer会创建对方的实例。
swat_command_handler.c中的swatCommandHandler函数处理来自swatL2capHandler的消息。
swat_audio_manager.c中的函数都是与esco连接有关,esco连接用在AUX等有线源的音频传输。
swat_api.c中的函数被swat_l2cap_handler.c和swat_command_handler.c调用。
swat_api.c中的函数都是对消息的封装:
void swatSendInitCfmToClient(swat_status_code status)
{
MAKE_SWAT_MESSAGE(SWAT_INIT_CFM);
message->status = status;
MessageSend(swat->clientTask, SWAT_INIT_CFM, message);
/* If the initialisation failed, free the allocated task */
if (status != swat_success)
{
free(swat);
swat = 0;
}
}
这些消息最后都发送给了应用层的sink_swat.c。
当SWAT协议启用后,soundbar在传输sub信号前将sub信号的采样率从48KHz降低到了1.2KHz,从而大幅减少了sub通道的数据量,减少了传输前所需缓冲的数据量,降低了系统延迟。
在有线源的情况下,SWAT会启用LOW LATENCY模式。此模式使用esco同步信道传输音频数据,延迟优于L2CAP连接时的ACL信道。
SWAT可以降低soundbar的系统音频延迟,在有线源的情况下可以做到40ms,在蓝牙源的情况下可以做到200ms以下。