跳转至

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

与 v5.0 的核心变化 - Q1 Process 重设计:引入 WireInstance(含缓冲区 + 格式),Process 签名改为 (pState, ppInWires[], numIn, ppOutWires[], numOut),模块不再持有独立 ppOut[] 数组 - Q2 SetParam/GetParam 简化:去掉 channelIdx,接口仅保留 (pState, paramId, pData, dataSize), 多通道/复杂参数格式由算法模块内部自定义 - Q3 dynchain_core.c 实现:新增完整链路解析与执行引擎示例,说明 instanceId / typeId / typeNumId 三者含义 - Q4 ID 体系说明:独立章节澄清 MODULE_TYPE_ID_H → 注册表 → ModuleInstance 的完整链路, 以及同类模块多实例(gain_v1#0gain_v1#N)的区分机制


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            ← 基础类型:WireCfg / PortSpec / ModulePortCfg
│   ├── 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 / WireCfg / WireInstance / PortSpec / ModulePortCfg。
 *
 * 设计原则(v6.0):
 *   WireInstance 是模块间音频数据的唯一载体,同时持有格式信息和数据缓冲区。
 *   Process 函数通过 WireInstance** 读写数据,不再依赖外部传入的独立缓冲区参数。
 *   PortSpec / ModulePortCfg 仅用于初始化阶段(GetMemSize / Init),运行期不再使用。
 */
#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:一条音频信号线(v6.0 核心新增)
 *
 * 每条连接对应一个 WireInstance,由框架在 DynChain_Init 时分配。
 * 输出端模块"拥有"(写入)outWire;输入端模块"借用"(只读)inWire,
 * inWire 实际上指向上游模块的 outWire,无需额外拷贝。
 *
 * AWE WireInstance 对照:
 *   buffer      → pBuffer / ppChannel
 *   sampleRate  → sampleRate
 *   wireInfo1   → channels / maxBlockSize / isComplex / sampleSizeBytes
 *   wireInfo2   → blockSize / dataType
 *   wireInfo3   → isIPC 等
 * ════════════════════════════════════════════════════════════════ */
#define WIRE_MAX_CHANNELS  64u

typedef struct WireInstance_ {
    /* 数据缓冲区(框架分配,输出端拥有) */
    float      *pBuffer;           /* 连续存储:channels × blockSize 个 float  */
    float     **ppChannel;         /* 通道指针数组:ppChannel[ch] → pBuffer 内 */

    /* 信号格式(Init 阶段填充,运行期只读) */
    uint32_t    channels;          /* 通道数              wireInfo1[9:0]       */
    uint32_t    blockSize;         /* 当前帧长(samples) wireInfo2[16:0]      */
    uint32_t    maxBlockSize;      /* 最大帧长            wireInfo1[26:10]     */
    float       sampleRate;        /* 采样率(Hz)                             */
    WireDataType dataType;         /* 数据类型            wireInfo2[22:17]     */
    uint8_t     sampleSizeBytes;   /* 每采样字节数        wireInfo1[31:28]     */
    uint8_t     isComplex;         /* 复数信号标志        wireInfo1[27]        */
    uint8_t     isIPC;             /* 跨核 IPC 标志       wireInfo3[20]        */
    uint8_t     reserved;
} WireInstance;

/* ════════════════════════════════════════════════════════════════
 * PortSpec / ModulePortCfg:仅用于 Init 阶段,告知模块 wire 格式
 * ════════════════════════════════════════════════════════════════ */

/* WireCfg:Init 时传入的格式描述(对应 WireInstance 的格式字段子集) */
typedef struct WireCfg_ {
    uint32_t     channels;
    uint32_t     blockSize;
    uint32_t     maxBlockSize;
    float        sampleRate;
    WireDataType dataType;
    uint8_t      sampleSizeBytes;
    uint8_t      isComplex;
    uint8_t      isIPC;
    uint8_t      reserved;
} WireCfg;

/* 端口方向 */
#define PORT_DIR_IN   0u
#define PORT_DIR_OUT  1u

/* 单端口描述符 */
#define PORT_ID_LEN   16

typedef struct PortSpec_ {
    char     id[PORT_ID_LEN];   /* 端口名:"input" / "output" / "input_1" 等 */
    uint8_t  direction;         /* PORT_DIR_IN 或 PORT_DIR_OUT */
    uint8_t  required;          /* 1 = 必须连接 */
    uint8_t  reserved[2];
    WireCfg  wire;              /* 该端口的 Wire 格式(Init 时由框架填充)*/
} PortSpec;

/* 模块端口配置(仅传入 GetMemSize / Init,运行期不使用)*/
#define MAX_PORTS_PER_MODULE 8

typedef struct ModulePortCfg_ {
    uint8_t  numPorts;
    uint8_t  reserved[3];
    PortSpec ports[MAX_PORTS_PER_MODULE];
} ModulePortCfg;

#ifdef __cplusplus
}
#endif
#endif /* DYNCHAIN_TYPES_H */

4.2 module_interface.h(模块接口函数表)

/* module_interface.h
 * 每个算法模块必须实现的 5 个标准接口函数,通过 ModuleFuncTable 注册到框架。
 *
 * v6.0 变化:
 *   Process  → 参数改为 WireInstance**,模块直接从 wire 读取格式和缓冲区
 *   SetParam → 去掉 channelIdx,参数格式由算法模块内部自定义
 *   GetParam → 同上
 */
#ifndef MODULE_INTERFACE_H
#define MODULE_INTERFACE_H

#include "dynchain_types.h"
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* ─────────────────────────────────────────────────────────────────
 * 函数指针类型定义
 * ───────────────────────────────────────────────────────────────── */

/**
 * GetMemSize:返回模块运行状态所需的内存字节数。
 * 决定因素:portCfg 中的通道数、帧长、采样率。
 * 约束:纯函数,不访问全局状态,不分配内存。
 */
typedef uint32_t (*ModuleGetMemSizeFn)(const ModulePortCfg *pPortCfg);

/**
 * Init:在调用方提供的内存区域原地初始化模块。
 * pMem 大小保证 >= GetMemSize() 返回值。
 * 模块不得在此函数内再调用 malloc/alloc。
 */
typedef int32_t (*ModuleInitFn)(void *pMem, const ModulePortCfg *pPortCfg);

/**
 * Process:实时音频处理,每帧调用一次。(v6.0 重设计)
 *
 * @param pState     模块私有状态内存
 * @param ppInWires  输入 wire 数组,ppInWires[portIdx] 指向对应输入端口的 WireInstance
 *                   单输入模块:numIn=1,ppInWires[0] 即上游输出 wire
 *                   多输入模块(混音/上混):numIn>1,逐 portIdx 读取
 * @param numIn      实际输入 wire 数量
 * @param ppOutWires 输出 wire 数组,ppOutWires[portIdx] 指向本模块分配的输出 wire
 *                   模块将处理结果写入 ppOutWires[0]->ppChannel[ch][sample]
 * @param numOut     实际输出 wire 数量
 *
 * 约束:严禁内存分配,严禁阻塞调用,执行时间必须确定性。
 *
 * 读取示例(1 输入 1 输出):
 *   WireInstance *inW  = ppInWires[0];
 *   WireInstance *outW = ppOutWires[0];
 *   uint32_t ch = inW->channels, blk = inW->blockSize;
 *   for (c=0; c<ch; c++)
 *       for (n=0; n<blk; n++)
 *           outW->ppChannel[c][n] = inW->ppChannel[c][n] * gain;
 */
typedef void (*ModuleProcessFn)(
    void             *pState,
    WireInstance    **ppInWires,
    uint8_t           numIn,
    WireInstance    **ppOutWires,
    uint8_t           numOut
);

/**
 * SetParam:设置参数。(v6.0 简化)
 *
 * @param pState    模块私有状态
 * @param paramId   参数类型 ID(各模块头文件中 PARAM_XXX_YYY 定义)
 * @param pData     参数数据指针,格式由算法模块内部定义:
 *                    - 标量参数:float*
 *                    - 多通道参数:模块自定义 struct,如 GainChannelParam{chIdx, value}
 *                    - 批量参数:float array
 * @param dataSize  pData 指向的字节数
 *
 * 去掉 channelIdx 的原因:channelIdx 是参数数据的一部分,
 * 由模块根据 paramId 解析 pData 时自行提取,不需要框架层感知。
 */
typedef int32_t (*ModuleSetParamFn)(
    void          *pState,
    uint16_t       paramId,
    const void    *pData,
    uint32_t       dataSize
);

/**
 * GetParam:读取参数,结果写入 pBuf。(v6.0 简化,同 SetParam)
 */
typedef int32_t (*ModuleGetParamFn)(
    void          *pState,
    uint16_t       paramId,
    void          *pBuf,
    uint32_t       bufSize
);

/* ---- 可选函数 ---- */

/** GetDefaultPorts:返回该模块类型的默认端口配置(供框架 / 前端查询)。*/
typedef void (*ModuleGetDefaultPortsFn)(ModulePortCfg *pOut);

/** Destroy:释放模块内部动态资源(静态分配器场景设为 NULL)。*/
typedef void (*ModuleDestroyFn)(void *pState);

/* ─────────────────────────────────────────────────────────────────
 * ModuleFuncTable:注册到 ModuleRegistry 的函数表
 * ───────────────────────────────────────────────────────────────── */
typedef struct ModuleFuncTable_ {
    ModuleGetMemSizeFn      getMemSize;
    ModuleInitFn            init;
    ModuleProcessFn         process;
    ModuleSetParamFn        setParam;
    ModuleGetParamFn        getParam;
    ModuleGetDefaultPortsFn getDefaultPorts;  /* 可为 NULL */
    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. 对每个模块:sum += def->getMemSize(portCfg)
 *   3. 对每个模块:sum += outChannels × blockSize × sizeof(float) (输出缓冲)
 *   4. 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:框架层模块实例(v6.0 重设计)
 *
 * instanceId 命名约定:<typeName>#<index>,如 "gain_v1#0"、"gain_v1#22"
 *   - typeName 与注册表中的 typeName 一致(如 "gain_v1")
 *   - index 为同类型模块在本链路中的序号(0 起)
 *   - instanceId 在整条链路中全局唯一,后端 / 前端通过此 ID 操作具体实例
 *
 * typeNumId:数字类型 ID,如 0x10010001(MODULE_TYPE_CHANNEL_GAIN_V1)
 *   - 用于在注册表中快速查找 ModuleFuncTable
 *   - 与 instanceId 的关系:一个 typeNumId 对应 N 个不同 instanceId
 *
 * Wire 连接:
 *   pOutWires[portIdx]  — 本模块的输出 wire(框架分配缓冲区,模块写入)
 *   pInWires[portIdx]   — 本模块的输入 wire(指向上游模块的 pOutWires[x])
 *   无需独立 ppOut[][] 数组,直接通过 wire->ppChannel[ch] 访问
 * ════════════════════════════════════════════════════════════════ */
#define INSTANCE_ID_LEN    32
#define MAX_MODULES        32
#define MAX_CONNECTIONS    64
#define MAX_IN_WIRES        8   /* 单模块最大输入端口数(混音模块可多输入)*/
#define MAX_OUT_WIRES       4   /* 单模块最大输出端口数 */

typedef struct ModuleInstance_ {
    /* 标识信息 */
    char             instanceId[INSTANCE_ID_LEN]; /* "gain_v1#0" — 链路内全局唯一  */
    uint32_t         typeNumId;                   /* 0x10010001 — 注册表查找键     */

    /* 功能接口 */
    ModuleFuncTable  funcs;    /* 函数表(值拷贝,避免依赖符号地址)         */
    void            *pState;   /* 模块私有状态内存(指向 pMem 连续区域内)   */

    /* Wire 连接(由框架在 DynChain_Init 阶段绑定)*/
    WireInstance    *pInWires[MAX_IN_WIRES];   /* 输入 wire,指向上游模块的输出 wire  */
    uint8_t          numInWires;
    WireInstance    *pOutWires[MAX_OUT_WIRES]; /* 输出 wire,本模块拥有缓冲区        */
    uint8_t          numOutWires;
    uint8_t          reserved[2];
} ModuleInstance;

/* ════════════════════════════════════════════════════════════════
 * 连接表(拓扑排序 / 框架路由)
 * ════════════════════════════════════════════════════════════════ */
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;

/* ---- 内存大小查询 ---- */
uint32_t ModuleName_GetMemSize(const ModulePortCfg *pPortCfg);

/* ---- 原地初始化 ---- */
int32_t  ModuleName_Init(void *pMem, const ModulePortCfg *pPortCfg);

/* ---- 实时处理(v6.0:通过 WireInstance 访问 I/O)---- */
void     ModuleName_Process(
             void          *pState,
             WireInstance  **ppInWires,   /* ppInWires[portIdx]  */
             uint8_t         numIn,
             WireInstance  **ppOutWires,  /* ppOutWires[portIdx] */
             uint8_t         numOut);

/* ---- 参数设置/读取(v6.0:无 channelIdx,格式由模块自定义)---- */
int32_t  ModuleName_SetParam(
             void       *pState,
             uint16_t    paramId,
             const void *pData,
             uint32_t    dataSize);

int32_t  ModuleName_GetParam(
             void     *pState,
             uint16_t  paramId,
             void     *pBuf,
             uint32_t  bufSize);

/* ---- 默认端口配置(可选)---- */
void     ModuleName_GetDefaultPorts(ModulePortCfg *pOut);

/* ---- 注册到 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;

/* ---- 标准接口函数 ---- */
uint32_t Gain_GetMemSize(const ModulePortCfg *pPortCfg);
int32_t  Gain_Init      (void *pMem, const ModulePortCfg *pPortCfg);

/* v6.0:通过 WireInstance 读写 I/O */
void     Gain_Process   (void *pState,
                         WireInstance **ppInWires,  uint8_t numIn,
                         WireInstance **ppOutWires, uint8_t numOut);

/* v6.0:无 channelIdx,通道索引在 pData 内部携带(GainChannelParam)*/
int32_t  Gain_SetParam  (void *pState, uint16_t paramId,
                         const void *pData, uint32_t dataSize);
int32_t  Gain_GetParam  (void *pState, uint16_t paramId,
                         void *pBuf, uint32_t bufSize);
void     Gain_GetDefaultPorts(ModulePortCfg *pOut);

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
 * ───────────────────────────────────────────────────────────────── */
uint32_t Gain_GetMemSize(const ModulePortCfg *pPortCfg) {
    (void)pPortCfg;
    return (uint32_t)sizeof(GainModuleData);
}

/* ─────────────────────────────────────────────────────────────────
 * 2. Init
 * ───────────────────────────────────────────────────────────────── */
int32_t Gain_Init(void *pMem, const ModulePortCfg *pPortCfg) {
    if (!pMem || !pPortCfg) return DYNCHAIN_ERR_INVALID;

    GainModuleData *g = (GainModuleData *)pMem;
    memset(g, 0, sizeof(GainModuleData));

    /* 找到输入端口,读取 wire 格式 */
    const PortSpec *inPort = NULL;
    for (int i = 0; i < pPortCfg->numPorts; i++) {
        if (pPortCfg->ports[i].direction == PORT_DIR_IN) {
            inPort = &pPortCfg->ports[i]; break;
        }
    }
    if (!inPort) return DYNCHAIN_ERR_INVALID;

    uint32_t ch = inPort->wire.channels;
    if (ch > GAIN_MAX_CHANNELS) ch = GAIN_MAX_CHANNELS;

    g->channelCount = ch;
    g->enable       = 1;
    g->sampleRate   = inPort->wire.sampleRate;
    g->blockSize    = inPort->wire.blockSize;
    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(v6.0:直接操作 WireInstance,无 ppOut 数组)
 * ───────────────────────────────────────────────────────────────── */
void Gain_Process(void *pState,
                  WireInstance **ppInWires,  uint8_t numIn,
                  WireInstance **ppOutWires, uint8_t numOut)
{
    GainModuleData *g = (GainModuleData *)pState;
    if (numIn < 1 || numOut < 1) return;

    WireInstance *inW  = ppInWires[0];
    WireInstance *outW = ppOutWires[0];
    /* 从 wire 直接读取运行时格式,无需外部传参 */
    uint32_t ch  = inW->channels;
    uint32_t blk = inW->blockSize;

    if (!g->enable) {
        /* Bypass:零拷贝,直接将 inW->ppChannel 内容镜像到 outW */
        uint32_t copy_ch = (ch < outW->channels) ? ch : outW->channels;
        for (uint32_t c = 0; c < copy_ch; c++)
            memcpy(outW->ppChannel[c], inW->ppChannel[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  = inW->ppChannel[c];
        float       *pOut = outW->ppChannel[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(v6.0:无 channelIdx 参数,从 pData 内部解析通道索引)
 *
 * 调用示例:
 *   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(void *pState, uint16_t paramId,
                      const void *pData, uint32_t dataSize)
{
    GainModuleData *g = (GainModuleData *)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(v6.0:同 SetParam,通道索引由 pBuf 格式约定)
 *
 * 调用示例(读取通道 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(void *pState, uint16_t paramId,
                      void *pBuf, uint32_t bufSize)
{
    GainModuleData *g = (GainModuleData *)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. GetDefaultPorts
 * ───────────────────────────────────────────────────────────────── */
void Gain_GetDefaultPorts(ModulePortCfg *pOut) {
    if (!pOut) return;
    memset(pOut, 0, sizeof(ModulePortCfg));
    pOut->numPorts = 2;
    strncpy(pOut->ports[0].id, "input",  PORT_ID_LEN - 1);
    pOut->ports[0].direction = PORT_DIR_IN;  pOut->ports[0].required = 1;
    strncpy(pOut->ports[1].id, "output", PORT_ID_LEN - 1);
    pOut->ports[1].direction = PORT_DIR_OUT; pOut->ports[1].required = 1;
}

/* ─────────────────────────────────────────────────────────────────
 * 7. 函数表
 * ───────────────────────────────────────────────────────────────── */
const ModuleFuncTable kChannelGainFuncs = {
    .getMemSize      = Gain_GetMemSize,
    .init            = Gain_Init,
    .process         = Gain_Process,
    .setParam        = Gain_SetParam,
    .getParam        = Gain_GetParam,
    .getDefaultPorts = Gain_GetDefaultPorts,
    .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

/* ---- 标准接口函数(v6.0 签名)---- */
uint32_t XisndDemom_GetMemSize(const ModulePortCfg *pPortCfg);
int32_t  XisndDemom_Init      (void *pMem, const ModulePortCfg *pPortCfg);

/* v6.0:通过 WireInstance 访问 I/O */
void     XisndDemom_Process   (void *pState,
                                WireInstance **ppInWires,  uint8_t numIn,
                                WireInstance **ppOutWires, uint8_t numOut);

/* v6.0:无 channelIdx,pData 直接传 float* 或模块自定义结构体 */
int32_t  XisndDemom_SetParam  (void *pState, uint16_t paramId,
                                const void *pData, uint32_t dataSize);
int32_t  XisndDemom_GetParam  (void *pState, uint16_t paramId,
                                void *pBuf, uint32_t bufSize);
void     XisndDemom_GetDefaultPorts(ModulePortCfg *pOut);

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

uint32_t XisndDemom_GetMemSize(const ModulePortCfg *pPortCfg) {
    (void)pPortCfg;
    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;
}

int32_t XisndDemom_Init(void *pMem, const ModulePortCfg *pPortCfg) {
    awe_modXisndDemomInstance *S = (awe_modXisndDemomInstance *)pMem;
    memset(S, 0, sizeof(awe_modXisndDemomInstance));

    /* 从端口配置中读取 Wire 信息 */
    const PortSpec *inPort = &pPortCfg->ports[0];
    S->AlgoChanNum  = inPort->wire.channels;
    S->NumOutChan   = inPort->wire.channels;
    S->MemSize_Algo = XISND_DEFAULT_ALGO_WORDS;
    S->MemSize_Arch = 0;
    S->MemSize_Tune = XISND_DEFAULT_TUNING_WORDS;

    /* TuningBuffer 和 AlgoMem 布局在 pMem 的后半段 */
    uint8_t *base = (uint8_t *)pMem;
    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(void *pState, uint16_t paramId,
                             const void *pData, uint32_t dataSize)
{
    awe_modXisndDemomInstance *S = (awe_modXisndDemomInstance *)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 — 链路解析与初始化 */
#include "dynchain_core.h"
#include "dynchain_registry.h"
#include <string.h>

/* 线性内存分配辅助(在 pBase 内顺序分配)*/
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 主结构放在 pMem 起始 ── */
    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;

    /* ── 3. 逐模块初始化 ── */
    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 *instId    = (const char *)(p + 4);    /* 32字节 instanceId */
        uint16_t    channels  = p[36] | ((uint16_t)p[37] << 8);
        uint16_t    blockSize = p[38] | ((uint16_t)p[39] << 8);
        float       sampleRate; memcpy(&sampleRate, p+40, 4);
        uint8_t     numIn  = p[44];
        uint8_t     numOut = p[45];
        p += 48;

        ModuleInstance *inst = &chain->instances[i];
        strncpy(inst->instanceId, instId, INSTANCE_ID_LEN - 1);
        inst->typeNumId = typeNumId;

        /* 3.1 查注册表 */
        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.2 构建 portCfg(仅用于 getMemSize / init)*/
        ModulePortCfg portCfg;
        memset(&portCfg, 0, sizeof(portCfg));
        portCfg.numPorts = (uint8_t)(numIn + numOut);
        for (uint8_t k = 0; k < numIn; k++) {
            portCfg.ports[k].direction       = PORT_DIR_IN;
            portCfg.ports[k].required        = 1;
            portCfg.ports[k].wire.channels   = channels;
            portCfg.ports[k].wire.blockSize  = blockSize;
            portCfg.ports[k].wire.sampleRate = sampleRate;
            portCfg.ports[k].wire.dataType   = WIRE_DATA_FLOAT32;
        }
        for (uint8_t k = 0; k < numOut; k++) {
            uint8_t idx = numIn + k;
            portCfg.ports[idx].direction       = PORT_DIR_OUT;
            portCfg.ports[idx].wire.channels   = channels;
            portCfg.ports[idx].wire.blockSize  = blockSize;
            portCfg.ports[idx].wire.sampleRate = sampleRate;
            portCfg.ports[idx].wire.dataType   = WIRE_DATA_FLOAT32;
        }

        /* 3.3 分配并初始化模块状态内存 */
        uint32_t stateSz = (inst->funcs.getMemSize(&portCfg) + 7u) & ~7u;
        if (offset + stateSz > memSize) return DYNCHAIN_ERR_MEMORY;
        inst->pState = LinearAlloc(base, &offset, stateSz, 8u);
        inst->funcs.init(inst->pState, &portCfg);

        pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_INFO,
                       "[Init] [%u] %s (0x%08X) state=%uB", i, instId, typeNumId, stateSz);
    }

    /* ── 4. 解析连接表,分配 WireInstance 和音频缓冲区 ── */
    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];
        p += 4;

        chain->connections[i] = (DynChainConn){ fromMod, fromPort, toMod, toPort };

        ModuleInstance *from = &chain->instances[fromMod];
        ModuleInstance *to   = &chain->instances[toMod];

        /* 4.1 分配 WireInstance */
        WireInstance *wire = (WireInstance *)LinearAlloc(
                                 base, &offset, sizeof(WireInstance), 8u);

        /* 继承源模块输出端口的 wire 格式(从 portCfg 推断,此处直接用简化逻辑) */
        /* 实际实现中,需从 from 的 portCfg 中获取对应 portIdx 的 wire 格式 */
        wire->channels      = 20;              /* 示例:20通道,实际从 portCfg 读 */
        wire->blockSize     = 240;             /* 示例:240 samples              */
        wire->sampleRate    = 48000.0f;
        wire->dataType      = WIRE_DATA_FLOAT32;
        wire->sampleSizeBytes = 4u;

        /* 4.2 分配连续音频缓冲区(ch × blk × 4 bytes)*/
        uint32_t bufSz = (wire->channels * wire->blockSize * sizeof(float) + 7u) & ~7u;
        wire->pBuffer  = (float *)LinearAlloc(base, &offset, bufSz, 8u);

        /* 4.3 分配通道指针数组 */
        uint32_t ptrSz = (wire->channels * sizeof(float *) + 7u) & ~7u;
        wire->ppChannel = (float **)LinearAlloc(base, &offset, ptrSz, 8u);
        for (uint32_t c = 0; c < wire->channels; c++)
            wire->ppChannel[c] = wire->pBuffer + c * wire->blockSize;

        /* 4.4 绑定 wire:from 的输出 → to 的输入(同一指针,零拷贝)*/
        from->pOutWires[fromPort] = wire;
        if (fromPort + 1 > from->numOutWires)
            from->numOutWires = fromPort + 1;

        to->pInWires[toPort] = wire;           /* to 的输入直接指向 from 的输出 */
        if (toPort + 1 > to->numInWires)
            to->numInWires = toPort + 1;

        pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_INFO,
                       "[Wire] [%u]port%u → [%u]port%u  %uch×%usmp",
                       fromMod, fromPort, toMod, toPort,
                       wire->channels, wire->blockSize);
    }

    /* ── 5. 拓扑排序(Kahn BFS)── */
    /* inDegree[i]:模块 i 的未满足输入依赖数 */
    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;
    }

    chain->rebuildState = REBUILD_IDLE;
    chain->fadeFactor   = 1.0f;
    *ppChain = chain;

    pPlatform->log(pPlatform->pCtx, DYNCHAIN_LOG_INFO,
                   "[Init] OK: %u modules, %u conns, total mem=%u B",
                   chain->numInstances, chain->numConnections, offset);
    return DYNCHAIN_OK;
}

10.3 DynChain_Process 骨架

/* DynChain_Process — 每帧调用,按拓扑序执行所有模块 */
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 = inst->pOutWires[0];
            uint32_t ch  = outW->channels;
            uint32_t blk = outW->blockSize;
            for (uint32_t c = 0; c < ch; c++)
                memcpy(outW->ppChannel[c], ppHwIn[c], blk * sizeof(float));
        }

        /* 调用模块自己的 Process(通过 WireInstance 传递 I/O)*/
        inst->funcs.process(inst->pState,
                             inst->pInWires,  inst->numInWires,
                             inst->pOutWires, inst->numOutWires);
    }

    /* ── 将链路末端模块的输出 wire 拷贝到硬件输出 ── */
    int8_t sinkIdx = chain->orderedIndices[chain->numInstances - 1];
    ModuleInstance *sink = &chain->instances[sinkIdx];
    if (sink->numOutWires > 0 && ppHwOut) {
        WireInstance *outW = sink->pOutWires[0];
        uint32_t ch  = outW->channels;
        uint32_t blk = outW->blockSize;
        for (uint32_t c = 0; c < ch; c++)
            memcpy(ppHwOut[c], outW->ppChannel[c], blk * sizeof(float));
    }

    /* ── 应用淡入/淡出增益 ── */
    if (chain->rebuildState != REBUILD_IDLE && ppHwOut) {
        float factor = chain->fadeFactor;
        WireInstance *outW = sink->pOutWires[0];
        uint32_t blk = outW->blockSize;
        uint32_t ch  = outW->channels;
        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 查找模块,调用其 setParam */
int32_t DynChain_SetParam(DynamicChain *chain, const char *instanceId,
                            uint16_t paramId, const void *pData, uint32_t dataSize)
{
    for (uint32_t i = 0; i < chain->numInstances; i++) {
        if (strncmp(chain->instances[i].instanceId, instanceId,
                    INSTANCE_ID_LEN) == 0) {
            return chain->instances[i].funcs.setParam(
                       chain->instances[i].pState, 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].instanceId = "gain_v1#0"                           │
│     instances[0].typeNumId  = 0x10010001                             │
│     instances[0].funcs      = kChannelGainFuncs  (值拷贝)          │
│     instances[0].pState     = <从 pMem 切片的状态内存>               │
│     instances[0].pInWires   = [wire0]           (Init 后绑定)      │
│     instances[0].pOutWires  = [wire1]           (Init 后绑定)      │
│                                                                      │
│     instances[1].instanceId = "gain_v1#1"   ← 第二个 gain 实例      │
│     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[] 中找 instanceId=="gain_v1#0"                     │
│   → 调用 instances[0].funcs.setParam(instances[0].pState, ...)      │
│                                                                      │
│   /* 设置第二个 gain 实例(不同位置)所有通道的 bypass */            │
│   float en = 0.0f;                                                   │
│   DynChain_SetParam(chain, "gain_v1#1", PARAM_GAIN_ENABLE, &en, 4); │
│   → 找 instanceId=="gain_v1#1" → instances[1].funcs.setParam(...)   │
│   → 两个实例的状态内存完全独立,互不影响                             │
└──────────────────────────────────────────────────────────────────────┘

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条连接
        ├─► ModuleRegistry_Lookup(SOURCE_V1)
        │     └─► Source_GetMemSize(portCfg)      → 返回 Source 状态大小
        ├─► ModuleRegistry_Lookup(CHANNEL_GAIN_V1)
        │     └─► Gain_GetMemSize(portCfg)         → sizeof(GainModuleData) = 440B
        ├─► ModuleRegistry_Lookup(XISNDDEMOM_V1)
        │     └─► XisndDemom_GetMemSize(portCfg)
        │           = sizeof(awe_modXisndDemomInstance)    ~72B
        │           + 80000×4                               312KB(TuningBuffer)
        │           + 3000116×4                             11.4MB(AlgoMem)
        │           ≈ 11.7MB
        ├─► 累加每模块输出缓冲:
        │     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)
        ├─► 解析帧,按顺序处理每个模块:
        │   [模块 0: source#1]
        │   ├─► 从 pMem 切片 Source 状态内存
        │   ├─► Source_Init(pSlice, portCfg)
        │   │     portCfg: input=20ch/fract32/48kHz/240samples
        │   └─► 分配输出缓冲 outputBuf[20ch×240×4B]
        │   [模块 1: gain#1]
        │   ├─► 从 pMem 切片 GainModuleData(440B)
        │   ├─► Gain_Init(pSlice, portCfg)
        │   │     ├─► g->channelCount = 20
        │   │     ├─► g->sampleRate   = 48000
        │   │     ├─► g->blockSize    = 240
        │   │     ├─► g->smoothTimeMs = 5.0f
        │   │     └─► g->currentGainLin[0..19] = 1.0f(0dB)
        │   └─► 分配输出缓冲 outputBuf[20ch×240×4B]
        │   [模块 2: xisnd#1]
        │   ├─► 从 pMem 切片 ~11.7MB
        │   ├─► XisndDemom_Init(pSlice, portCfg)
        │   │     ├─► S->AlgoChanNum = 20(来自 portCfg)
        │   │     ├─► S->TuningBuffer → pSlice+sizeof(instance)
        │   │     └─► S->AlgoMem     → pSlice+sizeof(instance)+80K×4
        │   │         注意:不调用 SETALLMASK,等待参数下发
        │   └─► 分配输出缓冲 outputBuf[20ch×240×4B]
        ├─► 解析连接表:
        │     conn[0]: source#1.output → gain#1.input  (20ch, 48000Hz)
        │     conn[1]: gain#1.output   → xisnd#1.input (20ch, 48000Hz)
        └─► 拓扑排序(Kahn BFS)
              orderedIndices = [0, 1, 2]  // source→gain→xisnd

═══════════════════════════════════════════════════════
阶段 4:参数初始化下发(后端依次发送 set_param 帧)
═══════════════════════════════════════════════════════

收到 set_param(gain#1, PARAM_GAIN_DB, ch=2, -3.0f)
  └─► DynChain_SetParam(pChain, "gain#1", 0x0101, &{channelIdx=2, value=-3.0f}, sizeof(GainChannelParam))
        └─► Gain_SetParam(pState, 0x0101, &p, sizeof(p))
              └─► g->gainDb[2] = -3.0f  (targetGainLin 在下次 Process 更新)

收到 set_param(xisnd#1, PARAM_XISND_VEHICLE_CFG, 0, 0.0f)
  └─► DynChain_SetParam → XisndDemom_SetParam → S->VehicleCfg=0 → awe_Set(MASK_VehicleCfg)

收到 set_param(xisnd#1, PARAM_XISND_ALGO_CHAN, 0, 20.0f)
  └─► DynChain_SetParam → XisndDemom_SetParam → S->AlgoChanNum=20 → awe_Set(MASK_AlgoChanNum)

收到 set_param(xisnd#1, PARAM_XISND_INIT_ALL, 0, 1.0f)  ← 触发全量初始化
  └─► DynChain_SetParam → XisndDemom_SetParam(typeId=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, 1, 2]
        ├─► i=0: source#1
        │   └─► Source_Process(inputs=[{ppHwIn,20ch}], ppOut=source.ppOut, 240)
        │         从硬件输入缓冲复制数据到 source.ppOut
        ├─► i=1: gain#1
        │   ├─► 查 connections:conn[0].toModIdx==1 → inputs[0]={source.ppOut,20ch}
        │   └─► Gain_Process(inputs, ppOut=gain.ppOut, 20ch, 240)
        │         对每通道:一阶平滑增益 → gain.ppOut[c][n] = in[c][n] * curGain[c]
        ├─► i=2: xisnd#1
        │   ├─► 查 connections:conn[1].toModIdx==2 → inputs[0]={gain.ppOut,20ch}
        │   └─► XisndDemom_Process(inputs, ppOut=xisnd.ppOut, 20ch, 240)
        │         ├─► 构建 sport_buf_t pIn  = {gain.ppOut, 240×4×20}
        │         ├─► 构建 sport_buf_t pOut = {xisnd.ppOut, 240×4×20}
        │         └─► _pif->ptr_FunTab->process(_pif, pIn, pOut)  ← xisnd算法核心
        └─► 将 xisnd.ppOut 复制到 ppHwOut

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 / portCfg)+ 连接表

/* config/default_link_config.c
 * 固化的默认链路帧和参数表。
 *
 * 重新生成链路帧:
 *   python tools/gen_default_link.py config/default_link.json
 *
 * 默认链路:source#1(20ch/fract32/48kHz/240smp)
 *           → gain#1(20ch) → xisnd#1(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#1  typeNumId=MODULE_TYPE_SOURCE_V1(0x10080001) */
    0x01, 0x00, 0x08, 0x10,
    /* instanceId = "source#1" (8 bytes + null) */
    0x73,0x6F,0x75,0x72,0x63,0x65,0x23,0x31,0x00,0x00,0x00,0x00,
    /* portCfg: 1 output port, 20ch/fract32/48kHz/240smp */
    0x01, 0x14, 0x00, 0xF0, 0x00, 0x00, 0x70, 0x47, 0x03, 0x04, 0x00, 0x00,

    /* Module 1: gain#1  typeNumId=MODULE_TYPE_CHANNEL_GAIN_V1(0x10010001) */
    0x01, 0x00, 0x01, 0x10,
    /* instanceId = "gain#1" */
    0x67,0x61,0x69,0x6E,0x23,0x31,0x00,0x00,0x00,0x00,0x00,0x00,
    /* portCfg: 1 input / 1 output, 20ch/float32/48kHz/240smp */
    0x02, 0x14, 0x00, 0xF0, 0x00, 0x00, 0x70, 0x47, 0x04, 0x04, 0x00, 0x00,

    /* Module 2: xisnd#1  typeNumId=MODULE_TYPE_XISNDDEMOM_V1(0x10070001) */
    0x01, 0x00, 0x07, 0x10,
    /* instanceId = "xisnd#1" */
    0x78,0x69,0x73,0x6E,0x64,0x23,0x31,0x00,0x00,0x00,0x00,0x00,
    /* portCfg: 1 input / 1 output, 20ch/fract32/48kHz/240smp */
    0x02, 0x14, 0x00, 0xF0, 0x00, 0x00, 0x70, 0x47, 0x03, 0x04, 0x00, 0x00,

    /* ---- payload: 连接表 (numConns=2) ---- */
    0x02, 0x00,
    /* conn[0]: source#1.output(portIdx=0) → gain#1.input(portIdx=0) */
    0x00, 0x00, 0x01, 0x00,
    /* conn[1]: gain#1.output(portIdx=1) → xisnd#1.input(portIdx=0) */
    0x01, 0x01, 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#1:全通道直通(0dB),使能,5ms平滑 */
    /* {instanceId, paramId, dataSize, pData} */
    { "gain#1", PARAM_GAIN_ENABLE, sizeof(float),            &kGainEnable_1 },
    { "gain#1", PARAM_GAIN_DB,     sizeof(GainChannelParam), &kGainDb0      },
    { "gain#1", PARAM_GAIN_DB,     sizeof(GainChannelParam), &kGainDb1      },
    { "gain#1", PARAM_GAIN_DB,     sizeof(GainChannelParam), &kGainDb2      },
    /* ... 通道 3~19 同理,实际由工具展开 ... */
    { "gain#1", PARAM_GAIN_SMOOTH, sizeof(float),            &kGainSmooth_5 },

    /* xisnd#1:车型0,20通道,触发全量初始化(必须最后)*/
    { "xisnd#1", PARAM_XISND_VEHICLE_CFG, sizeof(float), &kXisndVehCfg  },
    { "xisnd#1", PARAM_XISND_ALGO_CHAN,   sizeof(float), &kXisndChan    },
    { "xisnd#1", PARAM_XISND_NUM_OUT_CH,  sizeof(float), &kXisndOutCh   },
    { "xisnd#1", PARAM_XISND_MEM_ALGO,    sizeof(float), &kXisndMemAlgo },
    { "xisnd#1", PARAM_XISND_MEM_ARCH,    sizeof(float), &kXisndMemArch },
    { "xisnd#1", PARAM_XISND_MEM_TUNE,    sizeof(float), &kXisndMemTune },
    { "xisnd#1", 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#1", "typeId": "source_v1"       },
    { "instanceId": "gain#1",   "typeId": "channel_gain_v1" },
    { "instanceId": "xisnd#1",  "typeId": "xisnddemom_v1"   }
  ],
  "connections": [
    { "from": "source#1.output", "to": "gain#1.input"  },
    { "from": "gain#1.output",   "to": "xisnd#1.input" }
  ],
  "defaultParams": {
    "gain#1":  { "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 },
    "xisnd#1": { "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 中固化帧,解析模块列表:
  │        ├─► Source_GetMemSize(portCfg)        → N bytes
  │        ├─► Gain_GetMemSize(portCfg)           → 440 bytes
  │        └─► XisndDemom_GetMemSize(portCfg)     → ~11.7MB
  │             totalBytes ≈ 12MB + 输出缓冲 + 元数据
  ├─ [3] pMem = StaticAlloc(totalBytes, align=8)
  │        从 .dram_algo 线性池分配,地址由 linker script 决定
  ├─ [4] DynChain_Init(pMem, totalBytes, kDefaultLinkFrame, ...)
  │        ├─► Source_Init (pSlice0, portCfg)
  │        ├─► Gain_Init   (pSlice1, portCfg)  g->channelCount=20, enable=1
  │        ├─► XisndDemom_Init(pSlice2, portCfg)
  │        │     S->TuningBuffer → pSlice2 + sizeof(instance)
  │        │     S->AlgoMem     → pSlice2 + sizeof(instance) + 80K×4
  │        │     ⚠ 此时 NOT 调用 SETALLMASK,等参数下发
  │        └─► 拓扑排序:orderedIndices=[0,1,2]
  ├─ [5] 遍历 kDefaultParams[],逐条调用 DynChain_SetParam:
  │        ├─► SetParam("gain#1",  PARAM_GAIN_ENABLE, &1.0f, 4)
  │        ├─► SetParam("gain#1",  PARAM_GAIN_DB,     &{ch=0, 0.0f}, sizeof(GainChannelParam))
  │        ├─► ...(通道 1~19)
  │        ├─► SetParam("xisnd#1", PARAM_XISND_VEHICLE_CFG, &0.0f, 4)
  │        ├─► SetParam("xisnd#1", PARAM_XISND_ALGO_CHAN,   &20.0f, 4)
  │        ├─► SetParam("xisnd#1", PARAM_XISND_MEM_ALGO,   &3000116.0f, 4)
  │        └─► SetParam("xisnd#1", 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. 需要新增/修改的文件清单

文件 位置 状态 主要内容(v6.0)
dynchain_types.h include/ 修改 新增 WireInstance(含缓冲区+格式),保留 WireCfg/PortSpec/ModulePortCfg,删除 ProcessInput
dynchain_platform.h include/ 不变 DynChainPlatformOps 抽象接口
dynchain_interface.h include/ 修改 DynChain_SetParam/GetParam 去掉 channelIdx
module_interface.h include/ 修改 ModuleProcessFn 改为 WireInstance**ModuleSetParamFn/GetParamFn 去掉 channelIdx
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(解析帧/构建WireInstance图/拓扑排序)、DynChain_Process(拓扑序执行)、DynChain_SetParam
dynchain_registry.c framework/ 新增 ModuleRegistryEntry 数组 + Register/FindByNumId/FindByName
dynchain_parser.c framework/ 修改 解析帧时按新 WireInstance 格式填充
module_registry_all.c framework/ 不变 统一注册入口(按 modules_config.h 裁剪)
gain/gain_module.h modules/ 修改 新增 GainChannelParam;更新 Process/SetParam/GetParam 签名
gain/gain_module.c modules/ 修改 Process 改用 WireInstance;SetParam/GetParam 通过 GainChannelParam 携带通道索引
delay/delay_module.h modules/ 修改 参照 gain 模板更新签名
delay/delay_module.c modules/ 修改 Process 改用 WireInstance
xisnddemom/xisnddemom_module.h modules/ 修改 更新 Process/SetParam/GetParam 签名
xisnddemom/xisnddemom_module.c modules/ 修改 Process 改用 WireInstance;SetParam 去掉 channelIdx(xisnd 参数均为标量)
config/default_link.json config/ 不变 人类可读的默认链路描述(供工具生成 .c 文件)
config/default_link_config.h config/ 修改 DefaultParamEntry 去掉 channelIdx 字段(对齐新 SetParam API)
config/default_link_config.c config/ 修改 固化链路帧 + 参数表(由 gen_default_link.py 重新生成)
tools/gen_default_link.py tools/ 修改 生成的参数表条目去掉 channelIdx,改为 data 字节流
main.c 项目根 不变 固件自启动路径

14. 各版本关键差异对照表

方面 v4.0 v5.0 v6.0
内存管理 框架内置 1MB MemPool 静态数组 平台层 DynChainPlatformOps.alloc() 不变
Process 接口 不详 (pState, ProcessInput[], numIn, ppOut, outCh, blockSize) (pState, WireInstance**ppIn, numIn, WireInstance**ppOut, numOut) — 通过 wire 携带所有格式信息
输出缓冲 模块自持 ModuleInstance.ppOut[] 数组 消除独立 ppOut 数组,直接用 WireInstance.pBuffer
SetParam 接口 (paramId: const char*) 字符串 (paramTypeId, channelIdx, pVal, valSize) (paramId, pData, dataSize) — channelIdx 内化到 pData,算法自定义格式
WireInstance WireCfg 格式描述(无缓冲区) 完整 WireInstance(含 pBuffer/ppChannel
ProcessInput ProcessInput 封装 ppData+channels 同左 删除,改用 WireInstance
模块注册表 仅 API 声明 ModuleRegistryEntry 完整定义 + dynchain_registry.c
instanceId 未明确 gain#1(无类型前缀) gain_v1#0(格式:<typeName>#<index>
dynchain_core.c 无骨架 无骨架 完整 Init/Process/SetParam 骨架(见§10)
ID 体系 typeNumId 常量,但无体系说明 三 ID(typeNumId/typeName/instanceId)完整链路说明(见§11)
多 port 模块支持 需手动处理 WireInstance**天然支持 N 输入 M 输出
固件自启动 §10.3 新增 §12.3(内容不变)

文档版本:v6.0 生成时间:2026-03-26 参考文档:DSPAlgo_Architecture.md (v5.0) / awe_module_structure_analysis.md / System_Architecture_v6.md