爲了讓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
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等。
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以下。