一块会议麦克风如何同时处理 USB 音频与蓝牙回声
一块会议麦克风如何同时处理 USB 音频与蓝牙回声
会议设备最麻烦的地方不是把麦克风接上,而是声音会从扬声器再次进入麦克风。USB、AUX 和蓝牙又可能轮流成为音频入口,参考信号只要晚了几个缓冲块,远端就会听到拖尾回声。
本文对 MKF360 的 V0.7 工程做一次面向复现的静态核验,把硬件连接、DMA 数据流和 SpeexDSP 参数放在同一条链路里说明。所用版本是 Board1_V0.7、Schematic1_V0.7 和 PCB1_V0.7;没有把未完成的编译、烧录和实体声学测试写成结果。
图1 项目页提供的成品照,只说明原项目装配形态。
先画清楚音频的四个边界
主控 STM32H750VBT6 负责采集、播放和 DSP,ESP32 负责蓝牙音频。USB UAC、AUX 和蓝牙输入都进入接口输入缓冲,扬声器播放的参考信号单独进入回声消除器。这样做的意义是让“麦克风听到什么”和“扬声器实际播了什么”保持两个可检查的来源。
图2 同版 EDA 原理图 P1。模块关系先在这里确认,具体网络再看后续图页。
源码的核心顺序是:
interface_in_read(buffer1, PROCESS_FRAME_SAMPLES);
feedback_read(buffer2, PROCESS_FRAME_SAMPLES);
speex_echo_cancellation(echo, buffer1, buffer2, buffer3);
speex_preprocess_run(preprocess, buffer3);
interface_out_write(buffer3, PROCESS_FRAME_SAMPLES);
speaker_write(buffer3, PROCESS_FRAME_SAMPLES);
buffer1 是待处理的麦克风或接口输入,buffer2 是播放反馈。若把“准备播放的数据”冒充“已经播放的数据”,后面的滤波器只能不断追赶固定延迟。
PDM 先交给 DFSDM,再交给 DSP
板上使用 MP34DT05TR-A PDM 麦克风。PDM 不是 PCM,不能直接送入 Speex。IOC 文件把 DFSDM filter 0/1 配为 Sinc4、抽取因子 64 和循环 DMA,audio_dfsdm.c 再通过半传输/全传输回调设置事件,主循环在缓冲够一帧时读取数据。
图3 P2 的麦克风与 DFSDM 网络。硬件有两路麦克风,但当前软件只实际读取一路,因此不能把它写成已经完成波束成形的阵列。
void HAL_DFSDM_FilterRegConvHalfCpltCallback(
DFSDM_Filter_HandleTypeDef *filter)
{
if (filter == &hdfsdm1_filter0 || filter == &hdfsdm1_filter1)
event_group_set_event(EventGroup1,
EventGroup1DfsdmDmaHalfReady);
}
把整帧 DSP 放在中断里会拉长中断时间;这里只报事件,能让采集和算法各自守住边界。
AUX 输入的关键是电平和时序
AUX 是交流信号,ADC3 只能采集正电压。P3 的模拟前端完成低通、隔直和直流偏置,然后以差分方式送入 ADC3。配置文件给出了 12 位、TIM6 触发、BDMA 循环以及 16 倍过采样:
图4 P3 的 ADC3 输入。偏置是把交流波形搬进 ADC 有效范围,不能简单理解成“音量增益”。
ADC3.ExternalTrigConv=ADC_EXTERNALTRIG_T6_TRGO
ADC3.OversamplingMode=ENABLE
ADC3.Ratio=16
ADC3.Resolution=ADC_RESOLUTION_12B
ADC3.SingleDiff-4#ChannelRegularConversion=ADC_DIFFERENTIAL_ENDED
Bdma.ADC3.0.Mode=DMA_CIRCULAR
循环 DMA 只保证硬件继续写,不能保证软件及时消费。接口切换或任务阻塞时,旧数据仍可能被覆盖,所以应在工程中补充高水位和丢帧计数。
播放侧要同时看 DAC、I2S 和 DMA
DAC1 两个通道由 TIM7 触发,SPI3 使用全双工 I2S DMA。源码定义业务采样率 48 kHz、每毫秒 48 个采样、每块 20 ms:
图5 P4 的 DAC、I2S 与外部接口。
#define MKF360_AUDIO_SAMPLE_RATE_HZ (48000U)
#define PROCESS_FRAME_SAMPLES (512U)
#define FEEDBACK_DELAY_SAMPLES (1922U)
speex_echo_state_init(PROCESS_FRAME_SAMPLES, 1024);
512 个采样在 48 kHz 下约 10.67 ms,1922 个采样约 40.04 ms。后一个数字只是当前工程的补偿量,不能直接当成设备实测延迟。工程注释还记录了 I2S 接收偶发 0 样本,audio_io.c 用前值填补缓解空洞;这不是时钟同步方案,产品化时仍应记录错误回调和真实时钟偏差。
六条环形缓冲把切换落到代码
audio_buffer.c 用 lwrb 维护两路麦克风、反馈、扬声器播放、接口输入和接口输出缓冲。读取前检查可用字节数,不足一帧就返回错误;切换接口时调用 reset_audio_rb() 清掉旧路径残留。
图6、图7 同一 V0.7 工程重新导出的 PCB 顶层和底层,顶底面分开保存,便于定位接口、电源和模拟回路。它们不能替代阻抗、电源完整性或声学实测。
static inline int generic_read(lwrb_t *rb, void *out, size_t n)
{
const uint32_t bytes = n * MKF360_AUDIO_SAMPLE_SIZE;
if (lwrb_get_full(rb) < bytes) return -1;
lwrb_read(rb, out, bytes);
return 0;
}
这套结构给出的可迁移经验有三条:先确定播放参考点,再调回声参数;让 DMA 回调只报事件,让主循环消费整帧;给每条环形缓冲增加水位统计,才能知道“偶发卡顿”究竟是接口切换、任务阻塞还是时钟偏差。
图8 装配过程照片只用于说明原项目状态,不代表本文重新焊接和实测。
资料与验证范围
项目页面:MKF360 无线会议麦克风,同版本工程为 Board1_V0.7、Schematic1_V0.7、PCB1_V0.7,项目页标注 GPL 3.0。源码获取位置为 EcutAtom336/MKF360。原理图和 PCB 来自嘉立创 EDA 专业版重新导出,装配照片来自项目页;只做裁剪、压缩和结构化分析。
本次只核验源码结构、IOC 配置、同版本 EDA 图和关键参数,未完成 Keil 编译、固件烧录、实体声学、温升或续航测试。SpeexDSP、CherryUSB 和 lwrb 按各自源码许可使用。
技术交流,欢迎关注微信公众号:美男子玩编程。



