DSP 算法框架架构设计 v7.0
与 v6.0 的核心变化(Q1 深化对齐 AWE) - Q1.1 WireInstance 简化:去掉
ppChannel/channels/blockSize等独立字段, 改用wireInfo1/wireInfo2/wireInfo3三个位域(完全对齐 AWEfw_Wire.h), 通道指针通过Wire_ChanPtr(w, ch)宏实时计算(buffer + ch × blockSize) - Q1.2 ModuleInstance 重设计:加入typeName[24]/typeIndex(AWE 类型名 + 序号),pInWires[]+pOutWires[]合并为 单数组pWires[](输入在前、输出在后), 新增profileTime(AWEModuleInstanceDescriptor.profileTime对应),instanceId不再独立存储,按需从typeName#typeIndex构造 - Q1.3 所有模块接口统一为ModuleInstance*:GetMemSize/Init/Process/SetParam/GetParam全部改为以ModuleInstance*为第一参数,ModulePortCfg/PortSpec/WireCfg结构体整体删除, Init 直接从pInst->pWires[i]->wireInfo读格式,无中间结构 - Q1.4 DynChain_Init 8步新流程: 先填 WireInstance 格式(buffer=NULL)→ 绑 pWires[] → 拓扑排序 →getMemSize(inst)→ alloc pState → alloc wire buffers →init(inst)与 v5.0 相比的全部变化(Q1~Q4) - Q2 SetParam/GetParam 简化:去掉
channelIdx,接口仅保留(pInst, paramId, pData, dataSize)- Q3 dynchain_core.c 实现:链路解析与执行引擎示例 - Q4 ID 体系说明:typeNumId / typeName / instanceId三者含义与完整链路
1. 三层架构总览
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 3 — DynamicChain 框架(audio_chain / dynchain) │
│ │
│ dynchain_core.c 链路状态机、Process 执行引擎 │
│ dynchain_registry.c ModuleRegistry 模块注册表 │
│ dynchain_parser.c ParseLinkFrame:解析帧 → 构建链路 │
│ dynchain_topo.c Kahn BFS 拓扑排序 │
│ module_registry_all.c 统一注册入口(可裁剪开关) │
│ │
│ 公开 API:DynChain_GetMemSize / DynChain_Init / DynChain_Process │
│ DynChain_SetParam / DynChain_GetParam / DynChain_Rebuild│
├─────────────────────────────────────────────────────────────────────┤
│ Layer 2 — 平台抽象层(platform) │
│ │
│ dynchain_platform.h 抽象接口定义(alloc/free/reset/log) │
│ platform_static.c 静态内存实现(裸机DSP,线性分配器) │
│ platform_dynamic.c 动态内存实现(PC仿真/有OS,malloc/free) │
│ platform_dsp_xxx.c 目标平台具体实现(DRAM段配置) │
│ │
│ 职责:决定内存来自哪里(SRAM/DRAM/heap),算法不感知物理地址 │
├─────────────────────────────────────────────────────────────────────┤
│ Layer 1 — 模块实现层(modules) │
│ │
│ gain/gain_module.c 自研增益模块 │
│ delay/delay_module.c 自研延迟模块 │
│ xisnddemom/ │
│ xisnddemom_module.c AWE 适配层(Phase 1) │
│ xisnddemom_direct.c 直接实现(Phase 2,脱离 AWE) │
│ eq/eq_module.c 均衡器模块 │
│ ... │
│ │
│ 每个模块只需实现 5 个标准接口函数,不关心内存来自哪里 │
└─────────────────────────────────────────────────────────────────────┘
2. 目录结构
dspalgo/
├── include/ ← 公开头文件(所有层共用)
│ ├── dynchain_types.h ← 基础类型:WireInstance(wireInfo + buffer)
│ ├── dynchain_platform.h ← 平台抽象接口
│ ├── dynchain_interface.h ← DynamicChain 公开 API
│ ├── module_interface.h ← ModuleFuncTable 定义
│ ├── module_type_id.h ← 模块类型 ID 常量
│ └── modules_config.h ← 模块裁剪编译开关
│
├── framework/ ← Layer 3:DynamicChain 框架
│ ├── dynchain_core.c
│ ├── dynchain_registry.c
│ ├── dynchain_parser.c
│ ├── dynchain_topo.c
│ └── module_registry_all.c
│
├── platform/ ← Layer 2:平台适配
│ ├── platform_static.c ← 嵌入式裸机(线性分配器)
│ ├── platform_dynamic.c ← PC 仿真(malloc/free)
│ └── platform_dsp_target.c ← 目标 DSP 平台(DRAM 配置)
│
└── modules/ ← Layer 1:各模块实现
├── gain/
│ ├── gain_module.h
│ └── gain_module.c
├── delay/
│ ├── delay_module.h
│ └── delay_module.c
└── xisnddemom/
├── xisnddemom_module.h
├── xisnddemom_module.c ← Phase 1: AWE 适配层
└── xisnddemom_direct.c ← Phase 2: 脱离 AWE 的直接实现
3. Layer 2:平台抽象层
3.1 dynchain_platform.h
/* dynchain_platform.h
* 平台抽象接口:将内存分配、日志等平台相关操作从 DynamicChain 框架中解耦。
* 嵌入式 DSP 提供静态实现,PC 仿真提供 malloc/free 实现。
*/
#ifndef DYNCHAIN_PLATFORM_H
#define DYNCHAIN_PLATFORM_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ---- 日志级别 ---- */
#define DYNCHAIN_LOG_DEBUG 0
#define DYNCHAIN_LOG_INFO 1
#define DYNCHAIN_LOG_WARN 2
#define DYNCHAIN_LOG_ERROR 3
/* ---- 平台操作函数指针表 ---- */
typedef struct DynChainPlatformOps_ {
/**
* alloc: 分配 size 字节,按 align 字节对齐(align 通常为 4 或 8)。
* 嵌入式:从线性 DRAM 池递增分配;PC:malloc。
* 返回 NULL 表示失败。
*/
void* (*alloc)(void *pCtx, uint32_t size, uint32_t align);
/**
* free: 释放单块内存。
* 嵌入式静态分配器:本函数为空操作(no-op),通过 reset() 整体释放。
* PC 动态分配器:调用 free()。
*/
void (*free)(void *pCtx, void *ptr);
/**
* reset: 整体释放本次链路分配的所有内存(链路重建时调用)。
* 嵌入式:将线性分配偏移归零;PC:释放所有已分配块。
*/
void (*reset)(void *pCtx);
/**
* log: 日志输出。level 对应 DYNCHAIN_LOG_* 常量。
* 嵌入式:可接入 UART 打印;PC:printf 或 OutputDebugString。
*/
void (*log)(void *pCtx, int level, const char *fmt, ...);
/** pCtx: 调用方自定义上下文,作为每个函数的第一个参数透传。 */
void *pCtx;
} DynChainPlatformOps;
#ifdef __cplusplus
}
#endif
#endif /* DYNCHAIN_PLATFORM_H */
3.2 platform_static.c(嵌入式静态内存实现)
/* platform_static.c
* 裸机 DSP 静态内存平台实现。
* 所有内存来自编译期固定的 DRAM 段,通过线性分配器管理,无碎片。
*/
#include "dynchain_platform.h"
#include <string.h>
/* 将此段放置到外部 DRAM(在 linker script 中配置地址)*/
#define PLATFORM_POOL_SIZE (16u * 1024u * 1024u) /* 16 MB,按项目调整 */
static uint8_t s_pool[PLATFORM_POOL_SIZE] __attribute__((section(".dram_algo")));
static uint32_t s_offset = 0;
static void* StaticAlloc(void *pCtx, uint32_t size, uint32_t align) {
(void)pCtx;
/* 对齐处理 */
uint32_t aligned = (s_offset + align - 1u) & ~(align - 1u);
if (aligned + size > PLATFORM_POOL_SIZE) {
return NULL; /* 内存不足 */
}
void *p = &s_pool[aligned];
s_offset = aligned + size;
memset(p, 0, size);
return p;
}
static void StaticFree(void *pCtx, void *ptr) {
/* 静态分配器不支持单块释放,整体通过 reset() 归零 */
(void)pCtx; (void)ptr;
}
static void StaticReset(void *pCtx) {
(void)pCtx;
s_offset = 0;
/* 可选:清零整个 pool */
/* memset(s_pool, 0, PLATFORM_POOL_SIZE); */
}
static void StaticLog(void *pCtx, int level, const char *fmt, ...) {
(void)pCtx; (void)level; (void)fmt;
/* 接入平台 UART 打印:va_list + uart_printf */
}
/* 导出的平台操作实例 */
static DynChainPlatformOps s_staticPlatform = {
.alloc = StaticAlloc,
.free = StaticFree,
.reset = StaticReset,
.log = StaticLog,
.pCtx = NULL,
};
const DynChainPlatformOps* Platform_GetStaticOps(void) {
return &s_staticPlatform;
}
3.3 platform_dynamic.c(PC 仿真动态内存实现)
/* platform_dynamic.c
* PC 仿真 / 有 OS 平台的动态内存实现(malloc/free)。
*/
#include "dynchain_platform.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
static void* DynAlloc(void *pCtx, uint32_t size, uint32_t align) {
(void)pCtx; (void)align;
void *p = malloc(size);
if (p) memset(p, 0, size);
return p;
}
static void DynFree(void *pCtx, void *ptr) {
(void)pCtx;
free(ptr);
}
static void DynReset(void *pCtx) {
/* 动态平台无需整体 reset,各模块内存在 DynChain_Rebuild 中逐一 free */
(void)pCtx;
}
static void DynLog(void *pCtx, int level, const char *fmt, ...) {
(void)pCtx;
static const char *kLvl[] = {"DBG","INF","WRN","ERR"};
printf("[DynChain][%s] ", (level >= 0 && level <= 3) ? kLvl[level] : "???");
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf("\n");
}
static DynChainPlatformOps s_dynPlatform = {
.alloc = DynAlloc,
.free = DynFree,
.reset = DynReset,
.log = DynLog,
.pCtx = NULL,
};
const DynChainPlatformOps* Platform_GetDynamicOps(void) {
return &s_dynPlatform;
}
4. Layer 3:DynamicChain 框架
4.1 dynchain_types.h(基础类型)
/* dynchain_types.h
* 基础数据类型:WireDataType / WireInstance。
*
* 设计原则(v7.0 对齐 AWE):
* WireInstance 字段与 AWE fw_Wire.h 一一对应:
* buffer / sampleRate / wireInfo1 / wireInfo2 / wireInfo3
* 去掉独立的 channels/blockSize/ppChannel 字段,
* 全部通过 Wire_* 宏从 wireInfo1/2 解包读取,通道指针由 Wire_ChanPtr 宏实时计算。
* 去掉 WireCfg / PortSpec / ModulePortCfg:
* getMemSize / init 改为接收 ModuleInstance*,直接读取 pWires[] 中的格式信息。
*/
#ifndef DYNCHAIN_TYPES_H
#define DYNCHAIN_TYPES_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ════════════════════════════════════════════════════════════════
* Wire 数据类型枚举(对应 AWE wireInfo2[22:17])
* ════════════════════════════════════════════════════════════════ */
typedef enum WireDataType_ {
WIRE_DATA_INT16 = 0,
WIRE_DATA_INT24 = 1,
WIRE_DATA_INT32 = 2,
WIRE_DATA_FRACT32 = 3, /* Q31 定点 */
WIRE_DATA_FLOAT32 = 4, /* 单精度浮点(框架内部默认格式)*/
} WireDataType;
/* ════════════════════════════════════════════════════════════════
* WireInstance:一条音频信号线(对齐 AWE fw_Wire.h WireInstance)
*
* 每条连接对应一个 WireInstance,框架在 DynChain_Init 时分配。
* 输出端模块"拥有"buffer(写入);输入端直接持有上游输出 wire 的指针(零拷贝)。
*
* 字段与 AWE 对照:
* buffer ↔ buffer(AWE Sample*,此处 float*)
* sampleRate ↔ sampleRate
* wireInfo1 ↔ wireInfo1 [9:0]=channels [26:10]=maxBlockSize [27]=isComplex [31:28]=sampleSzBytes
* wireInfo2 ↔ wireInfo2 [16:0]=blockSize [22:17]=dataType
* wireInfo3 ↔ wireInfo3 [20]=isIPC [21]=isPrivate
*
* 去掉 ppChannel 数组:通道指针用 Wire_ChanPtr(w, ch) 宏实时计算,
* 等价于 &buffer[ch * blockSize],无需额外内存且与 AWE 一致。
* ════════════════════════════════════════════════════════════════ */
typedef struct WireInstance_ {
float *buffer; /* 连续音频缓冲区:channels × blockSize 个 float */
float sampleRate; /* 采样率(Hz) */
uint32_t wireInfo1; /* 位域:[9:0]=ch [26:10]=maxBlk [27]=cmplx [31:28]=szBytes */
uint32_t wireInfo2; /* 位域:[16:0]=blockSize [22:17]=dataType */
uint32_t wireInfo3; /* 位域:[20]=isIPC [21]=isPrivate 等 */
} WireInstance;
/* ---- wireInfo 构造宏(Init 阶段填充格式时使用)---- */
#define Wire_MakeInfo1(ch, maxBlk, cmplx, szBytes) \
(((uint32_t)(ch) & 0x3FFu) | (((uint32_t)(maxBlk) & 0x1FFFFu) << 10) | \
(((uint32_t)(cmplx) & 1u) << 27) | (((uint32_t)(szBytes) & 0xFu) << 28))
#define Wire_MakeInfo2(blk, dtype) \
(((uint32_t)(blk) & 0x1FFFFu) | (((uint32_t)(dtype) & 0x3Fu) << 17))
/* ---- 格式读取宏(对应 AWE ClassWire_GetChannelCount 等)---- */
#define Wire_Channels(w) ((w)->wireInfo1 & 0x3FFu)
#define Wire_MaxBlockSize(w) (((w)->wireInfo1 >> 10) & 0x1FFFFu)
#define Wire_BlockSize(w) ((w)->wireInfo2 & 0x1FFFFu)
#define Wire_DataType(w) ((WireDataType)(((w)->wireInfo2 >> 17) & 0x3Fu))
#define Wire_IsComplex(w) (((w)->wireInfo1 >> 27) & 1u)
#define Wire_SampleSzBytes(w) (((w)->wireInfo1 >> 28) & 0xFu)
#define Wire_SampleRate(w) ((w)->sampleRate)
/* 通道数据指针(替代 ppChannel 数组,实时计算,无额外内存开销)
* Wire_ChanPtr(w, ch) == &w->buffer[ch * blockSize] */
#define Wire_ChanPtr(w, ch) ((w)->buffer + (ch) * Wire_BlockSize(w))
#ifdef __cplusplus
}
#endif
#endif /* DYNCHAIN_TYPES_H */
4.2 module_interface.h(模块接口函数表)
/* module_interface.h
* 每个算法模块必须实现的 5 个标准接口函数,通过 ModuleFuncTable 注册到框架。
*
* v7.0 变化(对齐 AWE ModClassModule 设计):
* 所有接口第一个参数统一改为 ModuleInstance*,与 AWE 传递 void*pInstance 对齐。
* GetMemSize / Init 不再依赖独立的 ModulePortCfg,模块直接从 pInst->pWires[] 读格式。
* Process → (ModuleInstance*) 通过 ModInst_GetInWire/OutWire + Wire_ChanPtr 访问 I/O
* SetParam → (ModuleInstance*, paramId, pData, dataSize) 无 channelIdx(同 v6.0)
* GetParam → 同上
*/
#ifndef MODULE_INTERFACE_H
#define MODULE_INTERFACE_H
#include "dynchain_types.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ModuleInstance 前向声明(定义在 dynchain_core.h)*/
struct ModuleInstance_;
typedef struct ModuleInstance_ ModuleInstance;
/* ─────────────────────────────────────────────────────────────────
* 函数指针类型定义
* ───────────────────────────────────────────────────────────────── */
/**
* GetMemSize:返回模块私有状态所需的内存字节数。
*
* 模块从 pInst->pWires[0..numInWires-1] 读取输入 wire 格式:
* Wire_Channels(pInst->pWires[0]) → 通道数
* Wire_BlockSize(pInst->pWires[0]) → 帧长
* Wire_SampleRate(pInst->pWires[0]) → 采样率
*
* 注意:此时 pInst->pWires[i]->buffer == NULL,仅格式字段有效。
* 约束:纯函数,不分配内存,不修改任何状态。
*/
typedef uint32_t (*ModuleGetMemSizeFn)(const ModuleInstance *pInst);
/**
* Init:在 pInst->pState 指向的内存上原地初始化模块状态。
*
* 调用时机:pState 已由框架分配(大小 >= GetMemSize 返回值),
* pWires[i]->buffer 已分配,格式和缓冲区均就绪。
* 模块不得在此函数内再次调用 malloc/alloc。
*/
typedef int32_t (*ModuleInitFn)(ModuleInstance *pInst);
/**
* Process:实时音频处理,每帧调用一次。(对应 AWE pProcessFunc)
*
* 通过 pInst 获取一切信息,无需额外参数:
* ModInst_GetInWire(pInst, 0) → 输入 wire(指向上游输出 wire,零拷贝)
* ModInst_GetOutWire(pInst, 0) → 输出 wire(本模块拥有 buffer)
* Wire_Channels / Wire_BlockSize → 格式
* Wire_ChanPtr(w, ch) → 第 ch 通道数据指针
*
* 示例(1 输入 1 输出):
* WireInstance *inW = ModInst_GetInWire(pInst, 0);
* WireInstance *outW = ModInst_GetOutWire(pInst, 0);
* uint32_t ch = Wire_Channels(inW), blk = Wire_BlockSize(inW);
* for (c=0; c<ch; c++)
* for (n=0; n<blk; n++)
* Wire_ChanPtr(outW,c)[n] = Wire_ChanPtr(inW,c)[n] * gain;
*
* 约束:严禁内存分配,严禁阻塞,执行时间必须确定性。
*/
typedef void (*ModuleProcessFn)(ModuleInstance *pInst);
/**
* SetParam:设置参数。(无 channelIdx,同 v6.0)
*
* @param pInst 模块实例(可通过 pInst->pState 访问私有状态)
* @param paramId 参数类型 ID(PARAM_XXX_YYY 定义在各模块头文件)
* @param pData 参数数据,格式由模块内部定义:
* 标量:float* 多通道:{uint16_t chIdx; float val;} 等
* @param dataSize pData 的字节数
*/
typedef int32_t (*ModuleSetParamFn)(
ModuleInstance *pInst,
uint16_t paramId,
const void *pData,
uint32_t dataSize
);
/**
* GetParam:读取参数,结果写入 pBuf。(同 SetParam 设计原则)
*/
typedef int32_t (*ModuleGetParamFn)(
ModuleInstance *pInst,
uint16_t paramId,
void *pBuf,
uint32_t bufSize
);
/** Destroy:释放模块内部动态资源(静态分配器场景设为 NULL)。*/
typedef void (*ModuleDestroyFn)(ModuleInstance *pInst);
/* ─────────────────────────────────────────────────────────────────
* ModuleFuncTable:注册到 ModuleRegistry 的函数表
* 对应 AWE ModClassModule 中的函数指针集合
* ───────────────────────────────────────────────────────────────── */
typedef struct ModuleFuncTable_ {
ModuleGetMemSizeFn getMemSize;
ModuleInitFn init;
ModuleProcessFn process; /* 对应 AWE pProcessFunc */
ModuleSetParamFn setParam; /* 对应 AWE pSet */
ModuleGetParamFn getParam; /* 对应 AWE pGet */
ModuleDestroyFn destroy; /* 可为 NULL */
} ModuleFuncTable;
/* ---- 返回值常量 ---- */
#define DYNCHAIN_OK 0
#define DYNCHAIN_ERR_INVALID -1
#define DYNCHAIN_ERR_MEMORY -2
#define DYNCHAIN_ERR_NOT_FOUND -3
#define DYNCHAIN_ERR_NOT_INIT -4
#define DYNCHAIN_ERR_INVALID_PARAM -5
#define DYNCHAIN_ERR_BUSY -6 /* 链路正在重建中 */
#ifdef __cplusplus
}
#endif
#endif /* MODULE_INTERFACE_H */
4.3 module_type_id.h(模块类型 ID)
/* module_type_id.h
* 模块类型 ID 常量。高 16 位为功能分类,低 16 位为模块编号。
* 与后端 TypeNameToNumId() 映射表保持一致。
*/
#ifndef MODULE_TYPE_ID_H
#define MODULE_TYPE_ID_H
/* 增益类 0x1001xxxx */
#define MODULE_TYPE_CHANNEL_GAIN_V1 0x10010001u
#define MODULE_TYPE_UT_GAIN_20CH_V1 0x10010002u
/* 延迟类 0x1002xxxx */
#define MODULE_TYPE_UT_DELAY_20CH_V1 0x10020001u
/* 滤波/EQ 类 0x1003xxxx */
#define MODULE_TYPE_COMMON_EQ_V1 0x10030001u
/* 动态处理类 0x1004xxxx */
#define MODULE_TYPE_LIMITER_V1 0x10040001u
#define MODULE_TYPE_MDRC_V1 0x10040002u
/* 混音类 0x1005xxxx */
#define MODULE_TYPE_MIXER_V1 0x10050001u
/* 算法平台类 0x1007xxxx */
#define MODULE_TYPE_XISNDDEMOM_V1 0x10070001u
/* 信号源类 0x1008xxxx */
#define MODULE_TYPE_SOURCE_V1 0x10080001u
#endif /* MODULE_TYPE_ID_H */
4.4 modules_config.h(编译裁剪开关)
/* modules_config.h
* 编译期模块裁剪开关。设为 0 的模块不会链入最终二进制。
* 嵌入式固件只开启需要的模块,PC 仿真可全部开启。
*/
#ifndef MODULES_CONFIG_H
#define MODULES_CONFIG_H
#define DYNCHAIN_ENABLE_CHANNEL_GAIN 1
#define DYNCHAIN_ENABLE_UT_GAIN_20CH 1
#define DYNCHAIN_ENABLE_UT_DELAY_20CH 1
#define DYNCHAIN_ENABLE_COMMON_EQ 1
#define DYNCHAIN_ENABLE_LIMITER 0 /* 本项目暂不需要 */
#define DYNCHAIN_ENABLE_MDRC 0
#define DYNCHAIN_ENABLE_MIXER 1
#define DYNCHAIN_ENABLE_XISNDDEMOM 1
#define DYNCHAIN_ENABLE_SOURCE 1
#endif /* MODULES_CONFIG_H */
4.5 dynchain_interface.h(DynamicChain 公开 API)
/* dynchain_interface.h
* DynamicChain 框架对外暴露的公开 API。
* 嵌入式固件和 PC DLL 均使用此接口,不感知平台差异。
*/
#ifndef DYNCHAIN_INTERFACE_H
#define DYNCHAIN_INTERFACE_H
#include "dynchain_types.h"
#include "dynchain_platform.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ---- DynamicChain 句柄(不透明指针)---- */
typedef struct DynamicChain_ DynamicChain;
/* ════════════════════════════════════════════════════════════════
* 阶段一:内存查询
* ════════════════════════════════════════════════════════════════ */
/**
* DynChain_GetMemSize
* 解析链路帧,计算运行所需的总内存字节数(不执行任何分配)。
*
* @param pLinkFrame 后端下发的 Binary Frame v3 数据指针
* @param frameLen 帧字节长度
* @param pSizeOut 输出:所需总内存字节数
* @return DYNCHAIN_OK 或错误码
*
* 内部逻辑:
* 1. 解析帧头,得到模块列表和连接表
* 2. 构造临时 ModuleInstance(pWires[] 已绑定 wireInfo,buffer=NULL)
* 3. 对每个模块:sum += def->getMemSize(pInst) ← 模块从 pInst->pWires 读格式
* 4. 对每个 output wire:sum += ch × blk × sizeof(float) (输出缓冲)
* 5. sum += sizeof(DynamicChain) + 元数据
*/
int32_t DynChain_GetMemSize(
const uint8_t *pLinkFrame,
uint32_t frameLen,
uint32_t *pSizeOut
);
/* ════════════════════════════════════════════════════════════════
* 阶段二:初始化
* ════════════════════════════════════════════════════════════════ */
/**
* DynChain_Init
* 在调用方提供的内存区域上构建完整的 DynamicChain 实例。
*
* @param pMem 平台层分配的内存起始地址(大小 >= GetMemSize 返回值)
* @param memSize pMem 的字节数
* @param pLinkFrame 链路帧(同 GetMemSize 使用的帧)
* @param frameLen 帧字节长度
* @param pPlatform 平台操作接口(日志、调试回调等;内存已在 pMem 中)
* @param ppChain 输出:DynamicChain 实例指针(指向 pMem 内部)
* @return DYNCHAIN_OK 或错误码
*/
int32_t DynChain_Init(
void *pMem,
uint32_t memSize,
const uint8_t *pLinkFrame,
uint32_t frameLen,
const DynChainPlatformOps *pPlatform,
DynamicChain **ppChain
);
/* ════════════════════════════════════════════════════════════════
* 阶段三:参数初始化下发
* ════════════════════════════════════════════════════════════════ */
/**
* DynChain_SetParam
* 设置指定模块实例的参数。(v6.0:去掉 channelIdx)
*
* @param pChain DynamicChain 实例
* @param instanceId 目标模块实例 ID(如 "gain_v1#0")
* @param paramId 参数类型 ID(如 PARAM_GAIN_DB = 0x0101)
* @param pData 参数数据指针(格式由模块内部定义)
* @param dataSize 数据字节数
*
* 通道索引由 pData 内部携带(各模块自定义):
* 单通道标量:pData = float*, dataSize = 4
* 多通道参数:pData = GainChannelParam*, dataSize = sizeof(GainChannelParam)
*/
int32_t DynChain_SetParam(
DynamicChain *pChain,
const char *instanceId,
uint16_t paramId,
const void *pData,
uint32_t dataSize
);
int32_t DynChain_GetParam(
DynamicChain *pChain,
const char *instanceId,
uint16_t paramId,
void *pBuf,
uint32_t bufSize
);
/* ════════════════════════════════════════════════════════════════
* 阶段四:实时处理
* ════════════════════════════════════════════════════════════════ */
/**
* DynChain_Process
* 按拓扑排序顺序执行所有模块的 Process()。
* 音频线程循环调用此函数,要求执行时间确定、无锁。
*
* @param pChain DynamicChain 实例
* @param ppIn 外部输入缓冲(Source 模块使用)ppIn[ch][sample]
* @param ppOut 最终输出缓冲
* @param blockSize 本帧采样数
*/
void DynChain_Process(
DynamicChain *pChain,
const float **ppIn,
float **ppOut,
uint32_t blockSize
);
/* ════════════════════════════════════════════════════════════════
* 链路重建(动态切换)
* ════════════════════════════════════════════════════════════════ */
/**
* DynChain_RequestRebuild
* 提交新链路帧,框架在当前帧结束后执行淡出 → 重建 → 淡入。
* 调用此函数后立即返回(非阻塞),重建在 Process() 内部完成。
*
* @param pChain 当前 DynamicChain 实例
* @param pNewFrame 新链路帧数据(框架内部复制,调用后可释放)
* @param frameLen 新链路帧字节长度
* @param pPlatform 平台接口(用于重新分配内存)
*/
int32_t DynChain_RequestRebuild(
DynamicChain *pChain,
const uint8_t *pNewFrame,
uint32_t frameLen,
const DynChainPlatformOps *pPlatform
);
/* ════════════════════════════════════════════════════════════════
* 模块注册(在 main / platform_init 阶段调用一次)
* ════════════════════════════════════════════════════════════════ */
/**
* DynChain_RegisterAllModules
* 将 modules_config.h 中使能的所有模块注册到全局 ModuleRegistry。
* 实现在 module_registry_all.c 中,通过 #if 宏裁剪。
*/
void DynChain_RegisterAllModules(void);
#ifdef __cplusplus
}
#endif
#endif /* DYNCHAIN_INTERFACE_H */
5. 框架内部核心结构(dynchain_core.h 内部定义)
/* ════════════════════════════════════════════════════════════════
* 链路重建状态机
* ════════════════════════════════════════════════════════════════ */
typedef enum RebuildState_ {
REBUILD_IDLE = 0, /* 正常运行 */
REBUILD_FADEOUT, /* 正在淡出(逐帧降增益至 0)*/
REBUILD_PENDING, /* 淡出完成,等待重建执行 */
REBUILD_FADEIN, /* 重建完成,正在淡入 */
} RebuildState;
/* ════════════════════════════════════════════════════════════════
* ModuleInstance:框架层模块实例(v7.0 对齐 AWE ModuleInstanceDescriptor)
*
* AWE 字段对照:
* ModInstanceDescriptor.nUniqueInstanceID → typeNumId + typeIndex
* ModClassModule.typeName(非显式字段) → typeName[](显式存储)
* ModuleInstanceDescriptor.pWires → pWires[](单数组,in 前 out 后)
* ModuleInstanceDescriptor.packedFlags → numInWires / numOutWires
* ModuleInstanceDescriptor.pProcessFunc → funcs.process
* ModuleInstanceDescriptor.profileTime → profileTime
* ModClassModule.pSet / pGet → funcs.setParam / funcs.getParam
* 自定义字段区(如 enable、gainDb) → pState 指向的私有内存
*
* Wire 布局(对应 AWE packedFlags 编码约定):
* pWires[0 .. numInWires-1] ← 输入 wire(只读,指向上游输出 wire)
* pWires[numInWires .. numInWires+numOutWires-1] ← 输出 wire(读写,本模块拥有 buffer)
*
* instanceId 访问(不独立存储,按需构造):
* char id[32]; snprintf(id, 32, "%s#%u", inst->typeName, inst->typeIndex);
* ════════════════════════════════════════════════════════════════ */
#define TYPE_NAME_LEN 24 /* 最长类型名 "xisnddemom_v1\0" = 15B,留余量 */
#define MAX_MODULES 32
#define MAX_CONNECTIONS 64
#define MAX_WIRES 8 /* 单模块最大 wire 总数(输入 + 输出) */
typedef struct ModuleInstance_ {
/* --- 类型标识(对应 AWE ModInstanceDescriptor + ModClassModule)--- */
char typeName[TYPE_NAME_LEN]; /* "gain_v1" 注册时传入的类型名字符串 */
uint16_t typeIndex; /* 0,1,2,... 同类型在链路中的序号 */
uint32_t typeNumId; /* 0x10010001 注册表查找键 */
/* --- 函数表(对应 AWE ModClassModule 中的函数指针集合)--- */
ModuleFuncTable funcs;
void *pState; /* 模块私有状态内存(线性池切片,Init 前赋值)*/
/* --- Wire 连接(对应 AWE pWires + packedFlags)---
* 单数组,布局:[输入0..N-1] [输出0..M-1]
* 输入 wire 直接指向上游模块的输出 wire(零拷贝)
* 输出 wire 由框架分配 buffer,模块负责写入 */
WireInstance *pWires[MAX_WIRES];
uint8_t numInWires; /* 对应 AWE packedFlags[7:0] */
uint8_t numOutWires; /* 对应 AWE packedFlags[15:8] */
/* --- 性能分析(对应 AWE ModuleInstanceDescriptor.profileTime)--- */
uint32_t profileTime; /* 上一帧执行耗时,CPU cycles × 256 */
} ModuleInstance;
/* --- 便利宏(对应 AWE ClassModule_GetInputWire / GetOutputWire)--- */
#define ModInst_GetInWire(inst, idx) ((inst)->pWires[(idx)])
#define ModInst_GetOutWire(inst, idx) ((inst)->pWires[(inst)->numInWires + (idx)])
/* instanceId 字符串构造(不独立存储,避免冗余):
* char id[TYPE_NAME_LEN + 8];
* snprintf(id, sizeof(id), "%s#%u", inst->typeName, (uint32_t)inst->typeIndex); */
/* ════════════════════════════════════════════════════════════════
* 连接表(拓扑排序 / 框架路由)
* ════════════════════════════════════════════════════════════════ */
typedef struct DynChainConn_ {
uint8_t fromModIdx; /* 源模块在 instances[] 中的下标 */
uint8_t fromPortIdx; /* 源模块输出端口索引 */
uint8_t toModIdx; /* 目标模块在 instances[] 中的下标 */
uint8_t toPortIdx; /* 目标模块输入端口索引 */
} DynChainConn;
/* ════════════════════════════════════════════════════════════════
* 模块注册表(v6.0 正式定义,此前仅有 API,无结构)
*
* 全局单例,通过 DynChain_RegisterAllModules() 在启动时填充。
* 查找键为 typeNumId(O(N) 线性扫描,模块数量少性能无问题)。
* ════════════════════════════════════════════════════════════════ */
#define MAX_REGISTRY_ENTRIES 64
typedef struct ModuleRegistryEntry_ {
uint32_t typeNumId; /* 数字类型 ID,如 0x10010001 */
const char *typeName; /* 字符串类型名,如 "gain_v1"(仅日志用) */
ModuleFuncTable funcs; /* 函数表(值存储,模块自定义 extern const)*/
} ModuleRegistryEntry;
/* 注册表内部状态(实现在 dynchain_registry.c)*/
extern ModuleRegistryEntry g_registry[MAX_REGISTRY_ENTRIES];
extern uint32_t g_registryCount;
/* 注册 / 查找 API */
void ModuleRegistry_Register(uint32_t typeNumId,
const char *typeName,
const ModuleFuncTable *pFuncs);
const ModuleRegistryEntry* ModuleRegistry_FindByNumId(uint32_t typeNumId);
const ModuleRegistryEntry* ModuleRegistry_FindByName(const char *typeName);
/* ════════════════════════════════════════════════════════════════
* DynamicChain 主结构
* ════════════════════════════════════════════════════════════════ */
typedef struct DynamicChain_ {
ModuleInstance instances[MAX_MODULES];
uint8_t numInstances;
DynChainConn connections[MAX_CONNECTIONS];
uint8_t numConnections;
int8_t orderedIndices[MAX_MODULES]; /* 拓扑排序结果(执行顺序)*/
/* 链路重建 */
RebuildState rebuildState;
float fadeFactor; /* 0.0(静音)~ 1.0(全音量)*/
float fadeStep; /* 每帧增量(正=淡入,负=淡出)*/
uint8_t pendingFrame[4096];
uint32_t pendingFrameLen;
const DynChainPlatformOps *pPlatform;
/* 调试 */
void (*logFn)(int level, const char *msg);
} DynamicChain;
6. 模块头文件模板
每个新模块都按此模板创建头文件,保持接口一致性。
/* ================================================================
* [MODULE_NAME]_module.h
* 模块:[功能描述,如"20通道增益控制"]
* 版本:v1
* ================================================================ */
#ifndef MODULE_NAME_MODULE_H
#define MODULE_NAME_MODULE_H
#include "dynchain_types.h"
#include "module_interface.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ---- 参数类型 ID ---- */
/* 命名规则:PARAM_[MODULE]_[PARAM_NAME] = 0x[类别字节][编号字节] */
#define PARAM_MODULE_PARAM1 0xXX01u
#define PARAM_MODULE_PARAM2 0xXX02u
#define PARAM_MODULE_ENABLE 0xXX03u
/* ---- 多通道参数包(示例:单参数单通道)---- */
/* 当 paramId 指向通道相关参数时,pData 指向此结构体 */
typedef struct ModuleChannelParam_ {
uint16_t channelIdx; /* 目标通道索引(0 起)*/
float value;
} ModuleChannelParam;
/* ---- 内存大小查询(v7.0:直接传 ModuleInstance*,从 pInst->pWires[] 读格式)---- */
uint32_t ModuleName_GetMemSize(const ModuleInstance *pInst);
/* ---- 原地初始化(框架已将格式信息填入 pInst->pWires[],buffer 已分配)---- */
int32_t ModuleName_Init(ModuleInstance *pInst);
/* ---- 实时处理(v7.0:单参数,通过 ModInst_GetInWire/OutWire 访问 I/O)---- */
void ModuleName_Process(ModuleInstance *pInst);
/* ---- 参数设置/读取(无 channelIdx,格式由模块内部自定义)---- */
int32_t ModuleName_SetParam(ModuleInstance *pInst, uint16_t paramId,
const void *pData, uint32_t dataSize);
int32_t ModuleName_GetParam(ModuleInstance *pInst, uint16_t paramId,
void *pBuf, uint32_t bufSize);
/* ---- 注册到 ModuleRegistry 的函数表实例 ---- */
extern const ModuleFuncTable kModuleNameFuncs;
#ifdef __cplusplus
}
#endif
#endif /* MODULE_NAME_MODULE_H */
7. Gain 模块完整实现(详细示例)
7.1 gain_module.h
/* gain_module.h — 20 通道增益控制模块(v6.0)*/
#ifndef GAIN_MODULE_H
#define GAIN_MODULE_H
#include "dynchain_types.h"
#include "module_interface.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ---- 参数类型 ID(增益类 0x01xx)---- */
#define PARAM_GAIN_DB 0x0101u /* GainChannelParam:单通道增益(dB) */
#define PARAM_GAIN_MUTE 0x0102u /* GainChannelParam:0=取消静音 1=静音 */
#define PARAM_GAIN_ENABLE 0x0103u /* float*:0=bypass 1=正常处理 */
#define PARAM_GAIN_PHASE 0x0104u /* GainChannelParam:0=正相 1=反相 */
#define PARAM_GAIN_SMOOTH 0x0105u /* float*:平滑时间(ms) */
/* ---- 多通道参数包(Q2:通道索引由算法层自定义,框架无感知)---- */
typedef struct GainChannelParam_ {
uint16_t channelIdx; /* 目标通道索引(0 起,最大 GAIN_MAX_CHANNELS-1)*/
float value; /* 参数值(dB / 0or1)*/
} GainChannelParam;
#define GAIN_MAX_CHANNELS 32u
/* ---- Gain 模块状态结构体 ---- */
typedef struct GainModuleData_ {
/* 配置参数 */
uint32_t channelCount;
uint8_t enable;
uint8_t reserved[3];
float gainDb[GAIN_MAX_CHANNELS];
float mute[GAIN_MAX_CHANNELS];
float phase[GAIN_MAX_CHANNELS];
float smoothTimeMs;
/* 运行时平滑状态 */
float currentGainLin[GAIN_MAX_CHANNELS];
float targetGainLin[GAIN_MAX_CHANNELS];
float smoothCoeff;
float sampleRate;
uint32_t blockSize;
} GainModuleData;
/* ---- 标准接口函数(v7.0:所有接口统一接收 ModuleInstance*)---- */
uint32_t Gain_GetMemSize(const ModuleInstance *pInst);
int32_t Gain_Init (ModuleInstance *pInst);
/* v7.0:单参数,从 pInst->pWires[] 获取 I/O */
void Gain_Process (ModuleInstance *pInst);
/* v6.0/v7.0:无 channelIdx,通道索引在 pData 内部携带(GainChannelParam)*/
int32_t Gain_SetParam (ModuleInstance *pInst, uint16_t paramId,
const void *pData, uint32_t dataSize);
int32_t Gain_GetParam (ModuleInstance *pInst, uint16_t paramId,
void *pBuf, uint32_t bufSize);
extern const ModuleFuncTable kChannelGainFuncs;
#ifdef __cplusplus
}
#endif
#endif /* GAIN_MODULE_H */
7.2 gain_module.c
/* gain_module.c — 20 通道增益控制模块完整实现(v6.0)*/
#include "gain_module.h"
#include "module_type_id.h"
#include <string.h>
#include <math.h>
static float DbToLinear(float db) { return powf(10.0f, db / 20.0f); }
static float CalcSmoothCoeff(float smoothTimeMs, float sampleRate, uint32_t blockSize) {
if (smoothTimeMs <= 0.0f) return 0.0f;
float framesPerSec = sampleRate / (float)blockSize;
return expf(-1.0f / (smoothTimeMs * 0.001f * framesPerSec));
}
/* ─────────────────────────────────────────────────────────────────
* 1. GetMemSize(v7.0:接收 ModuleInstance*,格式由 pWires[] 提供)
* GainModuleData 大小固定(预分配 GAIN_MAX_CHANNELS 槽),无需读格式。
* ───────────────────────────────────────────────────────────────── */
uint32_t Gain_GetMemSize(const ModuleInstance *pInst) {
(void)pInst; /* 固定大小,此处不依赖 wire 格式 */
return (uint32_t)sizeof(GainModuleData);
}
/* ─────────────────────────────────────────────────────────────────
* 2. Init(v7.0:直接从 pInst->pWires[0] 读取输入 wire 格式)
* ───────────────────────────────────────────────────────────────── */
int32_t Gain_Init(ModuleInstance *pInst) {
if (!pInst || !pInst->pState) return DYNCHAIN_ERR_INVALID;
GainModuleData *g = (GainModuleData *)pInst->pState;
memset(g, 0, sizeof(GainModuleData));
/* 直接从输入 wire 读取格式(框架在调用 Init 前已填充 wireInfo1/2 + sampleRate)*/
WireInstance *inW = ModInst_GetInWire(pInst, 0);
if (!inW) return DYNCHAIN_ERR_INVALID;
uint32_t ch = Wire_Channels(inW);
if (ch > GAIN_MAX_CHANNELS) ch = GAIN_MAX_CHANNELS;
g->channelCount = ch;
g->enable = 1;
g->sampleRate = Wire_SampleRate(inW);
g->blockSize = Wire_BlockSize(inW);
g->smoothTimeMs = 5.0f;
g->smoothCoeff = CalcSmoothCoeff(g->smoothTimeMs, g->sampleRate, g->blockSize);
for (uint32_t c = 0; c < ch; c++) {
g->gainDb[c] = 0.0f;
g->targetGainLin[c] = 1.0f;
g->currentGainLin[c] = 1.0f;
}
return DYNCHAIN_OK;
}
/* ─────────────────────────────────────────────────────────────────
* 3. Process(v7.0:单参数 ModuleInstance*,用 ModInst_GetInWire/OutWire + Wire_ChanPtr)
* ───────────────────────────────────────────────────────────────── */
void Gain_Process(ModuleInstance *pInst)
{
GainModuleData *g = (GainModuleData *)pInst->pState;
WireInstance *inW = ModInst_GetInWire(pInst, 0);
WireInstance *outW = ModInst_GetOutWire(pInst, 0);
/* 从 wire 的 wireInfo 读取运行时格式,无需外部传参 */
uint32_t ch = Wire_Channels(inW);
uint32_t blk = Wire_BlockSize(inW);
if (!g->enable) {
/* Bypass:零拷贝,直接将输入 wire 通道数据拷贝到输出 wire */
uint32_t copy_ch = (ch < Wire_Channels(outW)) ? ch : Wire_Channels(outW);
for (uint32_t c = 0; c < copy_ch; c++)
memcpy(Wire_ChanPtr(outW, c), Wire_ChanPtr(inW, c), blk * sizeof(float));
return;
}
for (uint32_t c = 0; c < ch && c < g->channelCount; c++) {
float target = DbToLinear(g->gainDb[c]);
if (g->mute[c] > 0.5f) target = 0.0f;
if (g->phase[c] > 0.5f) target = -target;
g->targetGainLin[c] = target;
float coeff = g->smoothCoeff;
float curGain = g->currentGainLin[c];
const float *pIn = Wire_ChanPtr(inW, c);
float *pOut = Wire_ChanPtr(outW, c);
for (uint32_t n = 0; n < blk; n++) {
curGain = coeff * curGain + (1.0f - coeff) * target;
pOut[n] = pIn[n] * curGain;
}
g->currentGainLin[c] = curGain;
}
}
/* ─────────────────────────────────────────────────────────────────
* 4. SetParam(v7.0:接收 ModuleInstance*,通过 pInst->pState 访问私有状态)
*
* 调用示例:
* GainChannelParam p = { .channelIdx = 5, .value = -3.0f };
* DynChain_SetParam(chain, "gain_v1#0", PARAM_GAIN_DB, &p, sizeof(p));
*
* float ms = 10.0f;
* DynChain_SetParam(chain, "gain_v1#0", PARAM_GAIN_SMOOTH, &ms, sizeof(ms));
* ───────────────────────────────────────────────────────────────── */
int32_t Gain_SetParam(ModuleInstance *pInst, uint16_t paramId,
const void *pData, uint32_t dataSize)
{
GainModuleData *g = (GainModuleData *)pInst->pState;
if (!g || !pData) return DYNCHAIN_ERR_INVALID;
switch (paramId) {
case PARAM_GAIN_DB: {
if (dataSize < sizeof(GainChannelParam)) return DYNCHAIN_ERR_INVALID;
const GainChannelParam *p = (const GainChannelParam *)pData;
if (p->channelIdx >= g->channelCount) return DYNCHAIN_ERR_INVALID_PARAM;
g->gainDb[p->channelIdx] = p->value;
return DYNCHAIN_OK;
}
case PARAM_GAIN_MUTE: {
if (dataSize < sizeof(GainChannelParam)) return DYNCHAIN_ERR_INVALID;
const GainChannelParam *p = (const GainChannelParam *)pData;
if (p->channelIdx >= g->channelCount) return DYNCHAIN_ERR_INVALID_PARAM;
g->mute[p->channelIdx] = (p->value > 0.5f) ? 1.0f : 0.0f;
return DYNCHAIN_OK;
}
case PARAM_GAIN_ENABLE: {
if (dataSize < sizeof(float)) return DYNCHAIN_ERR_INVALID;
g->enable = (*(const float *)pData > 0.5f) ? 1u : 0u;
return DYNCHAIN_OK;
}
case PARAM_GAIN_PHASE: {
if (dataSize < sizeof(GainChannelParam)) return DYNCHAIN_ERR_INVALID;
const GainChannelParam *p = (const GainChannelParam *)pData;
if (p->channelIdx >= g->channelCount) return DYNCHAIN_ERR_INVALID_PARAM;
g->phase[p->channelIdx] = (p->value > 0.5f) ? 1.0f : 0.0f;
return DYNCHAIN_OK;
}
case PARAM_GAIN_SMOOTH: {
if (dataSize < sizeof(float)) return DYNCHAIN_ERR_INVALID;
float ms = *(const float *)pData;
g->smoothTimeMs = ms;
g->smoothCoeff = CalcSmoothCoeff(ms, g->sampleRate, g->blockSize);
return DYNCHAIN_OK;
}
default:
return DYNCHAIN_ERR_INVALID_PARAM;
}
}
/* ─────────────────────────────────────────────────────────────────
* 5. GetParam(v7.0:同 SetParam,接收 ModuleInstance*)
*
* 调用示例(读取通道 5 的增益):
* GainChannelParam query = { .channelIdx = 5, .value = 0.0f };
* DynChain_GetParam(chain, "gain_v1#0", PARAM_GAIN_DB, &query, sizeof(query));
* float result = query.value; // 读取结果写回 value 字段
* ───────────────────────────────────────────────────────────────── */
int32_t Gain_GetParam(ModuleInstance *pInst, uint16_t paramId,
void *pBuf, uint32_t bufSize)
{
GainModuleData *g = (GainModuleData *)pInst->pState;
if (!g || !pBuf) return DYNCHAIN_ERR_INVALID;
switch (paramId) {
case PARAM_GAIN_DB: {
if (bufSize < sizeof(GainChannelParam)) return DYNCHAIN_ERR_INVALID;
GainChannelParam *p = (GainChannelParam *)pBuf;
if (p->channelIdx >= g->channelCount) return DYNCHAIN_ERR_INVALID_PARAM;
p->value = g->gainDb[p->channelIdx];
return DYNCHAIN_OK;
}
case PARAM_GAIN_MUTE: {
if (bufSize < sizeof(GainChannelParam)) return DYNCHAIN_ERR_INVALID;
GainChannelParam *p = (GainChannelParam *)pBuf;
if (p->channelIdx >= g->channelCount) return DYNCHAIN_ERR_INVALID_PARAM;
p->value = g->mute[p->channelIdx];
return DYNCHAIN_OK;
}
case PARAM_GAIN_ENABLE:
if (bufSize < sizeof(float)) return DYNCHAIN_ERR_INVALID;
*(float *)pBuf = (float)g->enable;
return DYNCHAIN_OK;
case PARAM_GAIN_PHASE: {
if (bufSize < sizeof(GainChannelParam)) return DYNCHAIN_ERR_INVALID;
GainChannelParam *p = (GainChannelParam *)pBuf;
if (p->channelIdx >= g->channelCount) return DYNCHAIN_ERR_INVALID_PARAM;
p->value = g->phase[p->channelIdx];
return DYNCHAIN_OK;
}
case PARAM_GAIN_SMOOTH:
if (bufSize < sizeof(float)) return DYNCHAIN_ERR_INVALID;
*(float *)pBuf = g->smoothTimeMs;
return DYNCHAIN_OK;
default:
return DYNCHAIN_ERR_INVALID_PARAM;
}
}
/* ─────────────────────────────────────────────────────────────────
* 6. 函数表
* ───────────────────────────────────────────────────────────────── */
const ModuleFuncTable kChannelGainFuncs = {
.getMemSize = Gain_GetMemSize,
.init = Gain_Init,
.process = Gain_Process,
.setParam = Gain_SetParam,
.getParam = Gain_GetParam,
.destroy = NULL,
};
8. Xisnd 模块适配层(Phase 1:AWE 兼容)
8.1 xisnddemom_module.h
/* xisnddemom_module.h
* Xisnd 算法模块 DynamicChain 适配层。
* Phase 1:薄适配,内部调用 AWE awe_modXisndDemomInstance。
* Phase 2:替换为 xisnddemom_direct.c,去掉 AWE 依赖。
*/
#ifndef XISNDDEMOM_MODULE_H
#define XISNDDEMOM_MODULE_H
#include "dynchain_types.h"
#include "module_interface.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ---- 参数类型 ID(算法平台类 0x02xx)---- */
/* 注意:前端和后端 paramTypeId 保持一致 */
#define PARAM_XISND_ENABLE 0x0103u /* float, 0=关 1=开 */
#define PARAM_XISND_VEHICLE_CFG 0x0200u /* float, 车型配置 */
#define PARAM_XISND_ALGO_CHAN 0x0201u /* float, 算法通道数 */
#define PARAM_XISND_NUM_OUT_CH 0x0202u /* float, 输出通道数 */
#define PARAM_XISND_MEM_ALGO 0x0203u /* float, 算法内存大小(words) */
#define PARAM_XISND_MEM_ARCH 0x0204u /* float, 架构内存大小(words) */
#define PARAM_XISND_MEM_TUNE 0x0205u /* float, 调音内存大小(words) */
/* 特殊:typeId=0xFFFF 触发 SETALLMASK(全量初始化)*/
#define PARAM_XISND_INIT_ALL 0xFFFFu
/* 特殊:typeId=0x8000 触发 TuningBuffer 在线调音通道 */
#define PARAM_XISND_TUNING_BUF 0x8000u
/* ---- 标准接口函数(v7.0:统一接收 ModuleInstance*)---- */
uint32_t XisndDemom_GetMemSize(const ModuleInstance *pInst);
int32_t XisndDemom_Init (ModuleInstance *pInst);
/* v7.0:单参数,从 pInst->pWires[] 获取 I/O */
void XisndDemom_Process (ModuleInstance *pInst);
/* v6.0/v7.0:pData 直接传 float* 或模块自定义结构体 */
int32_t XisndDemom_SetParam (ModuleInstance *pInst, uint16_t paramId,
const void *pData, uint32_t dataSize);
int32_t XisndDemom_GetParam (ModuleInstance *pInst, uint16_t paramId,
void *pBuf, uint32_t bufSize);
extern const ModuleFuncTable kXisndDemomFuncs;
#ifdef __cplusplus
}
#endif
#endif /* XISNDDEMOM_MODULE_H */
8.2 xisnddemom_module.c 关键逻辑
/* xisnddemom_module.c
* GetMemSize:包含 AWE 实例 + TuningBuffer + AlgoMem 三块内存。
*/
/* AWE 默认内存规格(可通过 SetParam 修改后重初始化)*/
#define XISND_DEFAULT_TUNING_WORDS 80000u
#define XISND_DEFAULT_ALGO_WORDS 3000116u
/* v7.0:接收 ModuleInstance*,固定大小,无需读格式 */
uint32_t XisndDemom_GetMemSize(const ModuleInstance *pInst) {
(void)pInst;
uint32_t sz = 0;
sz += sizeof(awe_modXisndDemomInstance); /* AWE 模块实例结构体 */
sz = (sz + 3u) & ~3u; /* 4字节对齐 */
sz += XISND_DEFAULT_TUNING_WORDS * 4u; /* TuningBuffer: 80K words */
sz += XISND_DEFAULT_ALGO_WORDS * 4u; /* AlgoMem: ~11.4MB */
return sz;
}
/* v7.0:直接从 pInst->pWires[0] 读取输入 wire 格式,
* 内存基址从 pInst->pState 取,不再依赖独立 pMem 参数 */
int32_t XisndDemom_Init(ModuleInstance *pInst) {
if (!pInst || !pInst->pState) return DYNCHAIN_ERR_INVALID;
awe_modXisndDemomInstance *S = (awe_modXisndDemomInstance *)pInst->pState;
memset(S, 0, sizeof(awe_modXisndDemomInstance));
/* 从输入 wire 读取通道数(框架已填充 wireInfo1)*/
WireInstance *inW = ModInst_GetInWire(pInst, 0);
S->AlgoChanNum = inW ? Wire_Channels(inW) : 2u;
S->NumOutChan = S->AlgoChanNum;
S->MemSize_Algo = XISND_DEFAULT_ALGO_WORDS;
S->MemSize_Arch = 0;
S->MemSize_Tune = XISND_DEFAULT_TUNING_WORDS;
/* TuningBuffer 和 AlgoMem 紧跟在 pState(AWE 实例结构体)之后 */
uint8_t *base = (uint8_t *)pInst->pState;
uint32_t offset = (sizeof(awe_modXisndDemomInstance) + 3u) & ~3u;
S->TuningBuffer = (UINT32 *)(base + offset);
offset += XISND_DEFAULT_TUNING_WORDS * 4u;
S->AlgoMem = (UINT32 *)(base + offset);
/* 注意:此时不调用 SETALLMASK,等待后端下发 PARAM_XISND_INIT_ALL */
return DYNCHAIN_OK;
}
int32_t XisndDemom_SetParam(ModuleInstance *pInst, uint16_t paramId,
const void *pData, uint32_t dataSize)
{
awe_modXisndDemomInstance *S = (awe_modXisndDemomInstance *)pInst->pState;
/* 所有 xisnd 参数均为标量 float,直接读取 */
if (!pData || dataSize < sizeof(float)) return DYNCHAIN_ERR_INVALID;
float fval = *(const float *)pData;
/* paramId → AWE mask 映射 */
switch (paramId) {
case PARAM_XISND_ENABLE:
S->enable = (INT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_enable);
case PARAM_XISND_VEHICLE_CFG:
S->VehicleCfg = (UINT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_VehicleCfg);
case PARAM_XISND_ALGO_CHAN:
S->AlgoChanNum = (UINT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_AlgoChanNum);
case PARAM_XISND_NUM_OUT_CH:
S->NumOutChan = (UINT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_NumOutChan);
case PARAM_XISND_MEM_ALGO:
S->MemSize_Algo = (UINT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_MemSize_Algo);
case PARAM_XISND_MEM_ARCH:
S->MemSize_Arch = (UINT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_MemSize_Arch);
case PARAM_XISND_MEM_TUNE:
S->MemSize_Tune = (UINT32)fval;
return awe_modXisndDemomSet(S, MASK_XisndDemom_MemSize_Tune);
case PARAM_XISND_INIT_ALL:
/* 触发 platform_init(),完成 AlgoMem 内部布局和算法初始化 */
return awe_modXisndDemomSet(S, SETALLMASK);
case PARAM_XISND_TUNING_BUF:
/* 调用方已将调音数据写入 S->TuningBuffer,触发在线调音 */
return awe_modXisndDemomSet(S, MASK_XisndDemom_TuningBuffer);
default:
return DYNCHAIN_ERR_INVALID_PARAM;
}
}
9. 统一注册入口 module_registry_all.c
/* module_registry_all.c
* 根据 modules_config.h 中的开关,将各模块注册到全局 ModuleRegistry。
* 此文件是唯一需要感知所有模块的地方。
*/
#include "modules_config.h"
#include "module_type_id.h"
#include "dynchain_interface.h"
/* 按需包含各模块头文件 */
#if DYNCHAIN_ENABLE_CHANNEL_GAIN
#include "gain/gain_module.h"
#endif
#if DYNCHAIN_ENABLE_UT_DELAY_20CH
#include "delay/delay_module.h"
#endif
#if DYNCHAIN_ENABLE_XISNDDEMOM
#include "xisnddemom/xisnddemom_module.h"
#endif
/* ... 其他模块 ... */
void DynChain_RegisterAllModules(void) {
#if DYNCHAIN_ENABLE_CHANNEL_GAIN
ModuleRegistry_Register(MODULE_TYPE_CHANNEL_GAIN_V1,
"channel_gain_v1",
&kChannelGainFuncs);
#endif
#if DYNCHAIN_ENABLE_UT_DELAY_20CH
ModuleRegistry_Register(MODULE_TYPE_UT_DELAY_20CH_V1,
"ut_delay_20ch_v1",
&kUtDelay20chFuncs);
#endif
#if DYNCHAIN_ENABLE_XISNDDEMOM
ModuleRegistry_Register(MODULE_TYPE_XISNDDEMOM_V1,
"xisnddemom_v1",
&kXisndDemomFuncs);
#endif
}
10. dynchain_core.c 链路解析与执行引擎实现(Q3)
本节给出框架核心 DynChain_Init 和 DynChain_Process 的骨架实现,
展示链路帧如何解析为 ModuleInstance + WireInstance 图,以及 Process 如何按拓扑序调度。
10.1 二进制帧 v3 结构说明
Binary Frame v3 布局(字节流):
[0] SOF = 0xA5
[1] CMD = 0x02 (set_link)
[2..3] len = payload 字节数(LE uint16)
[4..] payload:
[0..1] numModules (uint16_t LE)
[2..] N × ModuleEntry:
[0..3] typeNumId (uint32_t LE) ← 注册表查找键
[4..35] instanceId (32字节,'\0'结尾) ← 如 "gain_v1#0"
[36..37] channels (uint16_t)
[38..39] blockSize (uint16_t)
[40..43] sampleRate (float)
[44] numInPorts (uint8_t)
[45] numOutPorts (uint8_t)
[46..47] padding
[..] numConns (uint16_t LE)
[..] M × ConnEntry:
[0] fromModIdx (uint8_t)
[1] fromPortIdx (uint8_t)
[2] toModIdx (uint8_t)
[3] toPortIdx (uint8_t)
10.2 DynChain_Init 骨架
/* dynchain_core.c — 链路解析与初始化(v7.0 新流程)
*
* 初始化分 8 步:
* 1. 分配 DynamicChain 主结构
* 2. 解析帧,填充各模块的 typeName/typeIndex/numInWires/numOutWires/funcs
* 3. 解析连接表,分配 WireInstance(仅填格式,buffer=NULL)
* 4. 将 wire 绑定到各模块 pWires[](输入直接引用,输出绑定自身)
* 5. 拓扑排序(Kahn BFS)
* 6. 按拓扑序:getMemSize(inst) → alloc pState
* 7. 为所有输出 wire 分配音频缓冲区(ch × blk × float)
* 8. 按拓扑序:init(inst)(格式和 buffer 均已就绪)
*/
#include "dynchain_core.h"
#include "dynchain_registry.h"
#include <string.h>
#include <stdlib.h> /* atoi */
static void* LinearAlloc(uint8_t *pBase, uint32_t *pOffset,
uint32_t size, uint32_t align)
{
uint32_t aligned = (*pOffset + align - 1u) & ~(align - 1u);
void *p = pBase + aligned;
*pOffset = aligned + size;
memset(p, 0, size);
return p;
}
int32_t DynChain_Init(void *pMem, uint32_t memSize,
const uint8_t *pFrame, uint32_t frameLen,
const DynChainPlatformOps *pPlatform,
DynamicChain **ppChain)
{
uint8_t *base = (uint8_t *)pMem;
uint32_t offset = 0;
/* ── 1. DynamicChain 主结构 ── */
DynamicChain *chain = (DynamicChain *)LinearAlloc(base, &offset,
sizeof(DynamicChain), 8u);
chain->pPlatform = pPlatform;
/* ── 2. 解析帧头,填充模块元数据 ── */
const uint8_t *p = pFrame + 4; /* 跳过 SOF/CMD/len */
uint16_t numMods = p[0] | ((uint16_t)p[1] << 8); p += 2;
chain->numInstances = (uint8_t)numMods;
for (uint32_t i = 0; i < numMods; i++) {
uint32_t typeNumId = p[0]|(p[1]<<8)|(p[2]<<16)|((uint32_t)p[3]<<24);
const char *instIdStr = (const char *)(p + 4); /* "gain_v1#0" 32字节 */
uint8_t numIn = p[44];
uint8_t numOut = p[45];
p += 48;
ModuleInstance *inst = &chain->instances[i];
inst->typeNumId = typeNumId;
inst->numInWires = numIn;
inst->numOutWires = numOut;
/* 从 instanceId 拆分 typeName + typeIndex(按 '#' 分隔)*/
strncpy(inst->typeName, instIdStr, TYPE_NAME_LEN - 1);
char *hash = strchr(inst->typeName, '#');
if (hash) {
inst->typeIndex = (uint16_t)atoi(hash + 1);
*hash = '\0'; /* typeName 截断为无序号部分 */
}
/* 查注册表,复制函数表 */
const ModuleRegistryEntry *reg = ModuleRegistry_FindByNumId(typeNumId);
if (!reg) {
pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_ERROR,
"[Init] typeNumId=0x%08X not registered", typeNumId);
return DYNCHAIN_ERR_NOT_FOUND;
}
inst->funcs = reg->funcs;
}
/* ── 3. 解析连接表,分配 WireInstance(格式就绪,buffer=NULL)── */
uint16_t numConns = p[0] | ((uint16_t)p[1] << 8); p += 2;
chain->numConnections = (uint8_t)numConns;
for (uint32_t i = 0; i < numConns; i++) {
uint8_t fromMod = p[0], fromPort = p[1];
uint8_t toMod = p[2], toPort = p[3];
uint16_t channels = p[4] | ((uint16_t)p[5] << 8);
uint16_t blockSize = p[6] | ((uint16_t)p[7] << 8);
float sampleRate; memcpy(&sampleRate, p + 8, 4);
p += 12;
chain->connections[i] = (DynChainConn){ fromMod, fromPort, toMod, toPort };
/* 分配 WireInstance,填入 wireInfo,buffer 留 NULL */
WireInstance *wire = (WireInstance *)LinearAlloc(
base, &offset, sizeof(WireInstance), 8u);
wire->sampleRate = sampleRate;
wire->wireInfo1 = Wire_MakeInfo1(channels, blockSize, 0u, 4u);
wire->wireInfo2 = Wire_MakeInfo2(blockSize, WIRE_DATA_FLOAT32);
wire->wireInfo3 = 0u;
wire->buffer = NULL; /* 步骤 7 分配 */
/* ── 4. 将 wire 绑定到 from(输出区)和 to(输入区)──
* from 的输出 wire 存放在 pWires[numInWires + fromPort]
* to 的输入 wire 存放在 pWires[toPort](直接引用上游输出 wire)*/
ModuleInstance *from = &chain->instances[fromMod];
ModuleInstance *to = &chain->instances[toMod];
from->pWires[from->numInWires + fromPort] = wire;
to->pWires[toPort] = wire;
pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_INFO,
"[Wire] [%u]port%u → [%u]port%u %uch×%usmp",
fromMod, fromPort, toMod, toPort, channels, blockSize);
}
/* ── 5. 拓扑排序(Kahn BFS)── */
uint8_t inDegree[MAX_MODULES] = {0};
for (uint32_t i = 0; i < chain->numConnections; i++)
inDegree[chain->connections[i].toModIdx]++;
uint8_t queue[MAX_MODULES]; int qHead = 0, qTail = 0, ordIdx = 0;
for (uint32_t i = 0; i < chain->numInstances; i++)
if (inDegree[i] == 0) queue[qTail++] = (uint8_t)i;
while (qHead < qTail) {
uint8_t cur = queue[qHead++];
chain->orderedIndices[ordIdx++] = (int8_t)cur;
for (uint32_t i = 0; i < chain->numConnections; i++) {
if (chain->connections[i].fromModIdx == cur) {
uint8_t next = chain->connections[i].toModIdx;
if (--inDegree[next] == 0) queue[qTail++] = next;
}
}
}
if (ordIdx != (int)chain->numInstances) {
pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_ERROR,
"[Init] Topo sort failed: cycle detected");
return DYNCHAIN_ERR_INVALID;
}
/* ── 6. 按拓扑序:getMemSize(inst) → alloc pState ──
* 此时 pWires[i]->wireInfo 已就绪(buffer=NULL),模块可读格式 */
for (int32_t oi = 0; oi < ordIdx; oi++) {
ModuleInstance *inst = &chain->instances[chain->orderedIndices[oi]];
uint32_t stateSz = (inst->funcs.getMemSize(inst) + 7u) & ~7u;
if (offset + stateSz > memSize) return DYNCHAIN_ERR_MEMORY;
inst->pState = LinearAlloc(base, &offset, stateSz, 8u);
pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_INFO,
"[Init] [%d] %s#%u state=%uB",
chain->orderedIndices[oi],
inst->typeName, inst->typeIndex, stateSz);
}
/* ── 7. 为所有输出 wire 分配音频缓冲区(ch × blk × sizeof(float))── */
for (uint32_t i = 0; i < chain->numConnections; i++) {
uint8_t fromMod = chain->connections[i].fromModIdx;
uint8_t fromPort = chain->connections[i].fromPortIdx;
ModuleInstance *from = &chain->instances[fromMod];
WireInstance *wire = from->pWires[from->numInWires + fromPort];
if (wire && !wire->buffer) {
uint32_t bufSz = (Wire_Channels(wire) * Wire_BlockSize(wire)
* sizeof(float) + 7u) & ~7u;
if (offset + bufSz > memSize) return DYNCHAIN_ERR_MEMORY;
wire->buffer = (float *)LinearAlloc(base, &offset, bufSz, 8u);
}
}
/* ── 8. 按拓扑序:init(inst)(格式 + buffer 均已就绪)── */
for (int32_t oi = 0; oi < ordIdx; oi++) {
ModuleInstance *inst = &chain->instances[chain->orderedIndices[oi]];
int32_t err = inst->funcs.init(inst);
if (err != DYNCHAIN_OK) {
pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_ERROR,
"[Init] %s#%u init failed: %d",
inst->typeName, inst->typeIndex, err);
return err;
}
}
chain->rebuildState = REBUILD_IDLE;
chain->fadeFactor = 1.0f;
*ppChain = chain;
pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_INFO,
"[Init] OK: %u modules, %u conns, mem=%uB",
chain->numInstances, chain->numConnections, offset);
return DYNCHAIN_OK;
}
10.3 DynChain_Process 骨架
/* DynChain_Process — 每帧调用,按拓扑序执行所有模块(v7.0)*/
void DynChain_Process(DynamicChain *chain,
const float **ppHwIn, /* 硬件输入(Source 模块消费)*/
float **ppHwOut, /* 硬件输出(Sink 模块写入)*/
uint32_t blockSize)
{
/* ── 淡出/淡入状态机 ── */
if (chain->rebuildState == REBUILD_FADEOUT) {
chain->fadeFactor -= chain->fadeStep;
if (chain->fadeFactor <= 0.0f) {
chain->fadeFactor = 0.0f;
chain->rebuildState = REBUILD_PENDING;
}
} else if (chain->rebuildState == REBUILD_PENDING) {
chain->pPlatform->reset(chain->pPlatform->pCtx);
uint32_t newSz = 0;
DynChain_GetMemSize(chain->pendingFrame, chain->pendingFrameLen, &newSz);
void *pNewMem = chain->pPlatform->alloc(chain->pPlatform->pCtx, newSz, 8u);
DynChain_Init(pNewMem, newSz,
chain->pendingFrame, chain->pendingFrameLen,
chain->pPlatform, &chain);
chain->rebuildState = REBUILD_FADEIN;
chain->fadeFactor = 0.0f;
} else if (chain->rebuildState == REBUILD_FADEIN) {
chain->fadeFactor += chain->fadeStep;
if (chain->fadeFactor >= 1.0f) {
chain->fadeFactor = 1.0f;
chain->rebuildState = REBUILD_IDLE;
}
}
/* ── 按拓扑序执行所有模块 ── */
for (uint32_t i = 0; i < chain->numInstances; i++) {
int8_t idx = chain->orderedIndices[i];
ModuleInstance *inst = &chain->instances[idx];
/* Source 模块(无输入 wire):将硬件输入注入其输出 wire */
if (inst->numInWires == 0 && inst->numOutWires > 0 && ppHwIn) {
WireInstance *outW = ModInst_GetOutWire(inst, 0);
uint32_t ch = Wire_Channels(outW);
uint32_t blk = Wire_BlockSize(outW);
for (uint32_t c = 0; c < ch; c++)
memcpy(Wire_ChanPtr(outW, c), ppHwIn[c], blk * sizeof(float));
}
/* v7.0:单参数调用,模块从 pInst 自取所有信息(I/O wire、格式、pState)*/
inst->funcs.process(inst);
}
/* ── 将末端模块的输出 wire 拷贝到硬件输出 ── */
int8_t sinkIdx = chain->orderedIndices[chain->numInstances - 1];
ModuleInstance *sink = &chain->instances[sinkIdx];
if (sink->numOutWires > 0 && ppHwOut) {
WireInstance *outW = ModInst_GetOutWire(sink, 0);
uint32_t ch = Wire_Channels(outW);
uint32_t blk = Wire_BlockSize(outW);
for (uint32_t c = 0; c < ch; c++)
memcpy(ppHwOut[c], Wire_ChanPtr(outW, c), blk * sizeof(float));
}
/* ── 应用淡入/淡出增益 ── */
if (chain->rebuildState != REBUILD_IDLE && ppHwOut) {
float factor = chain->fadeFactor;
WireInstance *outW = ModInst_GetOutWire(sink, 0);
uint32_t blk = Wire_BlockSize(outW);
uint32_t ch = Wire_Channels(outW);
for (uint32_t c = 0; c < ch; c++)
for (uint32_t n = 0; n < blk; n++)
ppHwOut[c][n] *= factor;
}
}
10.4 DynChain_SetParam 实现
/* SetParam:按 instanceId("typeName#typeIndex")查找模块,调用 setParam(inst, ...) */
int32_t DynChain_SetParam(DynamicChain *chain, const char *instanceId,
uint16_t paramId, const void *pData, uint32_t dataSize)
{
char id[TYPE_NAME_LEN + 8];
for (uint32_t i = 0; i < chain->numInstances; i++) {
ModuleInstance *inst = &chain->instances[i];
/* 按需构造 instanceId,不占用额外存储空间 */
snprintf(id, sizeof(id), "%s#%u", inst->typeName, (uint32_t)inst->typeIndex);
if (strncmp(id, instanceId, sizeof(id)) == 0) {
return inst->funcs.setParam(inst, paramId, pData, dataSize);
}
}
return DYNCHAIN_ERR_NOT_FOUND;
}
11. ID 体系说明:typeNumId / typeName / instanceId 完整链路(Q4)
本节回答 Q3/Q4 的核心问题:三种 ID 的含义及相互关系,以及同类型模块多实例如何区分。
11.1 三种 ID 的含义
| ID | 定义位置 | 类型 | 示例 | 用途 |
|---|---|---|---|---|
| typeNumId | module_type_id.h |
uint32_t |
0x10010001 |
注册表查找键;帧中携带;全局唯一类型编号 |
| typeName | module_registry_all.c 注册时传入 |
const char* |
"gain_v1" |
日志显示;前端模块库名;instanceId 前缀 |
| instanceId | 链路帧中携带,框架存入 ModuleInstance |
char[32] |
"gain_v1#0" |
参数路由;全链路唯一;同类型多实例的区分键 |
11.2 完整生命周期链路
┌──────────────────────────────────────────────────────────────────────┐
│ 步骤 1:编译期 — module_type_id.h │
│ │
│ #define MODULE_TYPE_CHANNEL_GAIN_V1 0x10010001u │
│ ↑ typeNumId,高16位=功能分类(增益类0x1001),低16位=版本编号 │
└──────────────────────────────────────────────────────────────────────┘
↓ 启动时注册
┌──────────────────────────────────────────────────────────────────────┐
│ 步骤 2:系统启动 — module_registry_all.c │
│ │
│ DynChain_RegisterAllModules() │
│ → ModuleRegistry_Register( │
│ 0x10010001, ← typeNumId(查找键) │
│ "gain_v1", ← typeName(日志/前端用) │
│ &kChannelGainFuncs │
│ ) │
│ │
│ 注册表全局单例: │
│ g_registry[0] = { .typeNumId=0x10010001, │
│ .typeName="gain_v1", │
│ .funcs=kChannelGainFuncs } │
└──────────────────────────────────────────────────────────────────────┘
↓ 后端/固件下发链路帧
┌──────────────────────────────────────────────────────────────────────┐
│ 步骤 3:链路帧内容(Binary Frame v3) │
│ │
│ Module Entry 0:typeNumId=0x10010001, instanceId="gain_v1#0", ... │
│ Module Entry 1:typeNumId=0x10010001, instanceId="gain_v1#1", ... │
│ ↑ 同一类型,两个实例,instanceId 不同 │
│ Module Entry 2:typeNumId=0x10070001, instanceId="xisnd#0", ... │
│ │
│ instanceId 命名约定:<typeName>#<index> │
│ index 由后端/工具生成,同一类型在链路中的唯一序号(0起) │
└──────────────────────────────────────────────────────────────────────┘
↓ DynChain_Init 解析
┌──────────────────────────────────────────────────────────────────────┐
│ 步骤 4:DynChain_Init — 构建 ModuleInstance │
│ │
│ for each ModuleEntry in frame: │
│ reg = ModuleRegistry_FindByNumId(entry.typeNumId) │
│ → 找到 kChannelGainFuncs │
│ │
│ instances[0].typeName = "gain_v1" │
│ instances[0].typeIndex = 0 │
│ instances[0].typeNumId = 0x10010001 │
│ instances[0].funcs = kChannelGainFuncs (值拷贝) │
│ instances[0].pState = <从 pMem 切片的状态内存> │
│ instances[0].pWires[0] = inWire0 ← 输入 wire(来自上游) │
│ instances[0].pWires[1] = outWire0 ← 输出 wire(自身拥有) │
│ /* instanceId 按需构造,不独立存储: │
│ * snprintf(id, .., "%s#%u", "gain_v1", 0) → "gain_v1#0" */ │
│ │
│ instances[1].typeName = "gain_v1" ← 第二个 gain 实例 │
│ instances[1].typeIndex = 1 ← 序号不同 │
│ instances[1].typeNumId = 0x10010001 ← typeNumId 相同 │
│ instances[1].funcs = kChannelGainFuncs ← funcs 相同 │
│ instances[1].pState = <另一块独立状态内存> ← 状态独立 │
└──────────────────────────────────────────────────────────────────────┘
↓ DynChain_SetParam 调用
┌──────────────────────────────────────────────────────────────────────┐
│ 步骤 5:参数路由 — 通过 instanceId 找到具体实例 │
│ │
│ /* 设置第一个 gain 实例(通道2增益)*/ │
│ GainChannelParam p0 = { .channelIdx=2, .value=-3.0f }; │
│ DynChain_SetParam(chain, "gain_v1#0", PARAM_GAIN_DB, &p0, sz); │
│ → 遍历 instances[],构造 "%s#%u" = "gain_v1#0" 匹配成功 │
│ → 调用 instances[0].funcs.setParam(&instances[0], ...) │
│ │
│ /* 设置第二个 gain 实例(bypass)*/ │
│ float en = 0.0f; │
│ DynChain_SetParam(chain, "gain_v1#1", PARAM_GAIN_ENABLE, &en, 4); │
│ → 构造 "gain_v1#1" 匹配 instances[1] │
│ → 两实例 pState 完全独立,互不影响 │
└──────────────────────────────────────────────────────────────────────┘
11.3 前端如何使用 ID
前端(TypeScript)管理的模块对象对应关系:
// 前端模块实例(对应 ModuleInstance)
interface FrontendModule {
instanceId: string // "gain_v1#0" ← 用于 SetParam/GetParam 路由
typeNumId: number // 0x10010001 ← 用于查找模块库图标/UI 组件
typeName: string // "gain_v1" ← 用于显示名称
// ... 参数、wire 信息等
}
// 发送 SetParam 时,instanceId 直接嵌入报文:
// { instanceId: "gain_v1#0", paramId: 0x0101, data: { channelIdx: 2, value: -3.0 } }
11.4 typeNumId 编码规则
0x 1001 0001
|||| ||||
|||| └─── 版本/编号:同功能类下的第 N 个模块(0001=v1,0002=v2/20ch变体)
└───────── 功能分类:
0x1001 — 增益类 (Gain)
0x1002 — 延迟类 (Delay)
0x1003 — 滤波/EQ 类
0x1004 — 动态处理类 (Limiter/MDRC)
0x1005 — 混音类 (Mixer)
0x1007 — 算法平台类 (Xisnd/AWE)
0x1008 — 信号源类 (Source)
12. 完整启动与运行调用链
12.1 静态启动流程(嵌入式 DSP)
以 Source(20ch) → Gain(20ch) → Xisnd(20ch) 链路为例,从上电到实时处理的完整调用顺序:
═══════════════════════════════════════════════════════
阶段 0:系统上电,注册所有模块(main.c / bsp_init)
═══════════════════════════════════════════════════════
main()
└─► DynChain_RegisterAllModules()
├─► ModuleRegistry_Register(channel_gain_v1, &kChannelGainFuncs)
├─► ModuleRegistry_Register(ut_delay_20ch_v1, &kUtDelay20chFuncs)
└─► ModuleRegistry_Register(xisnddemom_v1, &kXisndDemomFuncs)
═══════════════════════════════════════════════════════
阶段 1:后端下发 set_link 帧,查询内存需求
═══════════════════════════════════════════════════════
收到 Binary Frame v3 (CMD=0x02)
└─► DynChain_GetMemSize(pFrame, frameLen, &totalBytes)
│ 解析帧:3个模块 + 2条连接,先创建 WireInstance(wireInfo填充),绑 pWires[]
├─► ModuleRegistry_Lookup(SOURCE_V1) → inst->funcs = kSourceFuncs
│ └─► Source_GetMemSize(pInst) → 返回 Source 状态大小
├─► ModuleRegistry_Lookup(CHANNEL_GAIN_V1) → inst->funcs = kChannelGainFuncs
│ └─► Gain_GetMemSize(pInst) → sizeof(GainModuleData) = 440B
├─► ModuleRegistry_Lookup(XISNDDEMOM_V1) → inst->funcs = kXisndDemomFuncs
│ └─► XisndDemom_GetMemSize(pInst)
│ = sizeof(awe_modXisndDemomInstance) ~72B
│ + 80000×4 312KB(TuningBuffer)
│ + 3000116×4 11.4MB(AlgoMem)
│ ≈ 11.7MB
├─► 累加每条输出 wire 缓冲:
│ Gain outputBuf = 20ch × 240 × 4B = 19.2KB
│ Xisnd outputBuf = 20ch × 240 × 4B = 19.2KB
└─► totalBytes ≈ 12MB + 元数据开销
═══════════════════════════════════════════════════════
阶段 2:平台层分配内存
═══════════════════════════════════════════════════════
└─► pMem = Platform_GetStaticOps()->alloc(ctx, totalBytes, 8)
从 .dram_algo 段线性分配 ~12MB
(嵌入式:地址由 linker script 决定;PC:malloc)
═══════════════════════════════════════════════════════
阶段 3:初始化链路
═══════════════════════════════════════════════════════
└─► DynChain_Init(pMem, totalBytes, pFrame, frameLen, pPlatform, &pChain)
│
├─► 步骤2-4:解析模块 + 分配 WireInstance(wireInfo就绪,buffer=NULL)+ 绑 pWires[]
│
│ [WireInstance w0: source#0.out → gain#0.in]
│ wireInfo1 = Wire_MakeInfo1(20, 240, 0, 4) ← 20ch,maxBlk=240
│ wireInfo2 = Wire_MakeInfo2(240, FLOAT32) ← blockSize=240
│ sampleRate = 48000.0f; buffer = NULL(待步骤7)
│
├─► 步骤5:拓扑排序 → orderedIndices = [0(source), 1(gain), 2(xisnd)]
│
├─► 步骤6:按拓扑序分配 pState
│ [模块 0: source#0] source->pState = LinearAlloc(...)
│ [模块 1: gain#0] gain->pState = LinearAlloc(440B)
│ [模块 2: xisnd#0] xisnd->pState = LinearAlloc(~11.7MB)
│
├─► 步骤7:分配 wire buffer
│ w0.buffer = LinearAlloc(20×240×4=19.2KB)
│ w1.buffer = LinearAlloc(20×240×4=19.2KB)
│
└─► 步骤8:按拓扑序调用 init(pInst)
[模块 0: source#0]
├─► Source_Init(pInst)
│ ← 读 ModInst_GetOutWire(pInst,0)->wireInfo → 20ch/48kHz/240
[模块 1: gain#0]
├─► Gain_Init(pInst)
│ ├─► inW = ModInst_GetInWire(pInst, 0) ← 即 w0(已含 wireInfo)
│ ├─► g->channelCount = Wire_Channels(inW) = 20
│ ├─► g->sampleRate = Wire_SampleRate(inW) = 48000
│ ├─► g->blockSize = Wire_BlockSize(inW) = 240
│ ├─► g->smoothTimeMs = 5.0f
│ └─► g->currentGainLin[0..19] = 1.0f(0dB)
[模块 2: xisnd#0]
└─► XisndDemom_Init(pInst)
├─► S->AlgoChanNum = Wire_Channels(ModInst_GetInWire(pInst,0)) = 20
├─► S->TuningBuffer → pState+sizeof(instance)
└─► S->AlgoMem → pState+sizeof(instance)+80K×4
注意:不调用 SETALLMASK,等待参数下发
═══════════════════════════════════════════════════════
阶段 4:参数初始化下发(后端依次发送 set_param 帧)
═══════════════════════════════════════════════════════
收到 set_param(gain#0, PARAM_GAIN_DB, ch=2, -3.0f)
└─► DynChain_SetParam(pChain, "gain_v1#0", 0x0101, &{channelIdx=2, value=-3.0f}, sizeof(GainChannelParam))
└─► Gain_SetParam(&inst, 0x0101, &p, sizeof(p))
└─► g->gainDb[2] = -3.0f (targetGainLin 在下次 Process 更新)
收到 set_param(xisnd#0, PARAM_XISND_VEHICLE_CFG, 0, 0.0f)
└─► DynChain_SetParam → XisndDemom_SetParam(&inst, ...) → S->VehicleCfg=0 → awe_Set(MASK_VehicleCfg)
收到 set_param(xisnd#0, PARAM_XISND_ALGO_CHAN, 0, 20.0f)
└─► DynChain_SetParam → XisndDemom_SetParam(&inst, ...) → S->AlgoChanNum=20 → awe_Set(MASK_AlgoChanNum)
收到 set_param(xisnd#0, PARAM_XISND_INIT_ALL, 0, 1.0f) ← 触发全量初始化
└─► DynChain_SetParam → XisndDemom_SetParam(&inst, 0xFFFF, ...)
└─► awe_modXisndDemomSet(S, SETALLMASK=0xFFFFFF00)
├─► _pif = (adsp_v1_platform*)(S->AlgoMem)
├─► 布局 vtable / HandlWrapper / MediaFmt(Input) / MediaFmt(Output)
├─► _pif->ptr_Param->mFmtInput = {20ch, 240smp, 48kHz, fract32}
├─► _pif->ptr_Param->mFmtOutput = {20ch, 240smp, 48kHz, fract32}
└─► platform_init(_pif) → LIB_NO_ERROR ✓ 算法就绪!
═══════════════════════════════════════════════════════
阶段 5:实时处理循环(音频中断 / 音频任务)
═══════════════════════════════════════════════════════
每帧(240 samples / 5ms @ 48kHz):
audio_task_callback(ppHwIn[20ch], ppHwOut[20ch], 240):
└─► DynChain_Process(pChain, ppHwIn, ppHwOut, 240)
│
│ orderedIndices = [0(source), 1(gain), 2(xisnd)]
│
├─► i=0: source#0(无输入 wire)
│ ├─► 将 ppHwIn[c][...] 复制到 ModInst_GetOutWire(inst,0)->Wire_ChanPtr(c)
│ └─► Source_Process(pInst) ← 单参数,内部通过 ModInst_GetOutWire 访问
│
├─► i=1: gain#0
│ └─► Gain_Process(pInst)
│ ├─► inW = ModInst_GetInWire(pInst,0) ← 指向 source#0 的输出 wire
│ ├─► outW = ModInst_GetOutWire(pInst,0)
│ └─► 对每通道:平滑增益 → Wire_ChanPtr(outW,c)[n] = Wire_ChanPtr(inW,c)[n] * curGain[c]
│
├─► i=2: xisnd#0
│ └─► XisndDemom_Process(pInst)
│ ├─► inW = ModInst_GetInWire(pInst,0) ← 指向 gain#0 的输出 wire
│ ├─► outW = ModInst_GetOutWire(pInst,0)
│ ├─► 构建 sport_buf_t pIn = {Wire_ChanPtr(inW,0), 240×4×20}
│ ├─► 构建 sport_buf_t pOut = {Wire_ChanPtr(outW,0), 240×4×20}
│ └─► _pif->ptr_FunTab->process(_pif, pIn, pOut) ← xisnd算法核心
│
└─► 将 ModInst_GetOutWire(sink,0)->Wire_ChanPtr(c) 复制到 ppHwOut[c]
12.2 链路切换(重建)流程
═══════════════════════════════════════════════════════
触发:后端下发新的 set_link 帧
═══════════════════════════════════════════════════════
收到新 Binary Frame v3 (CMD=0x02)
└─► DynChain_RequestRebuild(pChain, pNewFrame, newLen, pPlatform)
├─► 拷贝 newFrame 到 pChain->pendingFrame[]
├─► pChain->pendingFrameLen = newLen
├─► pChain->rebuildState = REBUILD_FADEOUT
└─► pChain->fadeStep = -1.0f / FADE_FRAMES (负数 = 淡出)
音频帧循环中(DynChain_Process 内部):
if (rebuildState == REBUILD_FADEOUT):
fadeFactor += fadeStep ← 每帧递减
对 ppOut 所有样本乘以 fadeFactor ← 淡出
if (fadeFactor <= 0.0f):
fadeFactor = 0.0f
rebuildState = REBUILD_PENDING
if (rebuildState == REBUILD_PENDING):
─── 执行重建 ───
pPlatform->reset(ctx) ← 释放旧链路所有内存(线性池归零)
DynChain_GetMemSize(pendingFrame, pendingFrameLen, &newSize)
pNewMem = pPlatform->alloc(ctx, newSize, 8)
DynChain_Init(pNewMem, newSize, pendingFrame, pendingFrameLen,
pPlatform, &pChain)
后端重新下发所有 set_param
rebuildState = REBUILD_FADEIN
fadeStep = +1.0f / FADE_FRAMES ← 正数 = 淡入
if (rebuildState == REBUILD_FADEIN):
fadeFactor += fadeStep
对 ppOut 所有样本乘以 fadeFactor
if (fadeFactor >= 1.0f):
fadeFactor = 1.0f
rebuildState = REBUILD_IDLE ← 重建完成,恢复正常
12.3 固件自启动流程(无后端,上电即运行)
设计思路
在量产固件或调试 DSP 板上,DSP 上电后不等待上位机连接,而是从 Flash 中读取固化的链路帧和固化的默认参数表直接完成初始化,立即进入音频处理循环。
上位机连接后可通过 DynChain_RequestRebuild 下发新链路替换,不打断正在运行的音频(利用淡出/淡入状态机)。
┌────────────────────────────────────────────────────────┐
│ Flash / .rodata │
│ ┌──────────────────────────────────────────────────┐ │
│ │ kDefaultLinkFrame[] ← 固化链路帧(Binary v3) │ │
│ │ kDefaultParams[] ← 固化默认参数表 │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│ 上电直接读取,无需通信
▼
DynChain_GetMemSize → alloc → DynChain_Init → SetParam(default) → Process
│
│ (可选)上位机连接后
▼
DynChain_RequestRebuild(新链路帧) ← 后端动态替换,有淡出/淡入保护
新增文件:config/default_link_config.h
/* config/default_link_config.h
* 固化在 Flash 中的默认链路帧和初始参数表。
* 供嵌入式 main.c 在无后端情况下直接使用。
*/
#ifndef DEFAULT_LINK_CONFIG_H
#define DEFAULT_LINK_CONFIG_H
#include <stdint.h>
/* ---- 默认链路帧(Binary Frame v3,与后端 set_link 格式完全一致)---- */
/* 由 tools/gen_default_link.py 从 config/default_link.json 生成,勿手动修改 */
extern const uint8_t kDefaultLinkFrame[];
extern const uint32_t kDefaultLinkFrameLen;
/* ---- 默认参数表条目(v6.0:对齐 DynChain_SetParam 新接口)---- */
/* channelIdx 已内化到 pData,DefaultParamEntry 改用 pData 字节流 */
typedef struct DefaultParamEntry_ {
const char *instanceId; /* 目标模块实例 ID,如 "gain_v1#0" */
uint16_t paramId; /* 参数类型 ID,如 PARAM_GAIN_DB */
uint16_t dataSize; /* pData 字节数 */
const void *pData; /* 参数数据(各模块自定义格式) */
} DefaultParamEntry;
extern const DefaultParamEntry kDefaultParams[];
extern const uint32_t kDefaultParamsCount;
#endif /* DEFAULT_LINK_CONFIG_H */
新增文件:config/default_link_config.c
说明:
kDefaultLinkFrame[]由离线工具生成(见下节),这里展示其逻辑结构: - Binary Frame v3 格式:[帧头 0xA5][CMD 0x02][len 2B][payload]- payload 包含:模块列表(typeNumId / instanceId / numInWires / numOutWires)+ 连接表(含 wireInfo)
/* config/default_link_config.c
* 固化的默认链路帧和参数表。
*
* 重新生成链路帧:
* python tools/gen_default_link.py config/default_link.json
*
* 默认链路:source_v1#0(20ch/fract32/48kHz/240smp)
* → gain_v1#0(20ch) → xisnddemom_v1#0(20ch)
*/
#include "default_link_config.h"
#include "gain/gain_module.h"
#include "xisnddemom/xisnddemom_module.h"
/* ---- 固化链路帧(由工具自动生成)---- */
/* 生成命令:python tools/gen_default_link.py → 输出此数组 */
const uint8_t kDefaultLinkFrame[] = {
/* Binary Frame v3 帧头 */
0xA5, /* SOF 标记 */
0x02, /* CMD = set_link */
0x6C, 0x00, /* payload len = 108 bytes (little-endian) */
/* ---- payload: 模块列表 (numModules=3) ---- */
0x03, 0x00,
/* Module 0: source_v1#0 typeNumId=MODULE_TYPE_SOURCE_V1(0x10080001) */
0x01, 0x00, 0x08, 0x10,
/* instanceId = "source_v1#0" (32字节,'\0' 填充) */
0x73,0x6F,0x75,0x72,0x63,0x65,0x5F,0x76,0x31,0x23,0x30,0x00,
/* channels=20, blockSize=240, sampleRate=48000.0f, numIn=0, numOut=1 */
0x14, 0x00, 0xF0, 0x00, 0x00, 0x70, 0x3B, 0x47, 0x00, 0x01, 0x00, 0x00,
/* Module 1: gain_v1#0 typeNumId=MODULE_TYPE_CHANNEL_GAIN_V1(0x10010001) */
0x01, 0x00, 0x01, 0x10,
/* instanceId = "gain_v1#0" */
0x67,0x61,0x69,0x6E,0x5F,0x76,0x31,0x23,0x30,0x00,0x00,0x00,
/* channels=20, blockSize=240, sampleRate=48000.0f, numIn=1, numOut=1 */
0x14, 0x00, 0xF0, 0x00, 0x00, 0x70, 0x3B, 0x47, 0x01, 0x01, 0x00, 0x00,
/* Module 2: xisnddemom_v1#0 typeNumId=MODULE_TYPE_XISNDDEMOM_V1(0x10070001) */
0x01, 0x00, 0x07, 0x10,
/* instanceId = "xisnddemom_v1#0" */
0x78,0x69,0x73,0x6E,0x64,0x64,0x65,0x6D,0x6F,0x6D,0x5F,0x76,
/* channels=20, blockSize=240, sampleRate=48000.0f, numIn=1, numOut=1 */
0x14, 0x00, 0xF0, 0x00, 0x00, 0x70, 0x3B, 0x47, 0x01, 0x01, 0x00, 0x00,
/* ---- payload: 连接表 (numConns=2) ---- */
0x02, 0x00,
/* conn[0]: source_v1#0.output(portIdx=0) → gain_v1#0.input(portIdx=0) */
0x00, 0x00, 0x01, 0x00,
/* conn[1]: gain_v1#0.output(portIdx=0) → xisnddemom_v1#0.input(portIdx=0) */
0x01, 0x00, 0x02, 0x00,
};
const uint32_t kDefaultLinkFrameLen = sizeof(kDefaultLinkFrame);
/* ---- 固化默认参数表 ---- */
/* 顺序约束:xisnd 的 PARAM_XISND_INIT_ALL 必须最后下发(触发 platform_init)*/
/* 参数数据静态存储(DefaultParamEntry.pData 指向此处)*/
static const float kGainEnable_1 = 1.0f;
static const float kGainSmooth_5 = 5.0f;
static const GainChannelParam kGainDb0 = { 0, 0.0f };
static const GainChannelParam kGainDb1 = { 1, 0.0f };
static const GainChannelParam kGainDb2 = { 2, 0.0f };
/* ... 通道 3~19 同理,实际由工具展开 ... */
static const float kXisndVehCfg = 0.0f;
static const float kXisndChan = 20.0f;
static const float kXisndOutCh = 20.0f;
static const float kXisndMemAlgo = 3000116.0f;
static const float kXisndMemArch = 0.0f;
static const float kXisndMemTune = 80000.0f;
static const float kXisndInitAll = 1.0f;
const DefaultParamEntry kDefaultParams[] = {
/* gain_v1#0:全通道直通(0dB),使能,5ms平滑 */
/* {instanceId, paramId, dataSize, pData} */
{ "gain_v1#0", PARAM_GAIN_ENABLE, sizeof(float), &kGainEnable_1 },
{ "gain_v1#0", PARAM_GAIN_DB, sizeof(GainChannelParam), &kGainDb0 },
{ "gain_v1#0", PARAM_GAIN_DB, sizeof(GainChannelParam), &kGainDb1 },
{ "gain_v1#0", PARAM_GAIN_DB, sizeof(GainChannelParam), &kGainDb2 },
/* ... 通道 3~19 同理,实际由工具展开 ... */
{ "gain_v1#0", PARAM_GAIN_SMOOTH, sizeof(float), &kGainSmooth_5 },
/* xisnddemom_v1#0:车型0,20通道,触发全量初始化(必须最后)*/
{ "xisnddemom_v1#0", PARAM_XISND_VEHICLE_CFG, sizeof(float), &kXisndVehCfg },
{ "xisnddemom_v1#0", PARAM_XISND_ALGO_CHAN, sizeof(float), &kXisndChan },
{ "xisnddemom_v1#0", PARAM_XISND_NUM_OUT_CH, sizeof(float), &kXisndOutCh },
{ "xisnddemom_v1#0", PARAM_XISND_MEM_ALGO, sizeof(float), &kXisndMemAlgo },
{ "xisnddemom_v1#0", PARAM_XISND_MEM_ARCH, sizeof(float), &kXisndMemArch },
{ "xisnddemom_v1#0", PARAM_XISND_MEM_TUNE, sizeof(float), &kXisndMemTune },
{ "xisnddemom_v1#0", PARAM_XISND_INIT_ALL, sizeof(float), &kXisndInitAll }, /* ← SETALLMASK,必须最后 */
};
const uint32_t kDefaultParamsCount =
sizeof(kDefaultParams) / sizeof(kDefaultParams[0]);
新增文件:config/default_link.json(人类可读的链路描述,供工具读取)
{
"version": "3.0",
"global": {
"sampleRate": 48000,
"blockSize": 240,
"dataType": "fract32",
"channels": 20
},
"modules": [
{ "instanceId": "source_v1#0", "typeId": "source_v1" },
{ "instanceId": "gain_v1#0", "typeId": "channel_gain_v1" },
{ "instanceId": "xisnddemom_v1#0", "typeId": "xisnddemom_v1" }
],
"connections": [
{ "from": "source_v1#0.output", "to": "gain_v1#0.input" },
{ "from": "gain_v1#0.output", "to": "xisnddemom_v1#0.input" }
],
"defaultParams": {
"gain_v1#0": { "enable": 1, "gainDb": [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], "smoothTimeMs": 5 },
"xisnddemom_v1#0": { "vehicleCfg": 0, "algoChanNum": 20, "numOutChan": 20,
"memSizeAlgo": 3000116, "memSizeArch": 0, "memSizeTune": 80000 }
}
}
生成命令:
修改文件:main.c(固件自启动入口)
/* main.c
* 嵌入式 DSP 上电启动,不依赖后端。
* 使用 Flash 中固化的默认链路帧和参数表完成初始化,立即进入处理循环。
* 后端连接后可通过 DynChain_RequestRebuild 动态替换链路(有淡出/淡入保护)。
*/
#include "dynchain_interface.h"
#include "dynchain_platform.h"
#include "default_link_config.h"
static DynamicChain *s_pChain = NULL;
int main(void) {
/* ── 0. 硬件初始化 ── */
bsp_init();
/* ── 1. 模块注册(一次性,上电时执行)── */
DynChain_RegisterAllModules();
const DynChainPlatformOps *pPlatform = Platform_GetStaticOps();
/* ── 2. 查询固化链路所需内存大小 ── */
uint32_t totalBytes = 0;
int32_t ret = DynChain_GetMemSize(kDefaultLinkFrame,
kDefaultLinkFrameLen,
&totalBytes);
ASSERT(ret == DYNCHAIN_OK);
/* ── 3. 平台层从 .dram_algo 段分配内存(线性分配器,无碎片)── */
void *pMem = pPlatform->alloc(pPlatform->pCtx, totalBytes, 8u);
ASSERT(pMem != NULL);
/* ── 4. 初始化链路(原地构建,不再需要通信)── */
ret = DynChain_Init(pMem, totalBytes,
kDefaultLinkFrame, kDefaultLinkFrameLen,
pPlatform, &s_pChain);
ASSERT(ret == DYNCHAIN_OK);
/* ── 5. 下发固化默认参数(含 Xisnd SETALLMASK,顺序敏感)── */
for (uint32_t i = 0; i < kDefaultParamsCount; i++) {
const DefaultParamEntry *e = &kDefaultParams[i];
DynChain_SetParam(s_pChain,
e->instanceId,
e->paramId, /* v6.0:无 channelIdx */
e->pData, /* 格式由各模块自定义 */
e->dataSize);
}
/* ── 6. 启动音频处理任务(DMA 中断 / RTOS 任务)── */
audio_task_start(audio_process_callback); /* 永不返回 */
return 0;
}
/* 音频处理回调:由 DMA 搬运完成中断或 RTOS 高优先级任务周期调用 */
void audio_process_callback(float **ppIn, float **ppOut, uint32_t blockSize) {
DynChain_Process(s_pChain,
(const float **)ppIn,
ppOut,
blockSize);
}
/* 上位机连接后的链路替换回调(由通信任务调用)*/
void on_backend_set_link(const uint8_t *pNewFrame, uint32_t frameLen) {
/* 非阻塞:提交新帧,重建在音频线程的 Process() 内部淡出/淡入完成 */
DynChain_RequestRebuild(s_pChain, pNewFrame, frameLen,
Platform_GetStaticOps());
}
完整调用链(纯固件自启动)
═══════════════════════════════════════════════════════
上电自启动流程(无后端参与)
═══════════════════════════════════════════════════════
main()
│
├─ [0] bsp_init() 硬件初始化
│
├─ [1] DynChain_RegisterAllModules()
│ ├─► Register(source_v1, &kSourceFuncs)
│ ├─► Register(channel_gain_v1, &kChannelGainFuncs)
│ └─► Register(xisnddemom_v1, &kXisndDemomFuncs)
│
├─ [2] DynChain_GetMemSize(kDefaultLinkFrame, len, &totalBytes)
│ 读取 Flash 中固化帧,构造临时 pWires[] 后查询各模块:
│ ├─► Source_GetMemSize(pInst0) → N bytes
│ ├─► Gain_GetMemSize(pInst1) → 440 bytes
│ └─► XisndDemom_GetMemSize(pInst2) → ~11.7MB
│ totalBytes ≈ 12MB + 输出缓冲 + 元数据
│
├─ [3] pMem = StaticAlloc(totalBytes, align=8)
│ 从 .dram_algo 线性池分配,地址由 linker script 决定
│
├─ [4] DynChain_Init(pMem, totalBytes, kDefaultLinkFrame, ...)
│ ├─► Source_Init(pInst0)
│ ├─► Gain_Init(pInst1) g->channelCount=20, enable=1
│ ├─► XisndDemom_Init(pInst2)
│ │ S->AlgoChanNum ← Wire_Channels(pInst2->pWires[0])
│ │ S->TuningBuffer → pState + sizeof(instance)
│ │ S->AlgoMem → pState + sizeof(instance) + 80K×4
│ │ ⚠ 此时 NOT 调用 SETALLMASK,等参数下发
│ └─► 拓扑排序:orderedIndices=[0,1,2]
│
├─ [5] 遍历 kDefaultParams[],逐条调用 DynChain_SetParam:
│ ├─► SetParam("gain_v1#0", PARAM_GAIN_ENABLE, &1.0f, 4)
│ ├─► SetParam("gain_v1#0", PARAM_GAIN_DB, &{ch=0, 0.0f}, sizeof(GainChannelParam))
│ ├─► ...(通道 1~19)
│ ├─► SetParam("xisnddemom_v1#0", PARAM_XISND_VEHICLE_CFG, &0.0f, 4)
│ ├─► SetParam("xisnddemom_v1#0", PARAM_XISND_ALGO_CHAN, &20.0f, 4)
│ ├─► SetParam("xisnddemom_v1#0", PARAM_XISND_MEM_ALGO, &3000116.0f, 4)
│ └─► SetParam("xisnddemom_v1#0", PARAM_XISND_INIT_ALL, &1.0f, 4)
│ └─► awe_modXisndDemomSet(S, SETALLMASK)
│ └─► platform_init() → LIB_NO_ERROR ✓ 算法就绪!
│
└─ [6] audio_task_start(audio_process_callback)
└─► 每帧:DynChain_Process(pChain, ppHwIn, ppHwOut, 240)
orderedIndices=[0,1,2]
source → gain → xisnd → 硬件输出
═══════════════════════════════════════════════════════
(可选)后端上位机连接后的链路替换
═══════════════════════════════════════════════════════
通信任务收到新 set_link 帧
└─► on_backend_set_link(pNewFrame, newLen)
└─► DynChain_RequestRebuild(pChain, pNewFrame, newLen, pPlatform)
├─► 复制帧到 pendingFrame[]
├─► rebuildState = REBUILD_FADEOUT ← 触发淡出
└─► 音频线程的 Process() 执行淡出→重建→淡入,无中断
三种启动模式对比
| 模式 | 链路来源 | 参数来源 | 适用场景 |
|---|---|---|---|
| 12.1 有后端驱动 | 后端实时下发 set_link 帧 | 后端逐条下发 set_param | 调音开发阶段,上位机可修改链路 |
| 12.3 固件自启动 | Flash 固化帧 kDefaultLinkFrame[] |
Flash 固化表 kDefaultParams[] |
量产固件,DSP 上电即运行,无需上位机 |
| 12.3 混合模式 | 先用固化帧启动,后端连接后替换 | 先固化参数,后端连接后覆盖 | 量产+在线调音,上电立即出声,后端可动态调整 |
13. 需要新增/修改的文件清单
| 文件 | 位置 | 状态 | 主要内容(v7.0) |
|---|---|---|---|
dynchain_types.h |
include/ |
修改 | WireInstance(wireInfo½/3 + buffer);删除 WireCfg/PortSpec/ModulePortCfg/ProcessInput |
dynchain_platform.h |
include/ |
不变 | DynChainPlatformOps 抽象接口 |
dynchain_interface.h |
include/ |
修改 | DynChain_SetParam/GetParam 去掉 channelIdx |
module_interface.h |
include/ |
修改 | 所有函数指针统一为 ModuleInstance*;删除 getDefaultPorts |
module_type_id.h |
include/ |
不变 | typeNumId 常量定义,含编码规则注释 |
modules_config.h |
include/ |
不变 | 模块裁剪编译开关 |
platform_static.c |
platform/ |
不变 | 嵌入式静态线性分配器 |
platform_dynamic.c |
platform/ |
不变 | PC 仿真 malloc/free |
dynchain_core.c |
framework/ |
新增骨架 | DynChain_Init(8步流程:wireInfo绑定→topo排序→getMemSize→alloc pState→alloc buffers→init)、DynChain_Process(单参数 process(inst))、DynChain_SetParam(snprintf 按需构造 instanceId) |
dynchain_registry.c |
framework/ |
新增 | ModuleRegistryEntry 数组 + Register/FindByNumId/FindByName |
dynchain_parser.c |
framework/ |
修改 | 解析帧时填充 wireInfo½(buffer=NULL),bind pWires[] |
module_registry_all.c |
framework/ |
不变 | 统一注册入口(按 modules_config.h 裁剪) |
gain/gain_module.h |
modules/ |
修改 | 所有函数接受 ModuleInstance*;删除 getDefaultPorts |
gain/gain_module.c |
modules/ |
修改 | Init 从 pInst->pWires[0] 读格式;Process 用 Wire_ChanPtr;SetParam 通过 GainChannelParam.ch 携带通道索引 |
delay/delay_module.h |
modules/ |
修改 | 参照 gain 模板:所有函数接受 ModuleInstance* |
delay/delay_module.c |
modules/ |
修改 | Process 用 Wire_ChanPtr 替换 ppChannel |
xisnddemom/xisnddemom_module.h |
modules/ |
修改 | 所有函数接受 ModuleInstance* |
xisnddemom/xisnddemom_module.c |
modules/ |
修改 | Init 从 Wire_Channels(pInst->pWires[0]) 读通道数;SetParam 去掉 channelIdx |
config/default_link.json |
config/ |
修改 | instanceId 格式改为 "<typeName>#<index>"(如 "gain_v1#0") |
config/default_link_config.h |
config/ |
修改 | DefaultParamEntry 去掉 channelIdx 字段;instanceId 使用新命名 |
config/default_link_config.c |
config/ |
修改 | 固化链路帧(新 wireInfo 格式)+ 参数表(gain_v1#0 / xisnddemom_v1#0) |
tools/gen_default_link.py |
tools/ |
修改 | 生成 wireInfo 字段;参数表条目去掉 channelIdx,instanceId 用新命名 |
main.c |
项目根 | 不变 | 固件自启动路径 |
14. 各版本关键差异对照表
| 方面 | v4.0 | v5.0 | v6.0 | v7.0 |
|---|---|---|---|---|
| 内存管理 | 框架内置 1MB MemPool 静态数组 |
平台层 DynChainPlatformOps.alloc() |
不变 | 不变 |
| Process 接口 | 不详 | (pState, ProcessInput[], numIn, ppOut, outCh, blockSize) |
(pState, WireInstance**ppIn, numIn, WireInstance**ppOut, numOut) |
(ModuleInstance*) — 单参数,wire 通过 pInst->pWires[] 访问 |
| 输出缓冲 | 模块自持 | ModuleInstance.ppOut[] 数组 |
消除独立 ppOut 数组,直接用 WireInstance.pBuffer | 不变(同 v6.0) |
| SetParam 接口 | (paramId: const char*) 字符串 |
(paramTypeId, channelIdx, pVal, valSize) |
(paramId, pData, dataSize) — channelIdx 内化到 pData |
同 v6.0;ModuleInstance* 替换 void* |
| WireInstance | 无 | WireCfg 格式描述(无缓冲区) |
完整 WireInstance(含 pBuffer/ppChannel) |
简化:5字段(wireInfo1/2/3+buffer+sampleRate),删除 ppChannel,改用 Wire_ChanPtr 宏 |
| GetMemSize/Init 格式来源 | 无 | 外传 portCfg 结构体 |
外传 portCfg 结构体 |
从 pInst->pWires[i]->wireInfo 直接读取,无中间结构体 |
| ModuleInstance 标识 | 未明确 | 无 | instanceId 字符串字段 |
typeName[24] + typeIndex;instanceId 按需通过 snprintf 构造 |
| wire 数组布局 | 无 | 分离 ppIn[]/ppOut[] |
分离 ppIn[]/ppOut[] |
单数组 pWires[](AWE 风格):inputs[0..numIn-1], outputs[numIn..] |
| ProcessInput | ProcessInput 封装 ppData+channels |
同左 | 删除,改用 WireInstance | 已删除(同 v6.0) |
| getDefaultPorts | 无 | 有 | 有 | 删除;端口数从帧配置读取 |
| profileTime | 无 | 无 | 无 | ModuleInstance.profileTime(AWE 对齐,cycles×256) |
| 模块注册表 | 无 | 仅 API 声明 | ModuleRegistryEntry 完整定义 + dynchain_registry.c |
不变 |
| instanceId 命名格式 | 未明确 | gain#1(无类型前缀) |
gain_v1#0(格式:<typeName>#<index>) |
同 v6.0 |
| DynChain_Init 流程 | 无 | 无 | 解析帧 + portCfg → getMemSize/init | 8步:wireInfo→bind pWires[]→topo→getMemSize→alloc pState→alloc buffers→init |
| dynchain_core.c | 无骨架 | 无骨架 | 完整 Init/Process/SetParam 骨架(见§10) | 同 v6.0 骨架,接口改为 ModuleInstance* |
| 多 port 模块支持 | 无 | 需手动处理 | WireInstance** 天然支持 N 输入 M 输出 |
pWires[] 天然支持,ModInst_GetInWire/OutWire 宏简化访问 |
| 固件自启动 | 无 | §10.3 新增 | §12.3(内容不变) | §12.3(instanceId 更新为 v7.0 命名) |
文档版本:v7.0 生成时间:2026-03-26 参考文档:DSPAlgo_Architecture.md (v5.0) / awe_module_structure_analysis.md / System_Architecture_v6.md