系统架构设计 (v6.0)
1. 系统概述
本系统是一套车载 DSP 算法调音工具,支持算法链路的动态构建、参数实时调整与下发。系统分三层:Vue3 前端 → .NET8 后端 → C DSP 固件(或 PC 仿真 DLL)。
┌───────────────────────────────────────────────────────────────────────────────────────────┐
│ 前端调音工具 (Frontend) │
│ Vue3 + TypeScript + Pinia + Vite │
│ │
│ ┌────────────────────┐ ┌───────────────────────────────────────┐ ┌───────────────┐ │
│ │ 模块调色板 │ │ 链路编辑画布 │ │ 调音弹窗 │ │
│ │ (LeftPanel) │ │ LinkEditor (Canvas + Pan/Zoom) │ │ (每模块独立) │ │
│ │ │ │ │ │ │ │
│ │ moduleLibrary.ts: │ │ ChainNode (模块节点) │ │ GainTuning │ │
│ │ source_v1 │ │ ├── instanceId / moduleType │ │ DelayTuning │ │
│ │ channel_gain_v1 │ │ ├── ports[]: PortDescriptor │ │ XisndTuning │ │
│ │ ut_delay_20ch_v1 │ │ │ ├── Wire格式信息 │ │ │ │
│ │ xisnddemom_v1 │ │ │ │ (channels/blockSize/ │ │ BottomProperty│ │
│ │ common_eq_v1 │ │ │ │ sampleRate/dataType) │ │ Panel │ │
│ │ ...35+ modules │ │ │ └── isComplex/rows/cols/IPC │ └───────────────┘ │
│ └────────────────────┘ │ └── params: ModuleParams │ │
│ │ ├── GainModuleParams │ ┌───────────────┐ │
│ │ │ gainDb[] / mute[] │ │ StatusBar │ │
│ │ ├── XisndModuleParams │ └───────────────┘ │
│ │ │ vehicleCfg / algoChanNum │ │
│ │ │ memSizeAlgo / tuningParams │ │
│ │ └── SourceModuleParams │ │
│ │ blockSize/numChannels/... │ │
│ │ ChainEdge (连线) │ │
│ │ ├── fromModule/fromPort │ │
│ │ ├── toModule/toPort │ │
│ │ └── channels/sampleRate/blockSize │ │
│ └───────────────────────────────────────┘ │
│ │
│ linkStore (Pinia): currentLink(LinkConfig) / params Map / workspace │
│ LinkConfig: { version:"3.0", global:{sr,bs,dt}, chains:{ root: ChainDefinition } } │
└──────────────────────────────────────────┬────────────────────────────────────────────────┘
│ WebSocket (JSON) ws://host:5000/ws
│ write_link / set_param / get_param
┌──────────────────────────────────────────▼────────────────────────────────────────────────┐
│ 后端服务 (Backend) │
│ ASP.NET Core 8 (.NET 8) │
│ │
│ ┌───────────────┐ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ WS Handler │ │ ChainFlattener(链路展平服务) │ │
│ │ 消息路由 │ │ │ │
│ │ │ │ LinkConfig (JSON) │ │
│ │ write_link │ │ │ 1. 递归展平 SubGraphNode → 前缀化 instanceId │ │
│ │ set_param │ │ │ 2. PropagateWireInfo() → 所有-1端口解析为确定性值 │ │
│ │ get_param │ │ │ 3. TypeNameToNumId() │ │
│ │ read_link │ │ ▼ │ │
│ │ set_ambiance │ │ FlattenedLinkConfig │ │
│ └───────────────┘ │ ├── modules[]: FlatModuleEntry │ │
│ │ │ ├── instanceId / typeNumId │ │
│ ┌───────────────┐ │ │ ├── ports[]: ResolvedPortSpec │ │
│ │ ParamStore │ │ │ │ (channels/sr/blockSize/dataType 已全部确定) │ │
│ │ 参数缓存 │ │ │ └── initParams[]: ParamSetEntry{typeId,chIdx,value} │ │
│ └───────────────┘ │ └── connections[]: FlatConnectionEntry │ │
│ │ {fromIdx,fromPortIdx,toIdx,toPortIdx,ch,sr} │ │
│ ┌───────────────┐ │ │ │ │
│ │ ModuleRegistry│ │ ▼ │ │
│ │ 模块注册表 │ │ BinaryFrameBuilder │ │
│ └───────────────┘ │ ├── BuildSetLink() → CMD=0x02 Binary Frame v3 │ │
│ │ └── BuildSetParam() → CMD=0x01 Binary Frame │ │
│ ┌───────────────┐ └──────────────────────────────────────────────────────────────────┘ │
│ │ DspConnection │ │
│ │ TCP / Serial │ ┌──────────────────────────────────────────────────────────────────┐ │
│ └───────────────┘ │ AudioEngineService (PC仿真音频引擎) │ │
│ │ ├── NAudio WASAPI 声卡枚举/采集/播放 │ │
│ │ └── P/Invoke → DynChain_Process() │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└─────────┬──────────────────────────────────────────────────┬──────────────────────────────┘
│ Binary Frame v3 (TLV) │ P/Invoke (DllImport)
│ CMD=0x02 set_link │ PC 仿真路径
│ CMD=0x01 set_param │
┌─────────▼──────────────────────────┐ ┌─────────────▼────────────────────────────┐
│ DSP 算法层(嵌入式) │ │ PC 仿真层 │
│ C 语言嵌入式 DSP 固件 │ │ DynamicChain.dll + NAudio │
│ │ │ │
│ ┌──────────────────────────────┐ │ │ ┌──────────────────────────────────┐ │
│ │ DynamicChain 框架 │ │ │ │ DynamicChain 框架(同源代码) │ │
│ │ │ │ │ │ │ │
│ │ instances[]:ModuleInstance │ │ │ │ instances[]:ModuleInstance │ │
│ │ ┌──────────────────────┐ │ │ │ │ ┌──────────────────────────┐ │ │
│ │ │ gain#1 │ │ │ │ │ │ gain#1 │ │ │
│ │ │ typeId:channel_gain │ │ │ │ │ │ pState→GainModuleData │ │ │
│ │ │ portCfg:{in:20ch, │ │ │ │ │ │ ├params.gainDb[0..19] │ │ │
│ │ │ out:20ch} │ │ │ │ │ │ └channelState[0..19] │ │ │
│ │ │ pState→GainModuleData│ │ │ │ │ └──────────────────────────┘ │ │
│ │ └──────────────────────┘ │ │ │ │ ┌──────────────────────────┐ │ │
│ │ ┌──────────────────────┐ │ │ │ │ │ xisnd#1 │ │ │
│ │ │ xisnd#1 │ │ │ │ │ │ pState→awe_modXisnd... │ │ │
│ │ │ typeId:xisnddemom_v1 │ │ │ │ │ │ ├instance(ModuleInst) │ │ │
│ │ │ portCfg:{ │ │ │ │ │ │ ├enable/VehicleCfg │ │ │
│ │ │ in:20ch,fract32, │ │ │ │ │ │ ├AlgoChanNum/NumOutChan │ │ │
│ │ │ out:20ch,fract32} │ │ │ │ │ │ ├MemSize_Algo/Arch/Tune │ │ │
│ │ │ pState→ │ │ │ │ │ │ ├TuningBuffer(80K words)│ │ │
│ │ │ awe_modXisndDemom │ │ │ │ │ │ └AlgoMem(3M words) │ │ │
│ │ │ Instance │ │ │ │ │ │ ├adsp_v1_platform │ │ │
│ │ │ ├AlgoMem(3M words) │ │ │ │ │ │ ├adsp_audio_vtbl_t │ │ │
│ │ │ └TuningBuffer(80K) │ │ │ │ │ │ ├HandlWrapper │ │ │
│ │ └──────────────────────┘ │ │ │ │ │ ├MediaFmt(Input) │ │ │
│ │ │ │ │ │ │ └MediaFmt(Output) │ │ │
│ │ connections[]: │ │ │ │ └──────────────────────────┘ │ │
│ │ DynChainConnection │ │ │ │ │ │
│ │ [{from:0,to:1,ch:20, │ │ │ │ connections[] / orderedIndices[]│ │
│ │ sr:48000},...] │ │ │ │ MemPool[≥14MB for Xisnd] │ │
│ │ orderedIndices[]:topo order │ │ │ └──────────────────────────────────┘ │
│ │ MemPool[≥14MB]: │ │ │ │
│ │ GainModuleData(440B) │ │ │ 声卡→DynChain_Process→声卡 │
│ │ +outputBuf(20ch×240×4B) │ │ │ WAV抓取 / DynChain_GetMetrics │
│ └──────────────────────────────┘ │ └──────────────────────────────────────────┘
└────────────────────────────────────┘
2. 核心需求
| 需求 | 说明 |
|---|---|
| 动态链路构建 | 调音工具可在 UI 上拖拽组合模块,动态生成算法处理链 |
| 参数实时下发 | 调整任意参数后,变更实时通过 WebSocket → Backend → DSP 硬件生效 |
| 参数双向同步 | DSP 状态可回读到前端;多终端同时连接时参数变更广播同步 |
| 链路持久化 | 链路配置可保存为 JSON 文件,支持断电恢复和版本管理 |
| 模块化扩展 | 新算法模块只需实现标准接口即可接入系统,无需修改框架代码 |
| 以 Gain/Delay 为原型 | 首先以增益(Gain)和延迟(Delay)两个模块验证全链路框架 |
| 三种工作模式 | 系统支持 Chain Builder / Tuning / Test-Verify 三种前端工作模式,各模式对链路编辑和参数操作权限不同 |
| 调试窗口 | 提供延时、算力、内存、WAV 抓取、日志等多个调试面板,供开发和测试使用 |
| PC 仿真 | 后端调用 DynamicChain.dll + NAudio/PortAudio,无需实体 DSP 硬件即可在 PC 上实时运行算法链路 |
| 自动化测试 | 支持基于 YAML 脚本的自动化测试,可批量验证参数精度,输出 JUnit XML 报告 |
| 可变通道/采样率 | 链路中途通道数或采样率可变(如 Upmixer 2ch→12ch),模块级端口配置独立于全局配置 |
| C# 音频引擎可行性 | 评估并选定 C# 作为 PC 仿真音频引擎(P/Invoke + NAudio 为原型推荐方案) |
| 多端口扇入 | Mixer/Router 等模块可声明 N 个输入端口;ChainCanvas 以具名端口 ID 可视化多路连线扇入 |
| 子图嵌套画布 | Group 节点封装子链路;双击进入子画布编辑;面包屑导航跨层级跳转 |
| 跨层数据结构对齐 | 前端 JSON (LinkConfig) → 后端展平 (FlattenedLinkConfig) → Binary Frame v3 → DSP DynamicChain 连接表,各层数据结构显式定义并对齐 |
3. 三种工作模式
系统支持三种前端工作模式,后端根据当前模式在消息处理器中检查操作权限。
┌─────────────────────────────────────────────────────────────────────┐
│ 模式切换示意 │
│ │
│ [Chain Builder] ──── 全功能模式,链路结构完全可编辑 │
│ [Tuning Mode] ──── 仅加载本地 JSON,参数可调,链路锁定 │
│ [Test/Verify] ──── 可实时读取链路,参数可读写,链路结构只读 │
└─────────────────────────────────────────────────────────────────────┘
3.1 Mode A — Chain Builder(链路构建模式)
用途:开发阶段,完整编辑算法链路结构并下发到 DSP 或 PC 仿真。
| 能力 | 说明 |
|---|---|
| 链路编辑 | 添加/删除模块,调整 order,修改通道数和采样率 |
| 参数编辑 | 所有参数(系统参数 + 调音参数)均可修改 |
| 链路回读 | 可从 DSP 或 PC 仿真引擎读取当前运行链路 |
| 保存/加载 | 从本地文件或后端 JSON 保存/加载链路 |
| DSP/PC 连接 | 支持连接实体 DSP(TCP/串口)或 PC 仿真引擎 |
被锁定内容:无(全功能开放)
3.2 Mode B — Tuning Mode(调音模式)
用途:调音工程师在已固定链路基础上调整音效参数,防止误改链路结构。
| 能力 | 说明 |
|---|---|
| 链路加载 | 仅从本地 JSON 文件加载,不可实时从 DSP 读取 |
| 参数编辑 | 仅 tunable 参数(增益 dB、延迟 samples、EQ 参数等)可调 |
| 链路查看 | 链路结构只读展示(不可编辑画布) |
| 保存 | 可保存参数快照(仅参数,链路结构不变) |
被锁定内容: - 模块顺序(order) - 通道数(channels) - 采样率(sampleRate) - 帧大小(frameSize) - 模块添加/删除
参数可调性分类:
| 类别 | 可调 | 说明 |
|---|---|---|
tunable |
是 | gainDb, delaySamples, EQ 参数等 |
system |
否 | enable(模块使能), smoothTime, 链路顺序 |
3.3 Mode C — Test/Verify(测试验证模式)
用途:在 Mode B 基础上增强,可在线读取 DSP 实时状态进行验证。
| 能力 | 说明 |
|---|---|
| 链路获取 | 可选:从 DSP/PC 实时读取链路结构(只读) |
| 参数读写 | 所有 tunable 参数可读写 |
| 参数回读 | 可从 DSP 实时回读当前参数值 |
| 链路结构 | 只读,不可修改 |
| 调试工具 | 完整 Debug Panel 可用(见第 9 节) |
被锁定内容:链路编辑(与 Mode B 相同)
3.4 后端模式权限适配
后端通过 set_mode 消息切换当前模式,并在相应消息处理器中检查权限:
模式 A (chain_builder): write_link、set_param、connect_dsp 全部允许
模式 B (tuning): write_link 拒绝;仅 paramRole=tunable 的 set_param 允许
模式 C (test_verify): write_link 拒绝;set_param 允许;新增 get_dsp_chain 允许
4. 模块分类系统
系统中的算法模块按功能分为以下类别:
| 类别 ID | 名称 | 说明 | 示例模块 |
|---|---|---|---|
gain |
增益控制 | 音量、增益、静音 | channel_gain_v1, headroom_v1 |
delay |
延迟 | 通道对齐延迟 | ut_delay_20ch_v1 |
filter |
滤波器/EQ | 参数均衡、频率整形 | common_eq_v1, eq_4ch_v1 |
dynamics |
动态处理 | 压缩、限幅 | limiter_v1, mdrc_v1 |
mixing |
混音/上混 | 声道混合、环绕声 | surround_v1, upmixer_v1 |
soundDesign |
音效设计 | 失真、虚拟低音 | distortion_v1, virtual_bass_v1 |
spatialAudio |
空间音频 | 混响、声像 | ese_aves_v1, rm_reverb_20ch_v1 |
voice |
语音处理 | 降噪、回声消除 | ecnr_v1 |
logic |
逻辑控制 | 条件路由 | logic_v1 |
debug |
调试工具 | 日志、测试音 | log_window_v1, td_chime_v1 |
5. 部署场景
场景一:本地调试(开发机)
- Frontend:
http://localhost:5173 - Backend:
ws://localhost:5000/ws - DSP: TCP
192.168.x.x:9000或串口COM3
场景二:局域网调试(多端接入)
- Backend 绑定
0.0.0.0:5000 - 多个前端客户端同时连接,参数变更广播同步
场景三:跨网络(公网/VPN)
场景四:PC 仿真(无实体 DSP)
PC(Frontend + Backend + DynamicChain.dll + NAudio)
前端 WebSocket ──► 后端(ws://localhost:5000/ws)
后端 P/Invoke ──► DynamicChain.dll (C 算法库)
NAudio WASAPI ──► 声卡采集 ──► DLL Process() ──► 声卡播放
- 无需连接实体 DSP 硬件
- 算法链路与嵌入式固件共用同一 C 代码库(DLL 形态)
- 前端通过相同的 WebSocket 接口(端口 5000)进行调音操作
6. 核心数据流
6.1 参数下发流程
用户在前端调整参数
│
▼
前端 useParamStore.setParam(instanceId, paramId, value)
│ WebSocket: set_param { instanceId, paramId, value, channel }
▼
后端 HandleSetParam()
├── paramStore["gain#1.gainDb#0"] = "-3.5" (内存存储)
├── BinaryFrame.Build(instanceId, paramId, value) → byte[]
│ └── DSP Binary Frame (TLV 格式)
├── dspConn.SendAsync(frame) (异步发送)
└── BroadcastExcept(sender, param_update) (多端同步)
│ Binary Frame
▼
DSP DynamicChain_SetParam(instanceId, paramId, rawValue)
└── 找到对应 ModuleInstance → Module.SetParam()
6.2 链路构建流程
用户在链路编辑画布拖拽模块
│
▼
前端 useChainStore.addModule(moduleType, position)
└── 生成 ChainNode { instanceId, moduleType, ports, position }
│
│ (保存时) WebSocket: write_link { chains, global, rootChainId }
▼
后端 HandleWriteLink()
├── 持久化 JSON → ./data/current_link.json
├── ChainFlattener.Flatten(rootChainId) → FlattenedLinkConfig
└── 解析展平链路 → 生成 DSP 链路配置二进制 (Binary Frame v3)
│ Binary Frame v3 CMD=0x02 (set_link)
▼
DSP DynamicChain_LoadConfig(flattenedConfig)
├── 按连接表注册模块实例: ModuleRegistry.Create(moduleType)
├── 分配每模块独立输出缓冲区: PerModuleOutputBuffer.Alloc()
└── 初始化每个模块: module.Init()
6.3 参数回读流程
前端重连或手动同步
│ WebSocket: get_all_params { instanceId }
▼
后端 HandleGetAllParams()
└── 从 paramStore 返回该实例所有参数
│ get_all_params_ack { params: {...} }
▼
前端更新 Pinia Store → UI 同步刷新
6.4 PC 仿真音频数据流
声卡输入 (NAudio WASAPI)
│ float[] 音频帧 (nbSamples × inputChannels)
▼
AudioEngineService.OnAudioInput()
│ P/Invoke
▼
DynChain_Process(pChain, ppIn, ppOut, nbSamples) [DynamicChain.dll]
├── 按连接表顺序调用每个 ModuleInstance.Process()
└── 每模块使用独立输出缓冲区 (PerModuleOutputBuffer)
│ float[] 处理后音频帧
▼
声卡输出 (NAudio WASAPI)
6.5 调试数据采集流程
DSP/算法层 各模块 Process() 执行
├── 记录 Process() 耗时 → cpuUs[]
├── 上报 MemPool.used / total
└── 注入延时量 → latencyMs[]
│ (周期上报,默认 100ms 间隔)
▼
后端 DebugDataService.Collect()
├── 汇总各模块 CPU 占用
├── 汇总内存使用
└── 计算总链路延时
│ WebSocket 推送: debug_metrics { latency, compute, memory }
▼
前端 DebugPanel 实时刷新显示
├── DebugLatencyPanel → 总延时及各模块延时分解
├── DebugComputePanel → 各模块 CPU 占用排行
└── DebugMemoryPanel → 内存池使用率
7. 链路端口配置(多端口 / 子图 / 跨层对齐)
7.1 问题背景
旧版 DSPLink 的平铺端口字段(inputChannels / outputChannels / inputSampleRate / outputSampleRate)存在两个不足:
- 单端口假设:每个模块只能有一个输入端口和一个输出端口,无法支持 Mixer(多路扇入)、Router(多路扇出)等模块。
- 无子图概念:复杂链路只能平铺展示,无法将一组模块封装为可复用的 Group 节点。
v3.0 引入 PortDescriptor、SubGraphNode 和 LinkConfig(替换旧 DSPLink)来解决上述问题。
7.2 核心类型定义(TypeScript)
// -------------------------------------------------------
// 端口描述符(v6.0:完整 Wire 信息,对应 AWE WireInstance 位域)
// -------------------------------------------------------
interface PortDescriptor {
id: string // 端口唯一标识,如 "input_0"、"output"
direction: 'input' | 'output'
required: boolean // false 表示可选端口(用户可删除)
label: string // 面板显示名称,如 "Ch A"
// wireInfo1 字段
channels: number // -1 表示继承上游;正整数表示固定值(对应 wireInfo1[9:0])
maxBlockSize?: number // 最大 block 大小(wireInfo1[26:10])
isComplex?: boolean // 是否复数信号(wireInfo1[27])
sampleSizeBytes?: number // 每采样字节数(wireInfo1[31:28])
// wireInfo2 字段
blockSize: number // 当前 block 大小,-1 继承(wireInfo2[16:0])
dataType: WireDataType | -1 // 数据类型,-1 继承(wireInfo2[22:17])
// wireInfo3 字段
rows?: number // 矩阵行数(wireInfo3[9:0])
cols?: number // 矩阵列数(wireInfo3[19:10])
isIPC?: boolean // 跨核 IPC(wireInfo3[20])
isPrivate?: boolean // 私有 wire(wireInfo3[21])
isClockMaster?: boolean // 时钟主源(wireInfo3[22])
// Wire 基本属性
sampleRate: number // -1 表示继承上游(WireInstance.sampleRate)
}
type WireDataType = 'float32' | 'fract32' | 'int16' | 'int32' | 'int24'
// -------------------------------------------------------
// 模块算法参数基类(v6.0:与 PortDescriptor Wire信息严格分离)
// -------------------------------------------------------
interface ModuleParams {
[key: string]: unknown
}
// 具体模块参数类型(示例)
interface GainModuleParams extends ModuleParams {
enable: number
gainDb: number[] // 每通道增益(dB)
mute: number[] // 每通道静音
phase: number[] // 每通道极性翻转
smoothTimeMs?: number
}
interface XisndModuleParams extends ModuleParams {
enable: number
vehicleCfg: number // 车型配置
algoChanNum: number // 算法处理通道数
numOutChan: number // 输出通道数
memSizeAlgo: number // 算法核心内存(words)
memSizeArch: number // 架构内存(words)
memSizeTune: number // 调音缓冲内存(words)
tuningParams?: TuningParam[] // 在线调音参数(通过TuningBuffer通道传递)
}
interface SourceModuleParams extends ModuleParams {
blockSize: number
numChannels: number
sampleRate: number
dataType: WireDataType
isComplex: number
}
// -------------------------------------------------------
// 普通链路节点(v6.0:ports = Wire信息,params = 算法参数,两者严格分离)
// -------------------------------------------------------
interface ChainNode {
instanceId: string
moduleType: string
order: number
enabled: boolean
position: { x: number; y: number }
ports: PortDescriptor[] // ① Wire 格式信息(随 set_link 下发)
params?: ModuleParams // ② 算法参数(随 set_param 独立下发)
}
// -------------------------------------------------------
// 子图节点:moduleType 固定为 'subgraph'
// -------------------------------------------------------
interface SubGraphNode {
instanceId: string
moduleType: 'subgraph'
subGraphId: string // 引用 LinkConfig.chains 中的子链路 ID
position: { x: number; y: number }
ports: PortDescriptor[] // 镜像子链路的 externalPorts
}
// -------------------------------------------------------
// 连线:fromPort / toPort 替换旧的隐式单端口假设
// -------------------------------------------------------
interface ChainEdge {
id: string
fromModule: string // 上游节点 instanceId
fromPort: string // 上游端口 ID(对应 PortDescriptor.id)
toModule: string // 下游节点 instanceId
toPort: string // 下游端口 ID
channels: number // 连线上的实际信号通道数(前端自动计算)
sampleRate: number // 连线上的实际采样率(前端自动计算)
}
// -------------------------------------------------------
// 链路定义:普通链路 or 子图链路
// -------------------------------------------------------
interface ChainDefinition {
id: string
name: string
nodes: (ChainNode | SubGraphNode)[]
edges: ChainEdge[]
externalPorts?: PortDescriptor[] // 仅子图链路需要,定义对外暴露的端口
}
// -------------------------------------------------------
// 顶层配置(v3.0:含 dataType 全局字段,替换旧 v2.2 格式)
// -------------------------------------------------------
interface LinkConfig {
version: '3.0'
rootChainId: string // 根链路 ID,通常为 "root"
global: {
channels: number
sampleRate: number
blockSize: number
dataType: WireDataType // v3.0 新增:全局默认数据类型
}
chains: Record<string, ChainDefinition> // key = chainId
}
7.3 ModuleDescriptor 端口规则(v3.0)
interface ModuleDescriptor {
moduleType: string
category: string
displayName: string
// defaultPorts 替换旧的 portRule 字段
defaultPorts: PortDescriptor[]
// 可选:Mixer 等模块支持用户动态增减输入端口
maxDynamicInputPorts?: number
params: ParamDescriptor[]
}
defaultPorts直接列出模块所有默认端口,不再区分fixed / passthrough / configurable类型,规格由channels: -1(继承)或正整数(固定)表达。maxDynamicInputPorts非零时,前端画布允许用户在该模块上增加或删除输入端口(如 Mixer 的input_0 … input_N)。
示例(Mixer 模块):
const AudioMixerDescriptor: ModuleDescriptor = {
moduleType: 'audio_mixer_v1',
category: 'mixing',
displayName: 'Audio Mixer',
defaultPorts: [
{ id: 'input_0', direction: 'input', channels: -1, sampleRate: -1, label: 'Ch A', required: true },
{ id: 'input_1', direction: 'input', channels: -1, sampleRate: -1, label: 'Ch B', required: false },
{ id: 'output', direction: 'output', channels: -1, sampleRate: -1, label: 'Mix Out', required: true },
],
maxDynamicInputPorts: 8,
params: [],
}
7.4 链路信号流传播规则
前端 ChainCanvas 在链路编辑时,沿 ChainEdge 传播信号规格:
- 根链路起始节点:取
LinkConfig.global.channels和LinkConfig.global.sampleRate。 - 对每条
ChainEdge: - 上游节点
fromPort的channels/sampleRate若为-1,向上游递归解析实际值。 - 解析后的值写入
ChainEdge.channels和ChainEdge.sampleRate(前端自动计算,不由用户手动填写)。 - 若相邻端口规格不匹配(如通道数不符),画布在对应连线上显示橙色警告。
7.5 子图展平策略(后端 ChainFlattener)
ChainFlattener 在后端将含子图的 LinkConfig 展平为 DSP 可直接消费的 FlattenedLinkConfig:
展平规则:
1. 遍历根链路 nodes:
- 普通 ChainNode → 直接保留,instanceId 不变
- SubGraphNode (instanceId="group#1") → 递归展平子链路
子链路中每个节点的 instanceId 前缀化:
"group#1.upmixer#1"、"group#1.delay#1" ...
2. 边的处理:
- 子图内部边:fromModule/toModule 同步前缀化
- 跨子图边(父→子图入口 / 子图出口→父):
替换 SubGraphNode 端点为子图内对应的边界模块
3. 展平结果 FlattenedLinkConfig 只包含普通模块和连接表,
不含任何 SubGraphNode 或 subGraphId 引用
4. DSP 收到的是完全平坦的模块列表 + 连接表,
对子图结构完全透明
展平后 instanceId 示例:
7.6 DSP 端适配(v3.0)
DSP 不再使用全局 ChainRunConfig + 单一 ping-pong 缓冲区,改为:
- 连接表(Connection Table):显式记录
fromModuleIdx / fromPortIdx / toModuleIdx / toPortIdx,DSP 按连接表驱动数据路由,不依赖模块顺序。 - 每模块独立输出缓冲区:每个
ModuleInstance拥有自己的PerModuleOutputBuffer,取代单一 ping-pong 缓冲区。
/* 连接表项(由 set_link Binary Frame v3 附带传递)*/
typedef struct ConnectionEntry_ {
uint16_t fromModuleIdx;
uint16_t fromPortIdx;
uint16_t toModuleIdx;
uint16_t toPortIdx;
} ConnectionEntry;
/* 每模块的端口配置(同样由 set_link 帧附带传递)*/
typedef struct ModulePortCfg_ {
uint32_t inputChannels;
uint32_t outputChannels;
uint32_t inputSampleRate;
uint32_t outputSampleRate;
} ModulePortCfg;
8. PC 仿真架构
8.1 PC 仿真运行原理
┌─────────────────────────────────────────────────────────────────────┐
│ PC 仿真数据流 │
│ │
│ [音频输入] ──► AudioEngineService ──► [音频输出] │
│ (声卡/文件) │ (声卡/文件) │
│ │ P/Invoke │
│ ▼ │
│ DynamicChain.dll (C 算法库) │
│ ├── ChannelGain_v1 │
│ ├── UtDelay_20ch_v1 │
│ └── ... │
│ │
│ 前端 WebSocket ──► 后端 ──► DynChain_SetParam() │
│ (同实体 DSP 接口,固定端口 5000) │
└─────────────────────────────────────────────────────────────────────┘
8.2 技术方案分析(C# + C 算法库)
方案一(推荐原型):C# P/Invoke + NAudio
- C 算法库编译为
DynamicChain.dll(Windows)/libDynamicChain.so(Linux) - C# 通过
[DllImport]P/Invoke 调用算法接口 - NAudio WASAPI Exclusive 模式采集/播放(10–20 ms 延迟)
- 音频回调中禁用 GC 压缩:
GCSettings.LatencyMode = SustainedLowLatency - 优点:单进程,开发最简单,沿用现有 .NET8 架构
- 缺点:C# GC 偶发停顿(< 1ms),WASAPI 需 Windows,延迟略高于纯 C
方案二(推荐生产):双进程(C# Web + C 音频进程)
- C# ASP.NET Core 8 负责 WebSocket/REST
- 单独 C 进程(PortAudio + DynamicChain)负责音频 I/O
- 两进程间通过本地 TCP/共享内存通信参数变更
- 优点:C 进程无 GC,音频延迟最低(< 5 ms);GC 不干扰音频线程
- 缺点:进程间通信增加复杂度
方案三:前端 WASM
- C 算法库通过 Emscripten 编译为 WebAssembly
- 在浏览器 Web Audio API 中运行,AudioWorklet 调用 WASM
- 优点:跨平台(Windows/Mac/Linux/移动端)
- 缺点:性能约为原生 3–5 倍开销;Web Audio 固定 128 样本帧(2.67ms @ 48kHz);与系统声卡隔离,延迟较高
方案四:纯 C + 嵌入 HTTP/WebSocket
- PortAudio + DynamicChain + libwebsockets 全部用 C 实现
- 优点:最高效,延迟最低
- 缺点:需完全重写后端,放弃 .NET 生态,开发成本高
结论:
| 场景 | 推荐方案 |
|---|---|
| 原型验证 / 快速开发 | 方案一(P/Invoke + NAudio) |
| 生产级 / 低延迟需求 | 方案二(双进程 PortAudio) |
| 跨平台演示 / 无声卡 | 方案三(WASM) |
8.3 DLL 导出接口
算法库需暴露以下 C 接口供 P/Invoke 调用:
// DynamicChain.dll / libDynamicChain.so 导出接口
EXPORT int32_t DynChain_Create(DynamicChain **ppChain);
EXPORT int32_t DynChain_Destroy(DynamicChain *pChain);
EXPORT int32_t DynChain_LoadConfig(DynamicChain *pChain,
const char *configJson, uint32_t len);
EXPORT int32_t DynChain_SetParam(DynamicChain *pChain,
const char *instanceId,
const char *paramId,
const void *pValue, uint32_t size);
EXPORT int32_t DynChain_Process(DynamicChain *pChain,
float **ppIn, float **ppOut,
uint32_t nbSamples);
9. 调试子系统
9.1 调试功能分类
| 功能 | 前端 | 后端 | DSP/算法层 |
|---|---|---|---|
| 系统延时显示 | DebugLatencyPanel | 计算总延时 | 每模块延时注入量 |
| 算力分析 | DebugComputePanel | 汇总各模块 CPU 占用 | Process() 时间戳 |
| 内存分析 | DebugMemoryPanel | 汇总 MemPool 使用 | MemPool.used / total |
| WAV 节点抓取 | DebugWavPanel | 触发抓取,返回 WAV 文件 | Process() 后 hook |
| Log 显示 | DebugLogPanel | 转发日志流 | LOG_CALLBACK 函数 |
| 参数回读 | 复用 ParamPanel | 回读 DSP/算法实时值 | GetParam() |
| 单位转换工具 | ConversionToolPanel | — | — |
9.2 数据采集接口(后端推送)
后端通过 WebSocket 周期推送调试数据:
{
"type": "debug_metrics",
"timestamp": 1711900000000,
"latency": { "totalMs": 12.5 },
"compute": [
{ "instanceId": "gain#1", "cpuUs": 45 },
{ "instanceId": "delay#1", "cpuUs": 120 }
],
"memory": { "poolUsed": 204800, "poolTotal": 1048576 }
}
WAV 抓取结果通过 REST 接口 GET /api/debug/wav/{captureId} 以二进制流返回。
10. 自动化测试框架
10.1 测试脚本格式(YAML)
# test_gain_precision.yaml
name: "Gain 精度测试 - 20通道"
target: tcp://192.168.1.100:9000 # 目标 DSP 或 pc_sim
chain: ./data/test_chain.json # 测试链路
steps:
- action: set_param
instanceId: channel_gain#1
paramId: gainDb#0
value: -6.0
- action: set_param
instanceId: channel_gain#1
paramId: gainDb#1
value: -12.0
- action: wait_ms
ms: 100
- action: capture_wav
node: channel_gain#1
duration_ms: 1000
output: ./results/gain_ch0_ch1.wav
- action: verify_rms
file: ./results/gain_ch0_ch1.wav
channel: 0
expected_rms_db: -6.0
tolerance_db: 0.5
- action: verify_rms
file: ./results/gain_ch0_ch1.wav
channel: 1
expected_rms_db: -12.0
tolerance_db: 0.5
10.2 测试运行方式
# 命令行运行单个测试
dotnet Backend.dll --test test_gain_precision.yaml
# 批量运行(返回 JUnit XML 报告)
dotnet Backend.dll --test-suite ./tests/ --report ./results/report.xml
11. 模块实例标识
系统中每个运行时模块实例使用如下 ID 方案:
instanceId = "${moduleType}#${index}"
例:
gain#1 → 第 1 个增益模块实例
delay#1 → 第 1 个延迟模块实例
eq#2 → 第 2 个均衡器模块实例
子图展平后,子图内部模块的 instanceId 前缀化:
"${groupInstanceId}.${childInstanceId}"
例:
group#1.upmixer#1 → group#1 子图内的第 1 个 upmixer 实例
group#1.delay#1 → group#1 子图内的第 1 个 delay 实例
参数键格式(paramId 含通道后缀):
"${instanceId}.${paramId}"
例:
gain#1.enable → 增益模块 enable 标志
gain#1.gainDb#0 → 增益模块通道 0 的增益值
delay#1.delaySamples#3 → 延迟模块通道 3 的延迟量
12. 链路配置 JSON 格式(v3.0)
系统以 JSON 描述算法链路,保存于 ./data/current_link.json。v3.0 格式新增 global.dataType 字段,并将 global.frameSize 重命名为 global.blockSize,完整对应 AWE Wire 信息:
{
"version": "3.0",
"rootChainId": "root",
"global": { "channels": 20, "sampleRate": 48000, "blockSize": 240, "dataType": "fract32" },
"chains": {
"root": {
"id": "root", "name": "Main Chain",
"nodes": [
{ "instanceId": "gain#1", "moduleType": "channel_gain_v1", "order": 0, "enabled": true,
"position": {"x":100,"y":200},
"ports": [
{"id":"input","direction":"input","channels":-1,"sampleRate":-1,"label":"Input","required":true},
{"id":"output","direction":"output","channels":-1,"sampleRate":-1,"label":"Output","required":true}
]
},
{ "instanceId": "group#1", "moduleType": "subgraph", "subGraphId": "surround_proc",
"position": {"x":300,"y":200},
"ports": [
{"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"Stereo In","required":true},
{"id":"output","direction":"output","channels":8,"sampleRate":48000,"label":"7.1 Out","required":true}
]
},
{ "instanceId": "mixer#1", "moduleType": "audio_mixer_v1", "order": 2, "enabled": true,
"position": {"x":550,"y":200},
"ports": [
{"id":"input_0","direction":"input","channels":-1,"sampleRate":-1,"label":"Ch A","required":true},
{"id":"input_1","direction":"input","channels":-1,"sampleRate":-1,"label":"Ch B","required":false},
{"id":"output","direction":"output","channels":-1,"sampleRate":-1,"label":"Mix Out","required":true}
]
}
],
"edges": [
{"id":"e1","fromModule":"gain#1","fromPort":"output","toModule":"mixer#1","toPort":"input_0","channels":2,"sampleRate":48000},
{"id":"e2","fromModule":"group#1","fromPort":"output","toModule":"mixer#1","toPort":"input_1","channels":8,"sampleRate":48000}
]
},
"surround_proc": {
"id": "surround_proc", "name": "Surround Processor",
"externalPorts": [
{"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"Stereo In","required":true},
{"id":"output","direction":"output","channels":8,"sampleRate":48000,"label":"7.1 Out","required":true}
],
"nodes": [
{ "instanceId": "upmixer#1", "moduleType": "upmixer_2to8_v1", "order": 0, "enabled": true,
"position": {"x":100,"y":100},
"ports": [
{"id":"input","direction":"input","channels":2,"sampleRate":48000,"label":"Stereo","required":true},
{"id":"output","direction":"output","channels":8,"sampleRate":48000,"label":"7.1","required":true}
]
}
],
"edges": []
}
}
}
字段说明:
- version:格式版本号,当前为 "3.0"(v6.0 架构)
- global.dataType:v3.0 新增,全局默认 Wire 数据类型(float32/fract32/int16 等)
- global.blockSize:v3.0 重命名自 frameSize,对应 AWE wireInfo2[16:0]
- rootChainId:根链路 ID,对应 chains 中的一个键,DSP 从此链路开始执行
- chains:所有链路定义的映射表,key 为 chainId;根链路和所有子图链路均存放于此
- nodes[].ports:端口描述符数组,替换旧版平铺的 inputChannels / outputChannels 等字段
- nodes[].moduleType = "subgraph":标识该节点为子图节点,subGraphId 指向 chains 中的子链路
- edges[].fromPort / edges[].toPort:端口 ID,替换旧版隐式单端口假设
- edges[].channels / edges[].sampleRate:连线上的实际信号规格,由前端自动计算并写入
- externalPorts:仅子图链路需要,定义该子图对外暴露的端口,与父链路中 SubGraphNode.ports 保持镜像
13. 技术栈汇总
| 层 | 技术 | 版本 | 说明 |
|---|---|---|---|
| 前端 | Vue3 | 3.x | 响应式 UI 框架 |
| 前端 | TypeScript | 5.x | 类型安全 |
| 前端 | Pinia | 2.x | 状态管理 |
| 前端 | Vite | 5.x | 构建工具 |
| 后端 | .NET | 8.0 | 运行时 |
| 后端 | ASP.NET Core | 8.0 | Web + WebSocket |
| 后端 | System.Text.Json | 内置 | JSON 序列化 |
| 后端音频 | NAudio | 2.x | WASAPI 音频 I/O(PC 仿真方案一) |
| 后端音频 | PortAudio | 19.x | 跨平台音频 I/O(PC 仿真方案二 C 进程) |
| 后端 P/Invoke | DllImport | .NET 内置 | 调用 C 算法库 DLL |
| DSP/PC仿真 | C (C99) | — | 嵌入式 DSP 固件 + PC DLL(同源代码库) |
| DSP | CAPI v3 接口 | — | DSP 平台适配层 |
| 通信 | WebSocket | RFC 6455 | 前后端全双工 |
| 通信 | TLV 二进制帧 | 自定义 v3 | 后端↔DSP |
14. 关键设计原则
- 模块接口统一:每个算法模块必须实现 5 个标准接口(GetMemSize / Init / Process / SetParam / GetParam)
- 运行时动态加载:链路结构在运行时通过注册表动态组装,不依赖编译期枚举顺序
- 参数单位前后端一致:前端 UI 单位(dB, ms, cm)→ 后端转换 → DSP 内部单位(linear, samples)
- 无损热更新参数:单个参数更新不中断音频流(平滑过渡)
- 链路重配置需静音过渡:增删模块等结构性变更需 mute→变更→unmute 保护
- 状态可恢复:断线重连后前端可通过
get_all_params+read_link完整恢复状态 - 三种模式权限隔离:后端按当前工作模式(Chain Builder / Tuning / Test-Verify)检查每条消息的操作权限,拒绝越权操作并返回明确错误
- 算法库双形态:同一 C 代码库同时支持嵌入式静态库(DSP 固件)和 PC 动态库(DLL/SO),通过条件编译区分平台适配层,算法核心代码不做修改
- 可观测性优先:算法库内置日志回调(LOG_CALLBACK)、每模块 Process() 性能计时和 WAV 抓取 Hook,调试子系统可在不修改算法代码的前提下获取完整运行时信息
- 端口显式化:所有模块连接通过具名端口 ID 建立,不依赖隐式顺序;
ChainEdge的fromPort/toPort字段与PortDescriptor.id一一对应,确保多端口扇入/扇出语义无歧义 -
子图透明性:前端子图对 DSP 完全透明,后端
ChainFlattener负责将所有SubGraphNode递归展平为平坦模块列表和连接表;DSP 只看到平坦的模块-连接表,不感知子图层级结构 -
Wire 信息与算法参数分离(v6.0):
PortDescriptor(Wire 格式)与ModuleParams(算法参数)在前端/后端/DSP 三层均保持独立存储和独立传输,互不干扰。Wire 参数随set_link下发,算法参数随set_param单独下发。 -
Xisnd 两阶段初始化(v6.0):Xisnd 模块需先通过
set_link配置 Wire 规格(portCfg),再通过set_param(SETALLMASK)触发全量初始化(platform_init),后才能接受音频数据处理。顺序不可颠倒。 -
在线调音通道(TuningBuffer)(v6.0):Xisnd 的参数调整分两类:① Build 参数(vehicleCfg 等)通过
set_param直接写入 AWE 结构体;② 调音参数通过 TuningBuffer 专用通道(MASK=0x8000)传递,以 COMMAND_HEADER_WRITE/READ 区分读写方向。 -
内存池扩展策略(v6.0):含 Xisnd 的链路内存需求约 12MB,嵌入式平台需使用外部 DRAM 池,或将 AlgoMem/TuningBuffer 单独申请于 DRAM 段而非统一 MemPool。当前
DYNAMIC_CHAIN_POOL_SIZE需从 1MB 扩展至 ≥14MB。
15. Wire 信息三层对应关系(v6.0)
参考:
debug/awe_module_structure_analysis.md中 WireInstance 位域定义
前端 PortDescriptor 后端 ResolvedPortSpec DSP WireInstance / PortSpec
─────────────────── ───────────────────── ───────────────────────────
channels: 20 ──► Channels: 20 ──► wireInfo1[9:0] = 20
maxBlockSize: 240 ──► (传入portSpec) ──► wireInfo1[26:10] = 240
isComplex: false ──► IsComplex: 0 ──► wireInfo1[27] = 0
sampleSizeBytes: 4 ──► SampleSizeB: 4 ──► wireInfo1[31:28] = 4
blockSize: 240 ──► BlockSize: 240 ──► wireInfo2[16:0] = 240
dataType: 'fract32' ──► DataType: 3(enum) ──► wireInfo2[22:17] = 3
rows: 0 ──► Rows: 0 ──► wireInfo3[9:0] = 0
cols: 0 ──► Cols: 0 ──► wireInfo3[19:10] = 0
isIPC: false ──► IsIPC: 0 ──► wireInfo3[20] = 0
isPrivate: false ──► IsPrivate: 0 ──► wireInfo3[21] = 0
isClockMaster: false ──► IsClockMaster: 0 ──► wireInfo3[22] = 0
sampleRate: 48000 ──► SampleRate: 48000 ──► WireInstance.sampleRate (float)
16. Xisnd 模块参数三层对应关系(v6.0)
参考:
debug/awe_module_structure_analysis.md中awe_modXisndDemomInstance字段
前端 XisndModuleParams 后端 ParamSetEntry[] DSP awe_modXisndDemomInstance
────────────────────── ─────────────────── ─────────────────────────────
enable: 1 ──► {typeId:0x0103, ch:0, 1.0} ──► S->enable = 1
──► packedFlags = MODULE_ACTIVE
vehicleCfg: 0 ──► {typeId:0x0200, ch:0, 0.0} ──► S->VehicleCfg = 0
──► awe_modXisndDemomSet(0x200)
──► → _pif->vehicleCfg = 0
algoChanNum: 20 ──► {typeId:0x0201, ch:0, 20.0} ──► S->AlgoChanNum = 20
──► awe_modXisndDemomSet(0x400)
numOutChan: 20 ──► {typeId:0x0202, ch:0, 20.0} ──► S->NumOutChan = 20
memSizeAlgo: 3000116 ──► {typeId:0x0203, ch:0, ...} ──► S->MemSize_Algo = 3000116
memSizeArch: 0 ──► {typeId:0x0204, ch:0, ...} ──► S->MemSize_Arch = 0
memSizeTune: 80000 ──► {typeId:0x0205, ch:0, ...} ──► S->MemSize_Tune = 80000
[初始化触发]
initFull: true ──► CMD=0x01 mask=0xFFFFFFFF ──► awe_modXisndDemomSet(SETALLMASK)
──► → AlgoMem 内部布局初始化
──► → platform_init(_pif) 调用
[在线调音]
tuningParams[0]: ──► TuningBuffer 内容构建: ──► S->TuningBuffer[0] = 0x44332211
paramId: 0x16670004 TuningBuffer[0]=0x44332211 (WRITE命令头)
data: [...] TuningBuffer[1]=0x16670004 ──► awe_modXisndDemomSet(0x8000)
TuningBuffer[2]=dataLen ──► → _pif->set_param(0x16670004)
TuningBuffer[3..]=data[]
Tunable 参数分类(v6.0):
| 模块 | Tunable(调音可调) | System(锁定) |
|---|---|---|
| Gain | gainDb[], mute[], phase[] | enable, smoothTimeMs |
| Delay | delaySamples[] | enable |
| Xisnd | tuningParams[](在线调音,通过TuningBuffer) | enable, vehicleCfg, algoChanNum, numOutChan, memSize* |
| Source | — | 全部(Wire规格属于Build配置) |
17. 内存需求估算(v6.0)
| 组件 | 内存需求 | 说明 |
|---|---|---|
| GainModuleData (20ch) | ~440 B | params + channelState[] |
| GainModuleData outputBuf | 20×240×4 = 19.2 KB | 每模块独立输出缓冲 |
| DelayModuleData (20ch) | ~77 KB | 20通道循环缓冲区 |
| awe_modXisndDemomInstance (结构体) | ~72 B | 9个字段 + 基类32B |
| Xisnd TuningBuffer | 80,000×4 = 312 KB | 在线调音缓冲区 |
| Xisnd AlgoMem | 3,000,116×4 ≈ 11.4 MB | 算法全量内存 |
| Xisnd outputBuf | 20×240×4 = 19.2 KB | 每模块独立输出缓冲 |
| 单链路总计(gain+delay+xisnd) | ≈ 12 MB | 主要来自 Xisnd AlgoMem |
⚠️ 嵌入式注意:当前
DYNAMIC_CHAIN_POOL_SIZE = 1MB,需扩展至 ≥14MB 以容纳 Xisnd 模块。 PC 仿真环境无限制,可使用系统 malloc 代替 MemPool。
新节 A:子图链路回读设计
问题:DSP 硬件回读时返回平坦模块列表,子图层次结构丢失。
解决方案:后端持有层次结构的唯一权威来源。
操作流程对比:
get_dsp_chain(前端读链路):
Frontend → Backend → 读取 current_link.json → 返回完整 LinkConfig(含子图)
✅ 子图层次结构完整保留
read_dsp_chain(同步校验):
Frontend → Backend → CMD=0x06 → DSP → 回传平坦模块列表
Backend ChainSyncService 对比存储配置 vs DSP 回读
→ 不一致时广播 chain_sync_warning
原则:链路层次结构(LinkConfig with sub-graphs)永远由 Backend current_link.json 持有,DSP 回读仅用于运行态校验,不用于重建层次结构。
新节 B:混合 ID 策略
系统采用 字符串(JSON 层)+ 数字(Binary 层)混合 方案:
| 层 | ID 类型 | 原因 |
|---|---|---|
| Frontend ↔ Backend WebSocket JSON | 字符串 | 可读性、schema 一致、工具友好 |
| Backend ↔ DSP Binary Frame | 数字 | 紧凑、DSP O(1) 查找、带宽节省 75% |
| DSP 调试 Log | 字符串(可选宏) | 调试时可读 |
set_param 帧格式对比
| 版本 | 格式 | 大小 |
|---|---|---|
| 旧版(字符串) | [CMD][LEN]["gain#1.gainDb#2\0"][f32] |
~30 字节 |
| 新版 v3(数字) | [CMD=0x01][LEN=9][moduleIdx:u8][paramTypeId:u16][channelIdx:u16][type:u8][f32] |
14 字节 |
paramTypeId 编号规范(u16)
高字节 = 模块类别:
0x01 = gain 0x02 = filter/EQ 0x03 = delay
0x04 = dynamics 0x05 = mixer 0x06 = routing
低字节 = 该类别内参数编号(从 0x01 起)
示例:
0x0101 = Gain.gainDb 0x0102 = Gain.mute
0x0201 = EQ.bandFreq 0x0202 = EQ.bandGain 0x0203 = EQ.bandQ
0x0301 = Delay.delaySamples
新节 C:多维参数寻址
EQ 等模块有多通道多波段参数(10ch × 10band × 5类型 = 500组合)。
方案:ParamSchema.dimensions?: ParamDimension[] 替代 channels: number
interface ParamDimension {
name: string // "channel", "band"
count: number
labels?: string[]
}
// EQ bandFreq schema:
{ paramId:"bandFreq", paramTypeId:0x0201,
dimensions:[{name:"channel",count:10},{name:"band",count:10}],
min:20, max:20000, unit:"Hz", role:"tunable" }
channelIdx 编码规则:
- 标量参数(全局):channelIdx = 0
- 1D(通道):channelIdx = ch_index
- 2D(通道×波段):channelIdx = (ch_index << 8) | band_index
| 模块 | 旧方案条目数 | 新方案条目数 |
|---|---|---|
| Gain(20通道) | 20 条 | 1 条 + dimensions:[{count:20}] |
| EQ(10ch×10band×5类型) | 500 条 | 5 条 + 2D dimensions |
新节 D:音效模式管理架构(三级结构)
ModulePreset(单模块参数快照)
└── presetId, name, instanceId, params: { "bandFreq[2][5]": "120.0" }
↓ N个 ModulePreset 组合成
AmbianceProfile(一种音效模式,如"HIFI"、"动感")
└── profileId, name, modulePresets: { instanceId → presetId }
↓ M个 AmbianceProfile 打包成
ParamBin(二进制导出包,CMD=0x04 下发 DSP)
└── 全部 ambiance × module × preset 的参数数据,DSP 一次性加载
ParamBin 生成与使用流程
前端 → generate_param_bin → 后端 ParamBinGenerator
→ 二进制 ParamBin → CMD=0x04 → DSP DynChain_LoadParamBin()
音效切换:
前端 → set_ambiance(idx) → CMD=0x05 → DSP DynChain_SetAmbiance(idx)
DSP 内部:O(num_params) 遍历,无字符串操作,直接写入模块参数
ParamBin 二进制格式
[magic:"PCFG":4][version:u8][numAmbiances:u8][numModules:u8][reserved:u8]
Module Table (numModules × 24 bytes): instanceId[16] + typeNumId:u32 + numPresets:u8 + pad:u8[3]
Ambiance Table (numAmbiances × (16+numModules) bytes): name[16] + presetIndices:u8[numModules]
Param Data: per module per preset: [paramCount:u16][paramTypeId:u16 + channelIdx:u16 + value:f32]×N
前端 UI 功能
| 界面 | 位置 | 功能 |
|---|---|---|
| Preset 面板 | 每个模块面板右侧 | 当前模块的参数快照 CRUD |
| Profile Manager | 右侧可折叠侧边栏 | 全局音效模式管理,快速切换 |
| Param Gen 视图 | TopBar 入口 | Ambiance × Module 矩阵,生成 ParamBin |
新增节 E:AWE 框架验证阶段(v6.0 更新)
验证策略
当前阶段 DSP 算法验证依赖 AudioWeaver (AWE) 框架。原则:在 audiochain 层做 AWE 适配,不修改 DynChain 核心逻辑,验证通过后切换自研实现。
┌────────────────────────────────────────────────────────┐
│ DynChain 框架(不变) │
│ ModuleRegistry / DynamicChain / ModuleInstance │
│ DynChain_ParseLinkFrame / Process / SetParamNum │
├────────────────────────────────────────────────────────┤
│ AWE 适配层(v6.0:xisnddemom_module.c) │
│ │
│ XisndDemom_GetMemSize(portCfg) │
│ ← sizeof(awe_modXisndDemomInstance) │
│ ← + 80000×4 (TuningBuffer) │
│ ← + portCfg->memSizeAlgo×4 (AlgoMem) │
│ │
│ XisndDemom_Init(pState, portCfg) │
│ ← portCfg 中提取 channels/sampleRate │
│ ← 分配 TuningBuffer (80K words) │
│ ← 分配 AlgoMem (3M words) │
│ ← S->AlgoChanNum = portCfg->ports[0].channels │
│ │
│ XisndDemom_Process(pState, inputs, ppOut) │
│ ← inputs[0].ppData = 上游模块.ppOut │
│ ← 转换为 sport_buf_t + sport_stream_data_t │
│ ← 调用 awe_modXisndDemomProcess(pInstance) │
│ │
│ XisndDemom_SetParamNum(pState, typeId, chIdx, val) │
│ ← typeId=0x0200 → MASK=0x200 → VehicleCfg │
│ ← typeId=0x0201 → MASK=0x400 → AlgoChanNum │
│ ← typeId=0x0202 → MASK=0x800 → NumOutChan │
│ ← typeId=0x0203 → MASK=0x1000 → MemSize_Algo │
│ ← typeId=0x0204 → MASK=0x2000 → MemSize_Arch │
│ ← typeId=0x0205 → MASK=0x4000 → MemSize_Tune │
│ ← typeId=0xFFFF → SETALLMASK → 全量初始化 │
│ ← typeId=MASK_TuningBuffer → 在线调音读写 │
│ │
│ awe_gainmodule.c / awe_delaymodule.c(其他模块) │
├────────────────────────────────────────────────────────┤
│ AWE SDK (AWECore lib + platform_api.h) │
│ platform_init / adsp_audio_vtbl_t.process │
│ adsp_audio_vtbl_t.set_param/get_param │
└────────────────────────────────────────────────────────┘
阶段切换计划
| 阶段 | audiochain 实现 | 关键标志 |
|---|---|---|
| Phase 1(当前) | AWE SDK 调用(xisnddemom_module.c + AWECore) |
.awd 文件驱动,快速验证算法链路 |
| Phase 2(待切换) | 自研 C 实现 | 替换 awe_*module.c → gain_module.c 等 |
AWE 模块接口约定(v6.0 更新)
// 每个 AWE 适配模块必须实现 ModuleFuncTable 中的接口:
int32_t XisndDemom_GetMemSize(const ModulePortConfig *portCfg);
int32_t XisndDemom_Init(void *pState, const ModulePortConfig *portCfg);
int32_t XisndDemom_SetParamNum(void *pState, uint16_t paramTypeId,
uint16_t channelIdx, const void *pVal, uint32_t size);
// paramTypeId=0x0200 → MASK_XisndDemom_VehicleCfg (awe mask 0x200)
// paramTypeId=0x0201 → MASK_XisndDemom_AlgoChanNum (awe mask 0x400)
// paramTypeId=0xFFFF → SETALLMASK (触发 platform_init)
// paramTypeId=0x8000 → MASK_XisndDemom_TuningBuffer (在线调音)
int32_t XisndDemom_GetParamNum(void *pState, uint16_t paramTypeId,
uint16_t channelIdx, void *pBuf, uint32_t size);
void XisndDemom_Process(void *pState, const ModuleInputList *inputs,
float **ppOut, uint32_t numOut, uint32_t blockSize);
PC 仿真音频引擎(AudioEngine)
Frontend (Vue3)
AudioEnginePanel.vue
├── 声卡设备选择 (list_audio_devices → audio_device_list)
├── 引擎启停控制 (start/stop_audio_engine)
├── 挂载模块列表 (来自 DynChain 当前链路)
└── 实时资源监控 (audio_engine_status 推送,2s 间隔)
Backend (.NET8)
AudioEngineService.cs
├── NAudio WASAPI 设备枚举
├── 音频引擎启停
└── CPU/内存采样推送
PC 仿真 DLL
dynchain_sim.dll
└── 链路加载 → AWE audiochain 层(gain/delay/xisnddemom) → AWECore
验证用例:20ch Gain → Xisnd(主验证链路)
[Source 20ch] → [Gain 20ch] → [Xisnd 20ch]
↕ set_param ↕ set_param
gainDb[0..19] vehicleCfg / algoChanNum
+ TuningBuffer 在线调音
验证步骤:
1. 前端 LinkEditor 拖拽构建上述链路并 write_link(version="3.0")
2. 后端 ChainFlattener 展平 → PropagateWireInfo() 解析所有 -1 端口 → TypeNameToNumId() → BinaryFrameBuilder.BuildSetLink() → CMD=0x02
3. DSP/DLL DynamicChain_ParseLinkFrame() → 注册 gain + xisnd 模块 → 分配 MemPool(≥14MB)
4. 后端发送参数:set_param(xisnd#1, vehicleCfg=0) + set_param(xisnd#1, algoChanNum=20) + set_param(xisnd#1, SETALLMASK) → platform_init() 成功
5. 前端打开 XisndTuningDialog → 通过 TuningBuffer 通道发送调音参数(paramId=0x16670004)
6. AWE 音频引擎实时处理并通过所选声卡播放