# 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`

![Component1](./_static/_images/component1.png)

![Component2](./_static/_images/component2.png)

`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"中。

![zone](./_static/_images/zone.png)

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

### SMF

![SMF](./_static/_images/SMF.png)

代码路径：`adsp/ssf/smf`

整体解决方案分为3个层次：通讯层、消息分发层、应用服务层。
1. 通讯层框架(SIPC)：
- 封装 SIPC Instance实例用来表示传输实例，下面可以有多个传输通道（Channel）。
- 一个核可以有多个通讯实例Instance。
- 封装SIPC Channel实例用来表示传输通讯通道。
2. 消息分发层框架(dispatcher)：
为services提供服务，不负责解析msg的内容，时序上线运行dispatcher运行services，services产生相应的待处理命令后，dispatcher再处理。
- 消息分发Dispatcher维护一个消息队列和一个分发线程。
- 接收SIPC消息，找到对应的services进行解析。
接受消息的时候会轮训services->category和msg->category通过category_id区分是哪个服务的消息。
- 靠常驻线程运行disp->msg_queue中的指令。
3. 应用服务层框架(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`传输，这样会使得核间通讯的接口尤为发散，从软件代码层面也会令软件框架比较难以兼容。

![IPC](./_static/_images/IPC.png)

Sipc 分为三层，
- Sipc interface - 对外提供某一个板级方案，用来控制创建多少个instance和channel 用来进行通讯，具体实现由sipc driver 实现。
- Sipc driver - 用来实现 sipc 接口，并调用 sipc channel和 sipc intance 的接口，创建instance和所需要的channel。
- Sipc transport - 用来实现 sipc channel 和 sipc instance 的接口，HB-IPC需要实现这一层。

<a id="routing_configuration"></a>
## 路由配置

路由配置文件在`/adsp/ssf/sgf/conf/src/config_ar_x5.c`文件中。
以现有的语音唤醒路由图为例。当前路由拓扑为上面`zone`中例图所示。

### Component配置描述

```C++
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的属性
```C++
/**
 * 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触发。
```C++
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的输入/输出端口，使用辅助宏定义：
```C++
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，以及端口信息。

```C++
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如下所示：

```c++
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`如下所示：

```c++
	{
		.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。

```c++
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统一。

```c++
#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` 中定义：

```c++
#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

见[路由配置](#routing_configuration)章节。

## A核与DSP数据通信

四路alsa pcm device介绍:
1. device0：SMF FE raw —— mic原始音频，包含回采。
2. device1：SMF FE ASR —— 一级唤醒后的ASR音频，连续的，有1秒的buffer。
3. device2：SMF FE history  ——一级唤醒，4秒的唤醒词音频，有四秒的缓冲。
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`即可。

```c++
#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工具分析代码。

例如在出现以下报错时：

![trace](./_static/_images/trace.png)

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