跳转至

DSP 算法框架架构设计 v7.0

与 v6.0 的核心变化(Q1 深化对齐 AWE) - Q1.1 WireInstance 简化:去掉 ppChannel/channels/blockSize 等独立字段, 改用 wireInfo1/wireInfo2/wireInfo3 三个位域(完全对齐 AWE fw_Wire.h), 通道指针通过 Wire_ChanPtr(w, ch) 宏实时计算(buffer + ch × blockSize) - Q1.2 ModuleInstance 重设计:加入 typeName[24]/typeIndex(AWE 类型名 + 序号), pInWires[]+pOutWires[] 合并为 单数组 pWires[](输入在前、输出在后), 新增 profileTime(AWE ModuleInstanceDescriptor.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_InitDynChain_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
 * 固化在 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 */

说明: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 }
  }
}

生成命令:

python tools/gen_default_link.py config/default_link.json
# 输出:config/default_link_config.c(自动覆盖)


修改文件: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/ 修改 InitpInst->pWires[0] 读格式;ProcessWire_ChanPtrSetParam 通过 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/ 修改 InitWire_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