一起调试XVF3800麦克风阵列(六)

一、实际调试AEC参考与麦克风延时

上一章节我们使用工具分析了延时,这一次我们继续调试:

默认 AUDIO_MGR_SYS_DELAY 的值为-32 测量演示如下图为41个样本(未在40以内):

这里我们直接设置AUDIO_MGR_SYS_DELAY 的值为0 (延时参考信号):

复制代码
xvf_host --use i2c AUDIO_MGR_SYS_DELAY 0

测得延时为9个样本

这里 9 = 41 -32 (非常精确的展示了延时参考信号后对齐的样本数量!!!)

为了使延时小于40个采样点的动态余量,设置AUDIO_MGR_SYS_DELAY为 -28,推测41 - (32-28) = 41 - 4 = 37(延时样本满足动态余量又小于40!!)

如下图所示:非常精准的展示了如何调整AEC的延时!!!请分别对4个MIC分别进行测量因果关系!并找到平衡值!

二、确认AEC是否收敛

AEC 收敛(Acoustic Echo Cancellation Convergence) ,指的是回声消除算法中的自适应滤波器,逐步学习并逼近真实"回声路径",直到回声被有效消除、系统进入稳定工作的过程

此时我们播放一段测试音频,并读取AEC_AECCONVERGED的值,1 表示自适应滤波器学习完成,即收敛成功。

复制代码
xvf_host --use i2c AEC_AECCONVERGED

收敛成功:

三、读取AEC系数

当AEC收敛成功后,输入如下命令采集4个 AEC模块的参数(XVF3800对每一路麦克风都做了AEC回声消除处理!!!

python 复制代码
xvf_host --use i2c -gf  xx.bin

我在home目录下执行的生成了如下四个文件:

进入conda安装所有依赖环境下执行如下命令:

复制代码
python3 /home/raspberry/XVF3800-Software_v3_2_1-3/sources/xvf_tools.py read_aec_filter xx.bin

这里我们来详细分析,这个过程是干了什么:

时域分析(左图):

  • 冲激响应形状:应类似房间冲激响应
  • 能量分布:主要能量应集中在早期样本(我们集中在200个样本以内!后面拖尾很快衰减到接近 0,即收敛了!!
  • 滤波器长度:反映 AEC 可处理的延迟范围

频域分析(右图):

  • 频率响应:显示不同频率的增益
  • 峰值位置:可能对应主要回声路径的频率特性(低频能量偏高,中频相对平滑,高频衰减明显噪声占主导说明喇叭低频更强,高频在空气 + 壳体中被衰减,AEC 在高频主要靠 RES(残余回声抑制)
  • 动态范围:-75dB 以下通常表示噪声或未使用的频率

统计信息:

  • 峰值功率:反映滤波器的主要能量
  • 平均功率:反映整体滤波器能量水平

上图我们为了观察到左图与延时的对应关系,我们修改 XVF3800-Software_v3_2_1-3/sources/modules/fwk_xvf/modules/tuning/tuning目录下的代码 read_aec_filter.py文件,替换如下代码(限制为200默认 增加-t参数 自定义样本数量):

python 复制代码
# Copyright 2023 XMOS LIMITED.
# This Software is subject to the terms of the XCORE VocalFusion Licence.
import argparse
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt

"""
This script is used for plotting the XVF38xx AEC filter coefficients that are read from the device
by using the hostapp -gf <filename.bin> option.
Please note that the input argument to this script is the filename.bin without the .f<x>m<y> extension, so
the same filename that was used in the -gf option when retrieving the filter from the device.

Requirements:
    python3
    numpy
    matplotlib

Usage
    python3 read_aec_filter.py <aecfilter.bin>

Normally, this script is run after retrieving the filter coefficients from the device.
For example, from the <path_to_host_app>/build directory, one would run:
    sudo ./xvf_host -gf aecfilt.bin
    python3 ../python/read_aec_filter.py aecfilt.bin
"""
def read_aec_filter(filelist, show_plot, time_samples=200):
    num_files = len(filelist)
    fig, axs = plt.subplots(num_files, 2, figsize=(14,7))

    fig.suptitle('AEC impulse response', fontsize=14)
    for i in range(len(filelist)):
        axs[i,0].set_title(f'{filelist[i].name}, time')
        axs[i,1].set_title(f'{filelist[i].name}, freq')
        buf = np.fromfile(str(filelist[i]), dtype=np.float32)
        Buf = np.fft.rfft(buf)
        freq = np.linspace(0, 8000, num=int(len(buf)/2)+1) # Map 0-8kHz into 3072/2 bins
        H = 20*np.log10(np.abs(Buf + 1e-39))
        H_mean = 10*np.log10(np.mean(np.abs(Buf)**2) + 1e-39)
        print(f"{filelist[i].name}: Magnitude response peak = {np.max(H):.2f} dB, mean: {H_mean:.2f} dB")

        # Only plot the first time_samples samples for time domain view
        plot_samples = min(time_samples, len(buf))
        axs[i,0].plot(buf[:plot_samples])
        axs[i,0].set(xlabel='samples', ylabel='Amplitude')
        axs[i,0].set_xlim([0, plot_samples])
        axs[i,1].plot(freq, H)
        axs[i,1].set(xlabel='frequency(Hz)', ylabel='Magnitude(dB)')
        axs[i,1].set_ylim([max(-75, np.min(H)), np.max(H)+5])
        if i:
            axs[i,0].sharex(axs[0,0])
            axs[i,1].sharex(axs[0,1])

    figinstance = plt.gcf()
    if show_plot:
        plt.tight_layout()
        plt.show()

    plot_name = filelist[0].name.split('.')[0] + ".png"
    print(f"Filter plot saved in {plot_name}")
    figinstance.savefig(plot_name, dpi=200)


if __name__ == "__main__":
    parser = argparse.ArgumentParser("Plot the AEC filter files that have been read from the device using the host app's -gf option")
    parser.add_argument('aecfilter_file', type=str,
                        help='AEC filter .bin file without the .f<x>m<y> extension',
                        )
    parser.add_argument('--num-mics', '-m', type=int, default=4, help='number of mic inputs. Default=4')
    parser.add_argument('--num-ref', '-r', type=int, default=1, help='number of far end inputs. Default=1')
    parser.add_argument('--time-samples', '-t', type=int, default=200, help='number of time domain samples to display. Default=200')

    args = parser.parse_args()
    filelist = []
    for f in range(args.num_ref):
        for m in range(args.num_mics):
            name = Path(str(args.aecfilter_file) + f'.f{f}.m{m}')
            if not name.is_file():
                print(f"Error: file {name} not found")
                exit(1)
            filelist.append(name)

    show_plot = True
    read_aec_filter(filelist, show_plot, args.time_samples)

重新执行

python 复制代码
python3 /home/raspberry/XVF3800-Software_v3_2_1-3/sources/xvf_tools.py read_aec_filter xx.bin

输出:

如图所示:第一个麦克风的左图的地一个峰值样本大概在25 + 25 / 2 = 37个与测量值是一致的!只是在收敛的过程中有些缓慢!

在条参数的过程中:

频域的峰值系数和平均系数都应该小于0dB:

AEC 的自适应滤波器 ≈ 一个 FIR 回声路径模型
在频域看,就是:从扬声器 → 麦克风 的传递函数 H(f)

这个 H(f) 是物理系统,它有一个铁律:被动声学系统不可能整体"放大"信号!平均增益 > 0 dB在物理上是不合理的!(声音从扬声器发出,经过空气传播、墙壁反射、家具吸收,最终到达麦克风。 这个过程只能衰减能量,不能增加能量。)

如果平均增益 > 0 dB,意味着AEC滤波器在整体上"放大了"参考信号,这在物理上不可能,只可能是:

  • 系统延迟补偿错误(SYS_DELAY 设置不当,导致非因果关系
  • 麦克风增益过高(MIC_GAIN 太大),设置AUDIO_MGR_MIC_GAIN来满足要求!
  • 或参考信号(FAR)相对太小

在AEC调试中,频率域系数均值,数值越低(更负)说明整体衰减越多,通常更安全;但也不能太低,就是为了确保滤波器整体不会放大信号,否则回声消除力度不足。

峰值系数(peak coefficient value)建议接近0 dB:

在声学回声路径中,通常存在一条最强的直达声路径(direct path):

  • 这是扬声器到麦克风的最短路径,几乎没有反射。
  • 它的能量在整个回声路径中占比最高,对应冲激响应的第一个主峰(时间域的strong first peak)。
  • 在频率域,这个直达声路径在宽频带上贡献了一个相对平坦的增益(因为传播距离短,吸收少)。

因此:

  • peak coefficient value 本质上主要反映了直达声路径的增益
  • 如果 peak ≈ 0 dB(即 ≈1),意味着AEC滤波器对这条最强路径的估计既没有放大,也没有过度衰减,幅度匹配得非常准。
  • 这会带来两个关键好处:
    • 最大化回声抑制量(ERLE):最强的那部分回声被精准抵消,后续反射尾巴也更容易被覆盖。
    • 提高系统稳定性:避免局部频段过补偿(>0 dB)导致啸叫风险,也避免压得太低(<<0 dB)导致滤波器收敛太慢或对直达声抑制不足。

所以核心心思路就是:

  1. 先调 SYS_DELAY → 让时间域第一个强峰出现在合理位置(10~30样本)
  2. 再调 MIC_GAIN → 让频率域 peak 尽量靠近 0 dB(但不超过)
  3. 检查 mean 是否 < 0 dB(如不满足,略降 MIC_GAIN)
  4. 最后实听验证:单讲清晰无回声,双讲不失真、不啸叫

目前当前 peak coefficient value 已经快接近 0,但没有接近。

查看当前 AUDIO_MGR_MIC_GAIN的值:10

python 复制代码
xvf_host --use i2c AUDIO_MGR_MIC_GAIN

我们增大到20试试效果

python 复制代码
xvf_host --use i2c AUDIO_MGR_MIC_GAIN 20

测试结果如图所示,mic3明显接近0即增大MIC增益是合适的!

python 复制代码
python3 /home/raspberry/XVF3800-Software_v3_2_1-3/sources/xvf_tools.py read_aec_filter xx.bin -t 1000

查看也在200左右个样本收敛了!

相关推荐
北京耐用通信1 天前
耐达讯自动化Profibus三路中继器:低成本搞定阀门定位器稳定组网的硬核方案
人工智能·物联网·自动化
敢敢のwings1 天前
VGGT-Long:极简主义驱动的公里级单目三维重建系统深度解析(Pytorch安装手册版)
人工智能·pytorch·python
技术狂人1681 天前
(七)大模型工程落地与部署 10 题!vLLM/QPS 优化 / 高可用,面试实战必备(工程篇)
人工智能·深度学习·面试·职场和发展·vllm
新芒1 天前
海尔智家加速全球体育营销
大数据·人工智能
Hcoco_me1 天前
大模型面试题37:Scaling Law完全指南
人工智能·深度学习·学习·自然语言处理·transformer
aiguangyuan1 天前
CART算法简介
人工智能·python·机器学习
manjianghong861 天前
制作高质量AI视频需要哪些步骤
人工智能·音视频·ai视频·ai应用
咕噜企业分发小米1 天前
阿里云和华为云在AI教育领域有哪些技术竞争?
人工智能·阿里云·华为云
咕噜企业分发小米1 天前
阿里云和华为云在AI教育领域有哪些技术挑战?
人工智能·阿里云·华为云