杰理 SDK 文档中心
首页
首页
  • 入门指南

    • SDK 概述与芯片平台
    • 环境搭建与开发工具链
    • 编译、烧录与快速开始
  • 应用层开发

    • 语音玩具应用 voice_toy
    • 扩音器应用 voice_enhanced
    • 语音功能状态机 voice_func
    • 应用公共框架与配置
  • 音频子系统

    • 音频解码器与 MIDI 播放
    • 音频编码与录音
    • 音效算法(ANS、变调、变声、混响)
    • 音频输出、功放与硬件重采样
  • 存储与文件系统

    • 文件系统层(FAT、NOR_FS、SYDF 等)
    • 存储设备与设备管理
    • 参数存储 VM 与保留区
  • 系统机制

    • 消息与事件机制
    • 电源管理与低功耗
    • 固件升级机制
    • 外设驱动(按键、红外、SPI、USB)
    • 实时时钟与定时器
  • 构建系统与工具

    • 构建系统(Makefile 与 Code::Blocks)
    • 编译后处理与语音资源打包
  • 硬件平台与文档

    • 芯片平台与启动流程
    • 硬件文档、规格书与原理图

音效算法(ANS、变调、变声、混响)

AD24N SDK 中 sound_effect_list 音效算法集合的核心 DSP 效果:ANS 自适应降噪、变调(含移频啸叫抑制)、变声与混响/回声,以及它们如何通过 sound 链机制接入录音与播放数据通路。

Purpose and Scope

本文档说明 AD24N SDK 中音效算法的实现方式、接入链路与配置方法,覆盖以下四类效果:

  • ANS(Adaptive Noise Suppression):基于 NoiseSuppressLib 库的自适应降噪,挂在录音/编码链路上。
  • 变调(Pitch Shift):vo_pitch 变调器与 pitch_howling 移频变调(抑制啸叫),挂在扬声器/对讲链路上。
  • 变声(Voice Changer):voicechanger.c 实现的音高/共振峰/语速/颤音综合变声效果。
  • 混响/回声(Echo / Reverb):echo_api.c 回声效果。

同属 sound_effect_list 但不在本文范围内的音效(EQ、啸叫陷波、变速、能量检测)由各自的 Wiki 页面覆盖:pcm_eq(EQ)、notch_howling(陷波啸叫抑制)、speed(变速)、energe_detect(能量检测)。本文只引用它们所在的目录结构,不做展开。

Overview

AD24N 的音频处理采用 sound 链(sound chain) 设计:每一级音效都是一个 sound_out_obj / EFFECT_OBJ 节点,通过 link_*_sound() 函数把前级与后级串接起来,数据以短整型 PCM 块为单位流经各节点。效果算法普遍以 静态缓冲区 + 固定块长(如 128 点) 的方式运行,缓冲区通过 AT(.xxx_data) 指定到专用内存段(如 .ans_data、.voicechanger_data),避免运行期动态分配,从而适配 MCU 上无 MMU、内存受限的实时 DSP 场景。

四条主要接入路径:

  1. 录音/编码路径:encoder_api.c 在编码器启动时把 ANS 链接进 ADC 数据流,用于消除环境底噪。
  2. 扬声器路径:speak_api.c 按编译宏依次链接移频变调(啸叫抑制)、变声等效果,最后送入 DAC。
  3. 效果参数:部分效果(如变声)支持运行时通过 update_*_parm() 重开算法实例来热更新参数。
  4. 编译开关:每个效果都有独立宏(ANS_EN、VO_PITCH_EN、PITCHSHIFT_HOWLING_EN、HOWLING_EN、VO_CHANGER_EN 等),未使能时相关代码与数据段全部不参与编译。

Architecture

flowchart TD
    subgraph sg_RecPath["录音/编码链路"]
        ADC["ADC 采集"] --> ENC["encoder_api.c 编码器"]
        ENC -->|"link_ans_sound()"| ANS["ANS<br/>ans/ans_api.c"]
        ANS --> OUT["编码输出"]
    end

    subgraph sg_SpkPath["扬声器/对讲链路"]
        SRC["音频源(解码/ADC)"] --> PITCH["移频变调(啸叫抑制)<br/>pitch_howling/"]
        PITCH --> VC["变声<br/>voice_change/voicechanger.c"]
        VC --> ECHO["混响/回声<br/>echo/echo_api.c"]
        ECHO --> DAC["DAC 输出"]
    end

    subgraph sg_EffectList["sound_effect_list 音效库"]
        ANS
        PITCH
        VC
        ECHO
        EQ["pcm_eq(EQ)"]
        NH["notch_howling"]
        SPD["speed(变速)"]
        ED["energe_detect"]
    end

    ADC -->|"ANS_EN 使能"| ANS
    SRC -->|"HOWLING_EN 使能"| PITCH

架构说明:

  • sound_effect_list 是统一的音效存放目录,每个子目录对应一类效果;每个效果模块通常暴露一个 link_xxx_sound() 链接函数 + 一个 xxx_api() 初始化函数 + 一个 xxx_run() 处理函数,三者配合完成"接入链 → 初始化 → 逐块处理"。
  • ANS 只挂在录音/编码链路(encoder_api.c),因为它的输入是 ADC 采样率(8 kHz / 16 kHz),目标是降低录音信号底噪。
  • 变调/变声/混响挂在扬声器链路(speak_api.c),其中 pitch_howling 用移频方式把啸叫频率搬移出反馈回路,voicechanger 改变人声特性,echo 叠加回声营造空间感。
  • 数据以 PCM 块为单位逐级流动,每级处理完通过 remain_output 机制缓存未消费完的数据,保证下一级能以固定块长取数。

音效链机制:link 函数、EFFECT_OBJ 与 remain_output

所有音效共用的运行时骨架:

  • EFFECT_OBJ:效果对象,含 run 函数指针与 sound(sound_out_obj)节点。初始化时把 sound.p_obuf 指向前级输出缓冲,并把 *ppsound 指向本节点,从而把自身插入链中。
  • link_*_sound():调用 xxx_api() 创建效果;成功则置 enable |= B_DEC_EFFECT 并返回后级节点,失败则保持原链不变并打印 init fail 日志。
  • remain_output / set_remain_len:块式效果的"剩余数据"管理。当本帧数据不足以填满固定块长(如 ANS 的 128 点)时,先输出上一帧的剩余数据;若上一帧仍未输出完则直接返回 0(背压),避免把不完整块交给后级。

这一骨架保证:每一级效果都按自己的固定块长消费输入、按自己的节奏吐数据,链路任意一级繁忙时自动背压,不会丢数据或错位。

ANS 自适应降噪实现详解

ANS 模块位于 sdk/app/bsp/common/sound_effect_list/ans/ans_api.c,核心算法封装在 NoiseSuppressLib(sdk/include_lib/ans/NoiseSuppressLib.h),本文件负责参数、缓冲区与数据流的桥接。

固定块长与内存布局

#define READSIZE                128     //每次run处理样点数
#define ANS_RUN_BUFFSIZE        5400
#define ANS_TMP_BUFSIZE         3604
#define ANS_NEAR_SIZE           (READSIZE * 2)

static int ans_runbuf[ANS_RUN_BUFFSIZE / 4] AT(.ans_data);
static int ans_tmpbuf[ANS_TMP_BUFSIZE / 4] AT(.ans_data);

static short ans_output_buff[READSIZE] AT(.ans_data);

static remain_ops ans_remain_ops AT(.ans_data);
static EFFECT_OBJ ans_effect_obj AT(.ans_data);

Source: ans_api.c

设计意图:READSIZE = 128 是 ANS 算法库单次处理的最小样点块;所有中间缓冲(运行缓冲、临时缓冲、输出缓冲、剩余数据管理结构、效果对象)都声明为 static 并放入 .ans_data 段,启动时由链接脚本统一规划,运行时零动态分配。

逐块处理:ans_run()

int ans_run(void *hld, short *inbuf, int len)
{
    u32 rlen = 0;
    remain_ops *p_ans_remain_ops = &ans_remain_ops;

    /* 0. output 上次剩余的数据 */
    remain_output(&ans_effect_obj.sound, p_ans_remain_ops);
    if (p_ans_remain_ops->remain_len) {        //上次数据输出仍旧没输出完
        return 0;
    }

    /* 1. 新一轮输入输出 */
    memset(ans_output_buff, 0, sizeof(ans_output_buff));    //清空outdata

    /* 2. input 数据 */
    if (len < READSIZE * sizeof(short)) {
        /* 本次输入数据不够128个点 */
        return 0;
    }

    /* 3. 运算run */
    NoiseSuppress_Process(ans_runbuf, ans_tmpbuf, inbuf, ans_output_buff, NULL, NULL, READSIZE);

    /* 4. 设置需要 output 数据量 */
    set_remain_len(p_ans_remain_ops, READSIZE * sizeof(short));      //设置output需要输出一包的数据量

    /* 5. 输出 */
    remain_output(&ans_effect_obj.sound, p_ans_remain_ops);

    return READSIZE * sizeof(short);    //成功返回读取byte长度
}

Source: ans_api.c

处理顺序(对应注释中的 0~5 步):

  1. 先尝试把上一帧未送出的数据交给后级;若 remain_len 非零说明后级仍在消化,直接返回 0 形成背压。
  2. 清空输出缓冲,保证算法库不产生任何旁路残留。
  3. 输入不足 256 字节(128 个 short)时直接返回 0,等待攒够一帧。
  4. 调用 NoiseSuppress_Process() 完成 128 点降噪,输入输出均为 short。
  5. 把 256 字节登记为待输出长度并立即输出,随后返回本次消耗的字节数(READSIZE * sizeof(short))。

返回值语义:返回本次从输入中消费的字节数,上游据此推进数据指针;返回 0 表示本次没有消费。

初始化与参数:ans_api()

void *ans_api(void *obuf, void **ppsound, u32 sr)
{
    const u32 ans_supprt_sr[2] = {8000, 16000};
    if (sr != ans_supprt_sr[NS_IS_WIDEBAND]) {
        log_error("ans not support curr sr %d\n", sr);
        return NULL;
    }
    int tolbufsize = NoiseSuppress_QueryBufSize(NS_MODE, NS_IS_WIDEBAND);;
    ASSERT(ANS_RUN_BUFFSIZE >= tolbufsize);

    int maxtmpbufsize = NoiseSuppress_QueryTempBufSize(NS_MODE, NS_IS_WIDEBAND);
    ASSERT(ANS_TMP_BUFSIZE >= maxtmpbufsize);

    int ANS_AggressFactor = (int)(125 * 65536 / 100);/*范围:1~2,动态调整,越大越强(1.25f)*/
    int ANS_MinSuppress = (int)(10 * 65536 / 100);   /*范围:0~1,静态定死最小调整,越小越强(0.1f)*/
    int ANS_NoiseLevel = (int)(1429 * 1024);      /*范围:-100dB ~ -40dB (-75dB) (1429 = (10^(-75/20))*2^23)*/

    NoiseSuppress_Init(ans_runbuf, ANS_AggressFactor, ANS_MinSuppress, NS_MODE, NS_IS_WIDEBAND, ANS_NoiseLevel);

    return ans_phy(obuf, ppsound);
}

Source: ans_api.c

关键点:

  • 采样率强约束:NS_IS_WIDEBAND = 1 表示宽带回声消除模式,仅支持 16 kHz;非 16 kHz 直接返回 NULL 并打错误日志(NS_MODE = 0 选择窄带 8 kHz 模式需改此宏)。初始化失败时 link_ans_sound 会打印 ans init fail 并把链路保持原样。
  • 参数以 Q 格式定点传递:ANS_AggressFactor = 1.25(1.25×65536)、ANS_MinSuppress = 0.1、ANS_NoiseLevel = -75 dB 转成 2^23 定点(1429×1024)。这些是算法库的内部定点约定,调整时需保持同一 Q 格式。
  • 缓冲区大小运行时校验:NoiseSuppress_QueryBufSize / QueryTempBufSize 返回库所需字节数,用 ASSERT 保证静态数组足够大;若换库版本导致需求增大,会立即暴露问题而非运行期越界。

录音路径接入:encoder_api.c

#if (defined(ANS_EN) && (ANS_EN))
    cbuf_init(&cbuf_ans, &ans_buff[0], sizeof(ans_buff));   //cbuf_ans 为link
    p_curr_sound = link_ans_sound(p_curr_sound, &cbuf_ans, read_audio_adc_sr());
#endif

Source: encoder_api.c

编码器启动时若 ANS_EN 使能,先初始化 512 字节的环形缓冲 cbuf_ans(同样放 .ans_data),再把 ANS 效果链接进编码器输入链。read_audio_adc_sr() 提供实际 ADC 采样率,用于 ans_api 的 16 kHz 校验。链接函数本身:

void *link_ans_sound(void *p_sound_out, void *p_ans_obuf, u32 sr)
{
    sound_out_obj *p_next_sound = 0;
    sound_out_obj *p_curr_sound = p_sound_out;

    p_curr_sound->effect = ans_api(p_ans_obuf, (void **)&p_next_sound, sr);
    if (NULL != p_curr_sound->effect) {
        p_curr_sound->enable |= B_DEC_EFFECT;
        p_curr_sound = p_next_sound;
        log_info("ans init succ\n");
    } else {
        log_info("ans init fail\n");
    }
    return p_curr_sound;
}

Source: ans_api.c

成功时置 B_DEC_EFFECT 标志(表示该节点带解码后处理效果),返回后级节点让链继续;失败时不破坏原链。文件末尾还导出了算法库需要的 STFT 窗函数表 STFT_Win_FixHalf_M256_D128[](256 点窗 / 128 点跳),由链接脚本放入常量区。

变调(Pitch Shift)

变调相关实现位于两个模块:

  • vo_pitch/vo_pitch_api.c(宏 VO_PITCH_EN):人声/音乐变调效果,通过改变回放采样相位实现音调搬移。
  • pitch_howling/howling_pitchshifter_api.c + pitch_howling_phy.c(宏 PITCHSHIFT_HOWLING_EN / HOWLING_EN):移频抑制啸叫——把扬声器信号整体搬移一个微小频率偏移,破坏拾音反馈回路中的正反馈相位条件,从而抑制啸叫,同时人耳几乎无感。

扬声器路径中的接入点(speak_api.c):

#if defined(HOWLING_EN) && (HOWLING_EN) //移频抑制啸叫
    p_curr_sound = link_pitchshift_howling_sound(p_curr_sound, &cbuf_ads_o, 0, adc_sr);
#endif

Source: speak_api.c

与 ANS 的 link_ans_sound 相同,link_pitchshift_howling_sound 接受"当前链节点 + 输出缓冲 + 采样率",成功后返回后级节点。PITCHSHIFT_HOWLING_EN 与 HOWLING_EN 分别控制编译与运行使能,注释 移频抑制啸叫 明确其用途。

变声(Voice Changer)实现详解

变声模块位于 sdk/app/bsp/common/sound_effect_list/voice_change/voicechanger.c,基于 voiceChanger_av_api.h 提供的 get_voiceChangerA_func_api() 算法接口,可同时控制音高(shiftv)、共振峰(formant_shift)、语速(speedv)、预设音效(effect_v),并叠加**颤音合成(VOICESYN)**参数。

默认参数与初始化

void *voice_changer_api(void *obuf, u32 sr, void **ppsound)
{
    vc_parm.shiftv = 65;
    vc_parm.formant_shift = 100;
    vc_parm.speedv = 80;
    vc_parm.effect_v = EFFECT_VC_AV_BIRD5;

    vs_parm.randpercent = 100;
    vs_parm.vibrate_lenCtrol = 30;
    vs_parm.vibrate_rate_u = 0;
    vs_parm.vibrate_rate_d = 100;

    return voice_changer_phy(obuf, sr, &vc_parm, &vs_parm, ppsound);
}

Source: voicechanger.c

默认配置:音高 shiftv=65、共振峰 formant_shift=100(100% 不变形)、语速 speedv=80、预置音效 EFFECT_VC_AV_BIRD5(鸟鸣 5 号);颤音合成默认随机百分比 100%、颤音时长控制 30、上/下颤音速率 0/100。vc_parm 与 vs_parm 存放在 .voicechanger_data 段,2560 字(0x2010 字节)的工作缓冲 buflen 也固定在该段。

算法实例的打开与运行

void *voice_changer_phy(void *obuf, u32 sr, VOICECHANGER_AV_PARM *pvc_parm, VOICESYN_AV_PARM *pvs_parm, void **ppsound)
{
    u32 need_buff_len;
    VOICECHANGER_A_FUNC_API *ops;
    ops = get_voiceChangerA_func_api();
    need_buff_len = ops->need_buf(sr, pvc_parm);
    if (need_buff_len > sizeof(buflen)) {
        log_error("buff_len not enough, need 0x%x\n", need_buff_len);
        return 0;
    }
    ops->open(&buflen[0], sr, pvc_parm, pvs_parm, (void *)&vc_pitch_io);
    vc_sr = sr;
    ...
    vchange_obj.p_si = &vchange_si;
    vchange_obj.run = voice_changer_run;
    vchange_obj.sound.p_obuf = obuf;
    *ppsound = &vchange_obj.sound;
    return &vchange_obj;
}

Source: voicechanger.c

要点:

  • 缓冲区需求前置校验:ops->need_buf(sr, parm) 返回算法需要的字节数,超过静态 buflen 则报错退出,不初始化。
  • IO 上下文绑定:vc_pitch_io 把算法输出绑定到 vchange_obj.sound 与 sound_output,算法库通过该回调把处理结果推入 sound 链。
  • 运行时对象 vchange_obj、vchange_si 均为静态/全局,run 函数 voice_changer_run 通过 sound_in_obj 中转调用 ops->run(p_dbuf, inbuf, len)。

运行时热更新参数

void update_voice_changer_parm(VOICECHANGER_AV_PARM *new_vc_parm, VOICESYN_AV_PARM *new_vsyn_ctrol)
{
    if ((NULL == new_vc_parm) || (NULL == new_vsyn_ctrol)) {
        return;
    }
    VOICECHANGER_A_FUNC_API *ops;
    ops = get_voiceChangerA_func_api();
    OS_ENTER_CRITICAL();
    ops->open(&buflen[0], vc_sr, new_vc_parm, new_vsyn_ctrol, NULL);
    OS_EXIT_CRITICAL();
}

Source: voicechanger.c

运行时修改参数的方式是在临界区内用新参数重新调用 ops->open()(重开算法实例)。OS_ENTER_CRITICAL/OS_EXIT_CRITICAL 保证重开过程不会被音频中断打断,避免算法内部状态不一致;NULL 参数直接忽略。这是 MCU 场景下"无锁热更新 DSP 参数"的典型做法——不用队列、不用双缓冲,靠关中断换取原子性。

扬声器链路接入

void *link_voice_changer_sound(void *p_sound_out, void *p_dac_cbuf, void **pp_effect, u32 in_sr)
{
    sound_out_obj *p_next_sound = 0;
    sound_out_obj *p_curr_sound = p_sound_out;
    p_curr_sound->effect = voice_changer_api(p_curr_sound->p_obuf, in_sr, (void **)&p_next_sound);
    if (NULL != p_curr_sound->effect) {
        if (NULL != pp_effect) {
            *pp_effect = p_curr_sound->effect;
        }
        p_curr_sound->enable |= B_DEC_EFFECT;
        p_curr_sound = p_next_sound;
        p_curr_sound->p_obuf = p_dac_cbuf;
        log_info("voice change init succ\n");
    } else {
        log_info("voice change init fail\n");
    }
    return p_curr_sound;
}

Source: voicechanger.c

与 link_ans_sound 相比多了两点:可把效果对象指针回传给调用方(pp_effect,供上层后续调 update_voice_changer_parm 使用),并把后级节点的输出缓冲改绑到 DAC 环形缓冲(p_dac_cbuf),实现"变声后直送扬声器"。文件顶部还定义了 VC_NG_THRES = 712(底噪较大的方案建议值),用于噪声门控阈值参考。

混响 / 回声(Reverb / Echo)

混响效果位于 sdk/app/bsp/common/sound_effect_list/echo/echo_api.c,对外头文件为 sdk/include_lib/audio/echo_api.h 与 sdk/include_lib/audio/reverb_api.h。其与 ANS、变声共用同一套音效链骨架(EFFECT_OBJ + link_*_sound() + remain_output),叠加在扬声器/对讲链路上,用于为语音增加回声/空间感。实现细节(延迟线长度、反馈系数、混合比例等参数)位于算法库二进制中,源码层面未提供参数表,配置需依赖厂商算法库文档。

Core Flow:数据流与关键时序

ANS 块处理时序

sequenceDiagram
    participant E as encoder_api.c
    participant A as ans_run()
    participant R as remain_output
    participant NS as NoiseSuppressLib

    loop 每帧音频
        E->>A: 输入 PCM 块(len)
        A->>R: 先输出上一帧剩余数据
        R-->>A: remain_len 检查
        alt remain_len != 0
            A-->>E: 返回 0(背压, 待后级消化)
        else len < 256 字节
            A-->>E: 返回 0(数据不足一帧)
        else
            A->>A: 清空 ans_output_buff
            A->>NS: NoiseSuppress_Process(128 点)
            NS-->>A: 降噪后 128 点
            A->>R: set_remain_len(256)
            R->>E: 输出 256 字节
            A-->>E: 返回 256(已消费字节数)
        end
    end

变声参数热更新流程

flowchart TD
    Start([上层应用/协议栈]) --> P["update_voice_changer_parm()"]
    P --> CHK{"new_vc_parm 或<br/>new_vsyn_ctrol 为 NULL?"}
    CHK -->|"是"| RET["直接返回(忽略)"]
    CHK -->|"否"| OPS["get_voiceChangerA_func_api()"]
    OPS --> CS["OS_ENTER_CRITICAL()<br/>关中断"]
    CS --> OPEN["ops->open(重开算法实例, 新参数)"]
    OPEN --> CE["OS_EXIT_CRITICAL()<br/>开中断"]
    CE --> Done([完成, 新参数即刻生效])

扬声器链路整体顺序

flowchart LR
    A["音频源"] --> B["移频变调<br/>(啸叫抑制)"]
    B --> C["变声<br/>(voicechanger)"]
    C --> D["混响/回声<br/>(echo)"]
    D --> E["DAC"]
    F["link_pitchshift_howling_sound"] --> G["link_voice_changer_sound"]
    G --> H["link_echo_sound"]

Configuration Options

编译开关宏

宏默认作用域说明
ANS_EN关编译期使能 ANS 降噪,并编译 ans_api.c、NoiseSuppressLib
VO_PITCH_EN关编译期使能 vo_pitch 变调模块
PITCHSHIFT_HOWLING_EN关编译期编译移频啸叫抑制算法(howling_pitchshifter_api.c)
HOWLING_EN关运行期在扬声器链中实际链接移频啸叫抑制
VO_CHANGER_EN关编译期使能变声模块(voicechanger.c 整体 #if VO_CHANGER_EN 包裹)

ANS 运行参数(ans_api.c 内定义)

参数值格式说明
NS_MODE0枚举算法模式(0 = 窄带)
NS_IS_WIDEBAND1枚举1 对应 16 kHz;0 对应 8 kHz。决定支持的采样率
READSIZE128样点每次 run 处理块长
ANS_RUN_BUFFSIZE5400字节算法运行缓冲(静态,.ans_data)
ANS_TMP_BUFSIZE3604字节算法临时缓冲(静态,.ans_data)
ANS_AggressFactor125×65536/100 ≈ 1.25Q16抑制强度,范围 1~2,越大越强
ANS_MinSuppress10×65536/100 ≈ 0.1Q16最小抑制量,范围 0~1,越小越强
ANS_NoiseLevel1429×1024 ≈ -75 dBQ23噪声门限电平(-100dB ~ -40dB)

变声参数(voicechanger.c 默认值)

参数默认说明
vc_parm.shiftv65音高偏移(变调程度)
vc_parm.formant_shift100共振峰偏移(100 = 不变形)
vc_parm.speedv80语速
vc_parm.effect_vEFFECT_VC_AV_BIRD5预置音效(如鸟鸣等)
vs_parm.randpercent100颤音随机百分比
vs_parm.vibrate_lenCtrol30颤音时长控制
vs_parm.vibrate_rate_u / vibrate_rate_d0 / 100上/下颤音速率
VC_NG_THRES712噪声门控阈值(底噪大的方案建议值)

API Reference

ANS 模块(ans_api.c)

int ans_run(void *hld, short *inbuf, int len)

处理 128 点(256 字节)PCM 块的降噪。先输出上一帧剩余数据;输入不足一帧或上一帧未输出完时返回 0。

参数:

  • hld (void*): 效果对象句柄(当前实现未使用,保留接口兼容)
  • inbuf (short*): 输入 PCM 数据
  • len (int): 输入数据长度(字节)

返回: 本次消费的字节数(成功为 READSIZE * sizeof(short) = 256,否则 0)。

void *ans_api(void *obuf, void **ppsound, u32 sr)

初始化 ANS 效果。校验采样率(仅 16 kHz 通过当前配置)、查询并断言缓冲大小、以定点参数调用 NoiseSuppress_Init,最后调 ans_phy 构建效果节点。

参数:

  • obuf (void*): 前级输出缓冲
  • ppsound (void**): 出参,返回后级 sound 节点
  • sr (u32): 采样率,非 8000/16000 返回 NULL

返回: EFFECT_OBJ* 效果对象;失败返回 NULL 并打印 ans not support curr sr。

void *link_ans_sound(void *p_sound_out, void *p_ans_obuf, u32 sr)

把 ANS 链接进录音链。成功置 B_DEC_EFFECT 并返回后级节点;失败保持原链,返回原节点。

变声模块(voicechanger.c)

void *voice_changer_api(void *obuf, u32 sr, void **ppsound)

设置变声默认参数(shiftv=65、formant_shift=100、speedv=80、effect_v=鸟鸣5),调用 voice_changer_phy 完成初始化。返回 EFFECT_OBJ*。

void *voice_changer_phy(void *obuf, u32 sr, VOICECHANGER_AV_PARM *pvc_parm, VOICESYN_AV_PARM *pvs_parm, void **ppsound)

底层初始化:ops->need_buf(sr, parm) 校验静态缓冲 buflen(0x2010 字节)是否够用(不足打印 buff_len not enough 并返回 0),然后 ops->open() 打开算法实例并绑定 vc_pitch_io 输出回调,最后装配 vchange_obj(run = voice_changer_run)。

int voice_changer_run(void *hld, short *inbuf, int len)

经 sound_in_obj 中转调用算法库 ops->run(p_dbuf, inbuf, len),返回处理结果字节数。

void update_voice_changer_parm(VOICECHANGER_AV_PARM *new_vc_parm, VOICESYN_AV_PARM *new_vsyn_ctrol)

运行时热更新参数:NULL 参数直接返回;否则在 OS_ENTER_CRITICAL/OS_EXIT_CRITICAL 临界区内用新参数重开算法实例。

void *link_voice_changer_sound(void *p_sound_out, void *p_dac_cbuf, void **pp_effect, u32 in_sr)

把变声链接进扬声器链;成功时通过 pp_effect 回传效果对象、把后级 p_obuf 重绑到 DAC 缓冲,并置 B_DEC_EFFECT。

Failure Modes, Edge Cases & Concurrency

  • 采样率不支持:ans_api 只接受 8 kHz/16 kHz(当前配置固定 16 kHz 宽带),其他采样率初始化返回 NULL,链路自动跳过该效果(ans init fail),系统继续以无降噪方式工作——失败降级而非崩溃。
  • 静态缓冲不足:ANS 用 ASSERT(ANS_RUN_BUFFSIZE >= tolbufsize) 硬校验;变声用运行时 need_buf 比较并返回错误码。换算法库版本时若缓冲区需求增大,会在初始化阶段立刻暴露。
  • 块长不足/背压:ans_run 对 len < 256 与 remain_len != 0 均返回 0。这保证后级永远只收到完整块,代价是瞬时延迟可能增加一帧(约 8 ms @16 kHz)。
  • 参数更新的并发安全:update_voice_changer_parm 依赖关中断(OS_ENTER_CRITICAL)保证"重开算法实例"与音频中断处理互斥。若在支持多核/多任务的平台上移植,需改为互斥锁或双缓冲方案。
  • 重开即重置:变声参数更新通过 ops->open 重开实例,意味着内部状态(颤音相位、时延缓冲)会清零,连续快速更新参数可能引入可闻的爆音/卡顿,上层应限频更新。
  • 编译宏缺失时的行为:ANS_EN/VO_CHANGER_EN 未定义时相关文件整体不编译(#if defined(ANS_EN) && (ANS_EN)、#if VO_CHANGER_EN),link_* 调用点也被同条件包裹,不存在空指针悬链问题。

Performance & Operational Notes

  • 零动态内存:所有效果缓冲(.ans_data、.voicechanger_data)在链接期布局,运行期无 malloc/free,适合 MCU 实时音频。
  • 固定块长流水:每级 128 点/256 字节块处理 + remain_output 背压,CPU 占用稳定可预估;ANS 为 O(帧长) STFT 加窗运算,128 点窗口(STFT_Win_FixHalf_M256_D128)与跳长匹配。
  • 内存段隔离:.ans_data/.voicechanger_data 与普通数据分离,便于链接脚本放置到 SRAM 特定区域,也便于排查内存占用。
  • 日志埋点:初始化成功/失败均打印 log_info/log_error(如 ans init succ/fail、voice change init succ/fail、need buff len 0x%x),调试时可直接从串口日志判断各效果是否挂载成功。
  • 采样率联动:ANS 依赖 read_audio_adc_sr() 的实际 ADC 采样率,变调/变声依赖 adc_sr/in_sr;修改系统采样率配置时需同步核对各效果支持范围。

Extension Points

  • 新增音效:在 sound_effect_list 下新建目录,按既有模板实现 xxx_api()(初始化 + 静态缓冲)、xxx_run()(块处理 + remain_output 背压)、link_xxx_sound()(链接入 + B_DEC_EFFECT),并在 speak_api.c/encoder_api.c 中用对应编译宏包裹调用点。
  • 调参接口:变声已提供 update_voice_changer_parm() 作为运行时调参入口,可被协议栈/App 调用;ANS 参数目前为编译期常量,如需运行时可调需仿照变声增加"临界区重开实例"接口。
  • 链路顺序:扬声器链中移频 → 变声 → 回声的顺序由 speak_api.c 中 link_* 调用次序决定,调整调用顺序即可改变效果叠加次序(需注意啸叫抑制必须位于反馈回路信号注入点之前才有效)。

Related Links

  • ANS 实现 ans_api.c
  • 变声实现 voicechanger.c
  • 编码器接入 ANS(encoder_api.c)
  • 扬声器接入移频啸叫抑制(speak_api.c)
  • 混响头文件 reverb_api.h
  • 回声头文件 echo_api.h
  • ANS 算法库头文件 NoiseSuppressLib.h
  • 相关兄弟页面:EQ(pcm_eq)、啸叫抑制(notch_howling)、变速(speed)、能量检测(energe_detect)
Prev
音频编码与录音
Next
音频输出、功放与硬件重采样