炬芯ATS308X的扩音器:麦克风到扬声器直通功能实现

扩音器:麦克风到扬声器直通

app mic_pa 让手表充当放大器(扩音器):麦克风拾取到的任何声音都会立即从扬声器输出。

sh

复制代码
app mic_pa start [sr_khz] [mic_gain] [dac_vol] [mono]   # 默认值:16 200 -600 1
app mic_pa stop
app mic_pa info
app mic_pa vol  -300        # 运行时扬声器音量,步进 0.01 dB,0 = 最大
app mic_pa gain 300         # 运行时麦克风增益,步进 0.1 dB(300 = 30 dB)

工作原理

音频从不经过 CPU。shell 音频驱动已经有一条 ADDA 路径(audio_record_attr / audio_play_attr 中的 adda_en):

text

复制代码
ADC DMA 半缓冲区 --(audio_rec_read_data_cb)--> shell_audio_ringbuf (1 KiB)
                                                            |
                            (audio_play_write_data_cb) --> DAC DMA --> 扬声器
  • 当 handle->attribute.adda_en 被设置时,audio_rec_read_data_cb() 将每个采集到的半缓冲区放入共享的 ADDA 环形缓冲区(ring_buf_put)。

  • audio_play_write_data_cb() 从该环形缓冲区取出数据(ring_buf_get),并用 audio_out_write() 写入 DAC,因此扬声器仅以两个 DMA 的延迟播放麦克风信号。当环形缓冲区为空时,DAC 得到静音而不是杂音。

  • zephyr/drivers/audio/shell/audio_driver_shell.c 中的 audio_pa_start() 以 adda_en 构建两个实例,并按正确顺序启动它们:

    1. 创建录音器实例(它初始化 ADDA 环形缓冲区),

    2. audio_record_config()------占用采集会话,ADC 仍停止,

    3. 创建 + 配置 + 启动 DAC 播放器(它首先播放静音),

    4. audio_record_start()------现在环形缓冲区携带麦克风数据。

    audio_pa_stop() 以相反顺序关闭它们。

  • 应用侧(application/bt_watch/src/main/mic_pa.c)添加策略:通话进行时拒绝启动,并在通话到来时立即自行停止(bt_manager_hfp_get_status()),见下文。

  • CONFIG_MIC_PA(application/bt_watch/src/Kconfig、prj.conf)启用它。

参数

参数 默认值 单位 含义
sr_khz 16 kHz ADC 和 DAC 的采样率
mic_gain 200 0.1 dB ADC 通道 0 增益,200 = 20.0 dB
dac_vol -600 0.01 dB DAC 音量,0 = 最大,-600 = -6 dB
mono 1 - 忽略:此板的 DAC(phy_leopard)仅接受 MONO_MODE,因此单个麦克风通道始终送往两个 DAC 通道
mic 0 - 转发哪个板载麦克风:0 = 模拟输入对 INPUT0,1 = INPUT1

两个麦克风(app mic_pa mic)

板上有两个模拟麦克风。它们挂在编解码器的两个模拟输入对上:

模拟输入 焊盘 板级映射条目
对 0 INPUT0P / INPUT0N AUDIO_ANALOG_MIC0 -> adc 通道 0(扩音器之前使用的)
对 1 INPUT1P / INPUT1N 第二个麦克风

zephyr/boards/arm/ats3089p_dev_watch/board.c 中的板级表有一个 AUDIO_ANALOG_MIC1 条目,但它是 MIC0 的副本(相同的 INPUT0 对),因此选择它不会改变任何东西。AUDIO_ANALOG_MIC2 才是有用的:它将通道 0 映射到 INPUT0 对,通道 1 映射到 INPUT1 对 ,即同时采集两个麦克风。因此扩音器总是启动 AUDIO_ANALOG_MIC2(ch_gain[0] 和 ch_gain[1] 都获得 mic_gain),驱动的 ADC 回调在进入 ADDA 环形缓冲区之前仅保留选定的通道(audio_driver_shell.c 中的 pa_mic_stereo / pa_mic_sel)。因为两个通道始终被采集,切换只是翻转该选择:

text

复制代码
app mic_pa start 16 100 -900 1 0    # 星号:两个麦克风开启,转发 INPUT0
app mic_pa mic 1                    # 切换到 INPUT1 麦克风,声音继续运行
app mic_pa mic 0                    # 再切回
app mic_pa info                     # 显示当前活动的是哪一个

此板上所有 VMIC 偏置都已启用(CONFIG_AUDIO_ADC_0_VMIC_CTL_ARRAY = {3, 3, 3}),因此第二个麦克风无需额外 GPIO 即可供电。现在 ADC 传输两个通道(半缓冲区每 8 ms 到达一次,而不是 16 ms),到达环形缓冲区的去交织单声道流仍为 32 kB/s,这正是 DAC 消耗的。

在这些默认值下语音很响;麦克风靠近扬声器的扩音器会产生反馈(啸叫),因此请从较小的 mic_gain 或较低的 dac_vol 开始,然后逐步提高。

仅一个采集会话

ADC 只有一个采集会话(当第二个用户请求时,驱动记录 channel type:8 session max 1 and opened 1),因此扩音器不能与任何其他东西共享麦克风:

  • 普通的 audio record ... rec_file=1 录音在扩音器运行时将无法打开 ADC(反之亦然),

  • 电话通话需要同一会话用于两个方向,因此 app mic_pa 在通话期间拒绝启动,并在通话到来时停止扩音器(mic_pa: a call is up, megaphone off)。同时运行两者会使通话没有下行和扬声器声音。

  • 自动通话桥接(CONFIG_CALL_RECORDER)仅在录音文件打开时起作用,因此不会干扰扩音器。

备注

  • 不涉及媒体播放器和 DSP 会话:路径是本地 ADC 和 DAC 及其 DMA。不会写入磁盘,因此扩音器可以一直运行到电池耗尽。

  • 两个 DMA 缓冲区必须不同,这就是 shell 定义 SHELL_ADC_USE_DIFFERENT_BUFFER 的原因:录音器(id 0)使用 shell_audio_buffer1,播放器(id 0)使用 shell_audio_buffer。

  • 如果音调听起来不对(太低/半速),请尝试 mono 0:这会告诉 DAC 将环形缓冲区作为立体声读取,而不是将一个通道扩展到两个。注意 :在此板上不可能。zephyr/drivers/audio/phy_leopard/phy_audio_dac.c 中的 phy_dac_prepare_enable() 仅接受 MONO_MODE,对 STEREO_MODE 回答 [dac0][E]: Invalid channel mode 2,这会使 audio_out_open() 失败,启动返回 -14(-EFAULT)。因此 audio_pa_start() 强制 mono_en = 1,并在请求 0 时打印 mic_pa: this dac supports mono mode only, using mono。

  • 直通可以根据需要随意停止和重新启动:ADC/DAC 实例每次都会重新创建,ADDA 环形缓冲区由 audio_record_create() 重新初始化。

  • 扩音器运行时两个音频 shell 实例的 id 为 0,因此后续的 audio record create / audio play create 会重用它们(首先调用 audio_record_stop() / audio_play_stop())。

相关推荐
a努力。3 小时前
四维解析Agent记忆架构:从零实战LangGraph长期记忆
java·开发语言·数据库
睡一觉就好了。3 小时前
序列化与非序列化
linux·网络
Rain的Java大神之路3 小时前
被问麻了!Function Calling、MCP、Skill 到底啥关系?一张图讲透三层架构,附 Java 生产落地全流程+30个坑
java·后端·ai编程
Wang's Blog3 小时前
Java 项目部署之 Docker工具快速入门: Docker Compose 初识与安装
java·开发语言·docker
niucloud-admin3 小时前
JAVA V6 多商户商城 开发文档——项目启动
java·开发语言·python
众链网络3 小时前
Agent 重试的工程解法:幂等、断点续跑与熔断怎么落地
java·前端·数据库·人工智能
想做小南娘,发现自己是女生喵4 小时前
第三周学习文档 Linux C 多进程与多线程
linux·c语言·学习
Wang's Blog4 小时前
Java 项目部署之 Docker工具快速入门: 镜像命令从 pull 到 load 的完整实操
java·开发语言·docker
Wang's Blog4 小时前
Java 项目部署之 Docker工具快速入门: Docker 数据卷命令与挂载实操
java·开发语言·docker