CSR867x学习笔记:低音炮音频传输协议(SWAT)

为了让CSR867x的开发更容易,现与思度科技联合推出CSR867x学习板【淘宝链接:思度科技CSR开发板】。

技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠PPT、项目源码、视频教程)
——————————正文分割线———————————–

1. 引言

最近公司准备向客户推荐我们的soundbar+subwoofer整体解决方案。方案中bar和sub之间通过TWS无线蓝牙连接,soundbar作为master,subwoofer作为slave。

Demo试听过程中发现如下问题:

  • 蓝牙源的系统音频延迟接近700ms,AUX源的系统音频延迟接近400ms,而竞品的系统音频延迟不到200ms。
  • TWS不支持SPDIF输入源

咨询了原厂FAE,答复是TWS模式不适用于soundbar类型的产品,建议我们改用SWAT协议。

今天用运行ADK4.1的soundbar和ADK2.5.1的subwoofer体验了一把SWAT:

  • 在AUX等有线源的情况下,系统音频延迟有显著改善,基本上感觉不到。
  • 在蓝牙源SBC解码的情况下,系统音频延迟大约有300ms,优于TWS模式。

我们知道TWS模式下,master和slave之间的音频数据走的是A2DP协议,控制命令走的是AVRCP协议。同样是音频传输协议,为何SWAT能够显著缩短音频延迟呢?相信答案就在swat库的源码中,让我们一探究竟吧。

2. 代码整体框架

swat库源码在…\src\lib\swat路径下,乍一看文件还挺多:

  • swap_api.c/.h
  • swap_audio_manager.c/.h
  • swat_command_handler.c/.h
  • swat_device_manager.c/.h
  • swat_init.c/.h
  • swat_l2cap_handler.c/.h
  • swat_packet_handler.c/.h
  • swat_profile_handler.c/.h
  • swat_state_manager.c/.h
  • swat_service_record.h
  • swat.h

CSR867x学习笔记:低音炮音频传输协议(SWAT)_第1张图片

2.1. swat.h

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的自家协议。

2.2. swat_service_record.h

此头文件给出了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连接,一个用来传输控制信号,另一个用来传输媒体信号,这里即是低音通道的音频信号。

2.3. swat_state_manager.c/.h

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等有线源。

2.4. swat_profile_handler.c/.h

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里的函数执行消息处理函数。

2.5. swat_packet_handler.c/.h

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处理。

2.6. swat_l2cap_handler.c/.h

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等。
CSR867x学习笔记:低音炮音频传输协议(SWAT)_第2张图片

2.7. swat_init.c

swat_init.c里只有一个函数SwatInit。这个函数主要做了以下几件事:

  • 初始化swat结构体,包括挂接几个handler钩子函数swatL2capHandler、swatProfileHandler、swatCommandHandler
  • 注册service record(定义在swat_service_record.h)

2.8. swat_device_manager.c/.h

swat_device_manager.c中最主要的函数是swatAddDevice,其是对端设备的实例化。SWAT协议只支持一个对端设备。

soundbar在搜索到subwoofer后,尝试与其建立signalling通道的连接。此时soundbar和subwoofer会创建对方的实例。

2.9. swat_command_handler.c/.h

swat_command_handler.c中的swatCommandHandler函数处理来自swatL2capHandler的消息。

2.10. swat_audio_manager.c/.h

swat_audio_manager.c中的函数都是与esco连接有关,esco连接用在AUX等有线源的音频传输。

2.11. swat_api.c

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。

3. 为什么SWAT的延迟低?

当SWAT协议启用后,soundbar在传输sub信号前将sub信号的采样率从48KHz降低到了1.2KHz,从而大幅减少了sub通道的数据量,减少了传输前所需缓冲的数据量,降低了系统延迟。

在有线源的情况下,SWAT会启用LOW LATENCY模式。此模式使用esco同步信道传输音频数据,延迟优于L2CAP连接时的ACL信道。

4. 总结

SWAT可以降低soundbar的系统音频延迟,在有线源的情况下可以做到40ms,在蓝牙源的情况下可以做到200ms以下。

你可能感兴趣的:(蓝牙方案,CSR8670蓝牙芯片软件开发)