4.3.30.3. SSF中间件开发指南
基本概念
SSF= SGF(Smart Graph Framework)+ SMF(Smart Message Framework,其中包含SIPC)
代码路径:adsp/ssf
SGF
代码路径:adsp/ssf/sgf
SGF是音频子系统(HiFi5 DSP)的数据路由框架,实现以下功能:
1、音频下行链路,每一条链路满足可配置节点,可包含音效模块、驱动模块、通信模块等。
2、音频上行链路,每一条链路满足可配置节点,可包含音效模块、驱动模块、通信模块等。
SGF包含以下多个模块:
Module是dsp数据处理的基本模块。一个算法、一个驱动,只要在数据链路中处理音频数据,都可以算作是一个Module。
定义代码路径:adsp/ssf/sgf/ar/src/module.c
实现代码路径:adsp/ssf/sgf/modules
Component包含了Module,额外添加一系列用于处理数据路由的组件。Component是SGF通路的数据路由节点,能够接收多路数据,输出多路数据。
头文件路径:adsp/ssf/sgf/ar/include/comp.h
Data Port是Component数据的输入和输出接口,分为Input和Output。一个Component可以有0个或者多个Input/Output Port,根据配置定义。
头文件路径:adsp/ssf/sgf/ar/include/comp.h


Connector是用于连接Component,有传递数据、控制通路通断的功能,是SGF数据连接器。每两个Component之间需要有一个Connector连接。
包含Connector Base和Connector RB两种,前者是基本的Connector,仅传递数据指针;后者是带有Ringbuffer的Connector,能够缓冲一段数据。常用于多路数据的输入输出、不同节奏的业务之间的数据交互。
头文件路径:adsp/ssf/sgf/ar/include/conn.h
Graph是所有Component、以及相互之间的连接关系。
头文件路径:adsp/ssf/sgf/ar/include/graph_factory.h
代码路径:adsp/ssf/sgf/conf/src/config_ar_x5.c
Trigger被周期性触发,以运行Graph。
通常Trigger源可以来自于硬件:Timer Interrupt,I2S Interrupt,DMA Interrupt。
也可以来自于通讯中间件,由其他核来触发运行。
Zone用来划分不同的业务的Component,可以通过多线程进行处理一些数据节奏不一致的并发业务场景。
理论上,假设有n个component,理论上最多可以拥有n个zone,即每个component都在一个单独的zone中,但这样做会消耗大量的系统资源。
实际上,比较合理的方案是,”Zone”根据period_size、trigger源进行划分,同样的period size、同样的trigger应当放到一个zone中。
实际使用场景:
下行链路都是通过”I2S0 TX”输出,数据处理的period size一致,那么所有的下行链路都可被放在”Zone 1”中。
上行链路都是通过”I2S1 RX”输入,数据处理的period size一致,那么所有的上行链路都可被放在”Zone 2”中。

整个Graph的构建应遵循”使用尽量少的zone”的原则。
定义在comp_param结构体中的zone_id字段。
SMF

代码路径:adsp/ssf/smf
整体解决方案分为3个层次:通讯层、消息分发层、应用服务层。
通讯层框架(SIPC):
封装 SIPC Instance实例用来表示传输实例,下面可以有多个传输通道(Channel)。
一个核可以有多个通讯实例Instance。
封装SIPC Channel实例用来表示传输通讯通道。
消息分发层框架(dispatcher): 为services提供服务,不负责解析msg的内容,时序上线运行dispatcher运行services,services产生相应的待处理命令后,dispatcher再处理。
消息分发Dispatcher维护一个消息队列和一个分发线程。
接收SIPC消息,找到对应的services进行解析。 接受消息的时候会轮训services->category和msg->category通过category_id区分是哪个服务的消息。
靠常驻线程运行disp->msg_queue中的指令。
应用服务层框架(services)
每一个应用服务Service对接其他应用Application模块。
支持定制应用服务Service。
应用服务Service用来接收消息分发层Dispatcher发送过来的消息。
应用服务Service可以申请多个通讯通道Channel。 应用服务Service支持借助通讯通道Channel向不同的核发送消息。 可以注册多个service,挂在g_disp链上。
service解析后的进一步操作有如下两种:
1.control信息:service负责解析消息并通过smf_send_msg_async发送给dispatcher执行。
2.pipeline信息:直接执行pipeline动作,如open/start/triggerterminate/set_buf/set_param 不同的服务通过category_id区分。
SIPC
SIPC(Super Inter-Processor Communication) 是 GUA 芯片音频框架——智能通讯框架 SMF(Smart Msg Framework)的一个组成部分,用来封装不同IPC (比如 hb ipc)传输层,提供标准统一的接口供上层外部使用,使得软件框架可以从底层诸多IPC实现接口中脱离出来。
因为不同方案或者厂商会使用不同的IPC方式,也可能会有定制的IPC传输,这样会使得核间通讯的接口尤为发散,从软件代码层面也会令软件框架比较难以兼容。

Sipc 分为三层,
Sipc interface - 对外提供某一个板级方案,用来控制创建多少个instance和channel 用来进行通讯,具体实现由sipc driver 实现。
Sipc driver - 用来实现 sipc 接口,并调用 sipc channel和 sipc intance 的接口,创建instance和所需要的channel。
Sipc transport - 用来实现 sipc channel 和 sipc instance 的接口,HB-IPC需要实现这一层。
路由配置
路由配置文件在/adsp/ssf/sgf/conf/src/config_ar_x5.c文件中。
以现有的语音唤醒路由图为例。当前路由拓扑为上面zone中例图所示。
Component配置描述
struct comp_param {
uint32_t cuid;
char name[16];
uint32_t zone_id;
uint32_t rate;
uint32_t channel;
uint32_t bitdepth;
uint32_t period;
ele_attr_t attr;
uint32_t muid;
trigger_source_t trigger;
data_port_cfg_t in_port;
data_port_cfg_t out_port;
};
1.uint32_t cuid; Component Unique ID,使用辅助宏定义GEN_COMP_UID生成。
2.char name[16]; Component Name,注意最大size为16,字符串不超过15个字符。
3.uint32_t zone_id; 表明在哪一个域内,每个域为一个线程顺序处理。
4.uint32_t rate; Component能够处理的采样率。
5.uint32_t channel; Component能够处理的声道数。
6.uint32_t bitdepth; Component能够处理的位深。
7.uint32_t period; Component每帧处理的Periods。注:以sample点为单位。
8.ele_attr_t attr; Component的属性
/**
* element attribute enum
* which shows element belong to downlink or uplink
* and shows whether has thread
*/
typedef enum {
ELE_ATTR_DL = BIT(0),
ELE_ATTR_UL = BIT(1),
ELE_ATTR_THREAD = BIT(2),
ELE_ATTR_IL = BIT(5), /* interleaved */
ELE_ATTR_NIL = BIT(6), /* noninterleaved */
} ele_attr_t;
9.uint32_t muid; Module Unique ID,通过 GEN_MOD_UID 宏定义生成。
10.trigger_source_t trigger; 表明当前的Component可被哪个trigger触发。
typedef enum trigger_source {
TRIGGER_I2S0_DMA_TX,
TRIGGER_I2S0_DMA_RX,
TRIGGER_I2S1_DMA_TX,
TRIGGER_I2S1_DMA_RX,
TRIGGER_APP_START,
TRIGGER_APP_WRITE = TRIGGER_APP_START,
TRIGGER_SOURCE_BUFFER, /* Use source block ringbuffer to trigger process*/
} trigger_source_t;
10.data_port_cfg_t in_port;
11.data_port_cfg_t out_port; Component的输入/输出端口,使用辅助宏定义:
DEF_NONE_PORT // 没有端口
DEF_SINGLE_PORT(2) // 定义一个使用2个连续channel的端口
// 定义多个端口,每个端口使用 PORT(index, offset, cnt) 宏定义进行描述,连续的通道数
// PORT的第一个参数是port id,第二个是起始通道,第三个是通道数。
// (0,0,2)代表id为1的输出,会输出从0开始计数的两个通道(数组从0开始计数)。即第一、第二通道。
DEF_MULTI_PORTS(
PORT(0, 0, 2),
PORT(1, 2, 4)
)
// PORT_MASK定义离散的通道数,如71 = 1000111 代表输出1237四个通道
DEF_MULTI_PORTS(
PORT_MASK(0, 0, 71),
PORT_MASK(1, 0, 71),
PORT_MASK(2, 0, 71)
)
Link关系描述
一个link描述了source component cuid、sink component cuid,以及端口信息。
struct link_param {
uint32_t source_id;
uint32_t sink_id;
struct {
conn_attr_t conn_attr;
uint32_t default_status;
uint16_t sink_port_id;
uint16_t source_port_id;
} conn_param;
};
其中,component cuid为在Component配置中所定义的cuid。sink_port_id/source_port_id分别为sink component和source component所定义的端口id。
具体示例
以录音原始音频pipeline0为例。根据拓扑图,pipeline如下所示:
static link_param_t capture_links[] = {
{
.source_id = CUID_I2S_RX,
.sink_id = CUID_CAPTURE_DEVICE_SPLITTER,
.conn_param = {
.conn_attr = CONN_ATTR_BASE,
.default_status = CONN_STATUS_LINKED | CONN_STATUS_SWITCH_ON,
.source_port_id = PORT_ID(0),
.sink_port_id = PORT_ID(0),
},
},
{
.source_id = CUID_CAPTURE_DEVICE_SPLITTER,
.sink_id = CUID_FE_NORMAL_RECORD,
.conn_param = {
.conn_attr = CONN_ATTR_RB,
.default_status = CONN_STATUS_LINKED,
.source_port_id = PORT_ID(1), // 从源的port1输入
.sink_port_id = PORT_ID(0), // 送入
},
},
};
涉及的Component如下所示:
{
.cuid = CUID_I2S_RX,
.name = "i2s-rx",
.zone_id = 1,
.rate = 16000,
.channel = 8, // 1100 1111 从低到高,四路mic 2路ref,当前音频板为8通道
.bitdepth = PLATFORM_BIT_DEPTH,
.period = 256,
.attr = ELE_ATTR_UL | ELE_ATTR_IL | ELE_ATTR_THREAD,
.muid = MUID_I2S_RX,
.trigger = TRIGGER_I2S1_DMA_RX,
.in_port = DEF_NONE_PORT,
.out_port = DEF_SINGLE_PORT(8),
},
{
.cuid = CUID_CAPTURE_DEVICE_SPLITTER,
.name = "cp_splitter",
.zone_id = 1,
.rate = 16000,
.channel = 8,
.bitdepth = PLATFORM_BIT_DEPTH,
.period = 256,
.attr = ELE_ATTR_UL | ELE_ATTR_NIL,
.muid = MUID_SPLITTER_2,
.trigger = TRIGGER_SOURCE_BUFFER,
.in_port = DEF_SINGLE_PORT(8),
.out_port = DEF_MULTI_PORTS(
PORT_MASK(0, 0, 71), // 0100 0111 3mic + 1ref port0
PORT_MASK(1, 0, 79), // 0100 1111 4mic + 1ref port1
PORT_MASK(2, 0, 71) // 暂无使用
),
},
{
.cuid = CUID_FE_NORMAL_RECORD,
.name = "Normal_Record",
.zone_id = 1,
.rate = 16000,
.channel = 5, // 5路音频
.bitdepth = PLATFORM_BIT_DEPTH,
.period = 256,
.attr = ELE_ATTR_UL | ELE_ATTR_IL,
.muid = MUID_FE_UL_1,
.trigger = TRIGGER_I2S1_DMA_RX,
.in_port = DEF_SINGLE_PORT(5), // 5路音频
.out_port = DEF_NONE_PORT,
}
算法移植指南
中间件集成了低功耗和正常功耗下的地瓜智能语音前端(HISF)算法。通过两个module将算法分别集成,并接入中间件的路由通路。
编写module
在SGF框架中,所有的数据处理模块需要实现Module的接口,才能配合框架层使用。因此算法移植第一步需要依照Module接口说明,实现数据处理module。
static struct module_desc mod_hisf_lp = {
.mtid = MTID_HISF_LP,
.name = "HISF_LP",
.ops = {
.cmd = hisf_cmd,
.process = hisf_process,
.stack_size = hisf_get_stackreq,
.create = hisf_create,
},
};
common_module_register(mod_hisf_lp);
主要实现module的 cmd/process 接口,其他的接口根据module说明按需实现。
module放在 adsp\ssf\sgf\plugin\src目录下。
算法自身实现放在adsp\ssf\algo目录下。
定义MTID (Module Type ID)
MTID是用来区分不同Module的标识符,使用7bit来表示(0 ~ 127)。
MTID定义位置可放在adsp\ssf\sgf\conf\include\config_common.h内,也可自行存放,需要保证config和module看到的ID统一。
#define MTID_HISF_LP (0x45)
#define MTID_HISF_NORMAL (0x46)
将定义好的MTID填充到Module Description结构体中。
添加文件到Makefile
将module以及相关文件,添加到DSP SSF的Makefile中。 SSF Makefile位于adsp\ssf\Makefile:
MOD_LIB_SRC += mod_hisf_lp.c
定义CUID/MUID
算法移植后,需要放到Component中,并配置到Graph中才会被调用。
在 adsp\ssf\sgf\conf\include\config_ar_x5.h 中定义:
#define CUID_HISF_LP GEN_COMP_UID(CTID_BASE, MTID_HISF_LP, 0x1)
#define CUID_HISF_NORMAL GEN_COMP_UID(CTID_BASE, MTID_HISF_NORMAL, 0x1)
#define MUID_HISF_LP GEN_MOD_UID(MTID_HISF_LP, 0x1)
#define MUID_HISF_NORMAL GEN_MOD_UID(MTID_HISF_NORMAL, 0x1)
配置Graph
见路由配置章节。
A核与DSP数据通信
四路alsa pcm device介绍:
device0:SMF FE raw —— mic原始音频,包含回采。
device1:SMF FE ASR —— 一级唤醒后的ASR音频,连续的,有1秒的buffer。
device2:SMF FE history ——一级唤醒,4秒的唤醒词音频,有四秒的缓冲。
device4:SMF FE hisf normal —— 正常功耗预处理后音频,包含送唤醒sdk的enhanced音频和送vad sdk的asr音频。
调试指南
Log
基本log打印,log level可在adsp/ssf/sysdeps/xos/include/sys_log_cfg.h设置。
修改SYS_LOG_OUTPUT_LVL即可。
#define SYS_LOG_LVL_ASSERT 0
#define SYS_LOG_LVL_ERROR 1
#define SYS_LOG_LVL_WARN 2
#define SYS_LOG_LVL_INFO 3
#define SYS_LOG_LVL_DEBUG 4
#define SYS_LOG_LVL_VERBOSE 5
#define SYS_LOG_LVL_TOTAL_NUM 6
Backtrace
xt-addr2line 工具。
xt-addr2line 是一个用于将地址转换为源代码位置的命令行工具。通常情况下,它用于分析程序崩溃时的核心转储文件或堆栈跟踪信息,以便找到崩溃位置对应的源代码行数和文件名。这在软件调试和故障排除中非常有用。
在adsp目录中使用source env_hf5.sh后即可使用xt-addr2line工具分析代码。
例如在出现以下报错时:

可以使用:
xt-addr2line -e output/adsp -fpiC [地址]
以获取报错堆栈信息。