# Avaota F1 开发日志 - Day 3:音频系统开发 **版本**:v1.0 **日期**:2024-11-24 **主机环境**:Ubuntu 24.04 LTS **目标平台**:Avaota F1 (全志 V821 / 32-bit RISC-V) --- ## 第一部分:阶段 3 - 音频系统实现 ### 1. 音频采集模块(✅ 完成) #### 1.1 代码实现 实现了完整的音频采集类 [`audio_capture.cpp`](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/src/audio/audio_capture.cpp): **核心功能**: - 基于 **ALSA (Advanced Linux Sound Architecture)** - 支持 PDM 麦克风采集 - 音频格式:16kHz, 16-bit, Mono (单声道) **关键实现**: 1. **设备初始化** (`init()`): ```cpp snd_pcm_open(&m_pcm_handle, device, SND_PCM_STREAM_CAPTURE, 0); snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0); snd_pcm_hw_params_set_channels(handle, params, 1); // Mono ``` 2. **PCM 数据读取** (`read()`): ```cpp snd_pcm_sframes_t ret = snd_pcm_readi(handle, buffer, frames); ``` 3. **错误处理与自动恢复**: - **Overrun (缓冲区溢出)**:自动调用 `snd_pcm_prepare()` 恢复 - **Suspend (设备挂起)**:调用 `snd_pcm_resume()` 唤醒设备 - **通用错误**:使用 `snd_pcm_recover()` 尝试恢复 #### 1.2 缓冲区优化 为了平衡延迟和稳定性,配置了合理的缓冲区参数: - **Period Size**: 160 frames (10ms @ 16kHz) - **Buffer Size**: 1600 frames (100ms @ 16kHz) 这样的配置提供了: - **低延迟**:10ms 周期,适合实时语音交互 - **稳定性**:100ms 缓冲,防止频繁的 underrun/overrun --- ### 2. 音频播放模块(✅ 完成) #### 2.1 代码实现 创建了音频播放类 [`audio_player.h`](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/src/audio/audio_player.h) 和 [`audio_player.cpp`](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/src/audio/audio_player.cpp): **核心功能**: - 支持 I2S 扬声器 (MAX98357A) - 音频格式:16kHz, 16-bit, Mono - 与音频采集模块使用相同的参数配置 **关键实现**: 1. **播放设备初始化** (`init()`): ```cpp snd_pcm_open(&m_pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0); // 配置相同的音频参数 (16kHz, S16_LE, Mono) ``` 2. **PCM 数据写入** (`write()`): ```cpp snd_pcm_sframes_t ret = snd_pcm_writei(handle, buffer, frames); ``` 3. **Underrun 处理**: - 检测到 `-EPIPE` 错误时自动恢复 - 重新准备设备并重试写入 - 避免播放卡顿和杂音 --- ### 3. 测试程序(✅ 完成) #### 3.1 功能实现 创建了独立的音频测试程序 [`test_audio.cpp`](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/src/test_audio.cpp),包含三个测试模式: **测试 1:音频采集 (录音)** - 录音时长:5 秒 - 输出文件:`test_recording.pcm` (RAW PCM 格式) - 用途:验证麦克风和 ALSA 采集功能 ```bash /tmp/test_audio capture ``` **测试 2:音频播放** - 播放录制的 PCM 文件 - 用途:验证扬声器和 ALSA 播放功能 ```bash /tmp/test_audio playback ``` **测试 3:回环测试** - 同时进行录音和播放 - 用途:验证全双工音频能力,检测延迟 - 现象:应听到自己的声音(有轻微延迟) ```bash /tmp/test_audio loopback ``` #### 3.2 使用说明 ```bash # 运行所有测试(交互式) ./test_audio # 运行单个测试 ./test_audio capture # 或 1 ./test_audio playback # 或 2 ./test_audio loopback # 或 3 ``` --- ### 4. 编译配置(✅ 完成) #### 4.1 编译脚本 创建了专用的编译脚本 [`build_phase3.sh`](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/src/build_phase3.sh): **关键配置**: ```bash # 32位 RISC-V 工具链 CXX="${TOOLCHAIN_DIR}/bin/riscv32-unknown-linux-g++" # ALSA 库链接 LDFLAGS+=" -lasound" # 编译模块 - utils/logger.cpp - audio/audio_capture.cpp - audio/audio_player.cpp - test_audio.cpp ``` **编译步骤**: ```bash cd ~/path/to/AvaotaF1/src chmod +x build_phase3.sh ./build_phase3.sh ``` **输出**: - 可执行文件:`test_audio` - 架构:ELF 32-bit RISC-V - 已 strip,体积优化 --- ## 第二部分:技术要点总结 ### 1. ALSA 库关键概念 #### 1.1 PCM 设备 - **Capture (采集)**:从麦克风读取数据 - **Playback (播放)**:向扬声器写入数据 - **设备名**:`hw:0,0` (硬件卡 0, 设备 0) 或 `default` (默认设备) #### 1.2 音频参数 | 参数 | 值 | 说明 | |------|-----|------| | Format | `SND_PCM_FORMAT_S16_LE` | 16-bit signed little-endian | | Sample Rate | 16000 Hz | 16 kHz (语音级别) | | Channels | 1 | 单声道 (Mono) | | Access | `SND_PCM_ACCESS_RW_INTERLEAVED` | 交织模式 | #### 1.3 错误码处理 | 错误码 | 含义 | 解决方案 | |--------|------|----------| | `-EPIPE` | Overrun (采集) / Underrun (播放) | `snd_pcm_prepare()` | | `-ESTRPIPE` | 设备挂起 | `snd_pcm_resume()` | | 其他 | 通用错误 | `snd_pcm_recover()` | --- ### 2. 架构设计对比 #### ESP32S3 vs AvaotaF1 | 组件 | ESP32S3 (原) | AvaotaF1 (新) | |------|--------------|---------------| | **麦克风接口** | PDM (ESP_I2S 库) | PDM (ALSA) | | **扬声器接口** | I2S STD (ESP_I2S 库) | I2S (ALSA + MAX98357A) | | **API 风格** | Arduino 同步阻塞 | Linux ALSA 标准 | | **缓冲管理** | ESP_I2S 自动 | 手动配置 Period/Buffer | | **错误恢复** | 硬件自动 | 软件手动处理 | **关键差异**: - ESP32S3 的 I2S 库高度封装,使用简单 - ALSA 需要手动配置参数,但更灵活强大 - AvaotaF1 需要显式处理 Overrun/Underrun --- ### 3. 与网络模块集成 音频系统已按照 [`main.cpp`](file:///d:/CodingProjects/Antigravity/NaviGlass/NaviGlassClient/src/main.cpp) 的设计预留接口: #### 3.1 音频采集上行 (WebSocket) ```cpp // 主程序中的音频采集线程 void audio_capture_thread() { AudioCapture mic("hw:0,0", 16000, 1); mic.init(); WSClient ws_aud(SERVER_HOST, SERVER_PORT, "/ws_audio"); ws_aud.connect(); while (running) { int16_t buffer[320]; // 20ms @ 16kHz mic.read(buffer, 320); ws_aud.send_binary((uint8_t*)buffer, 640); // 640 bytes } } ``` #### 3.2 音频播放下行 (HTTP) ```cpp // 主程序中的音频播放线程 void audio_player_thread() { AudioPlayer speaker("hw:0,0", 16000, 1); speaker.init(); HTTPClient http; http.connect("http://192.168.110.188:8081/stream.wav"); http.stream_download([&](const uint8_t* data, size_t size) { speaker.write((const int16_t*)data, size / 2); return true; }); } ``` --- ## 第三部分:待完成事项 ### 当前阶段完成度:约 80% **✅ 已完成**: 1. 音频采集类实现 - **100%** 2. 音频播放类实现 - **100%** 3. 测试程序 - **100%** 4. 编译脚本 - **100%** **⚠️ 待验证**(需要开发板环境): 1. **Device Tree 配置** - **0%** - 启用 PDM 麦克风驱动 - 启用 I2S 扬声器驱动 - 验证 `/proc/asound/cards` 设备存在 2. **ALSA 设备测试** - **0%** - `arecord -l` 列出录音设备 - `aplay -l` 列出播放设备 - 原始 ALSA 工具测试 3. **音频模块集成测试** - **0%** - 运行 `test_audio` 程序 - 验证录音质量 - 验证播放音质 4. **端到端验证** - **0%** - 与服务器联调 - WebSocket 音频上传测试 - HTTP TTS 播放测试 --- ## 第四部分:部署与测试指南 ### 1. 编译(PC 端) ```bash cd ~/path/to/AvaotaF1/src ./build_phase3.sh # 验证输出 file test_audio # 预期:ELF 32-bit LSB executable, UCB RISC-V, statically linked ``` ### 2. 部署(SD 卡方式) ```bash # PC 端:复制到 SD 卡 cp test_audio /path/to/sd_card/ # 板端:挂载并运行 mount /dev/mmcblk0p1 /mnt/extsd cp /mnt/extsd/test_audio /tmp/ chmod +x /tmp/test_audio ``` ### 3. 前置检查(板端) ```bash # 检查声卡设备 cat /proc/asound/cards # 应该看到:0 [xxx]: ... # 列出录音设备 arecord -l # 应该看到:card 0: ..., device 0: ... # 列出播放设备 aplay -l # 应该看到:card 0: ..., device 0: ... ``` > [!WARNING] > 如果以上命令返回 "No soundcards found",说明 Device Tree 音频驱动未正确配置,需要先修改 DTS 并重新编译烧录内核。 ### 4. 原始 ALSA 测试(板端) ```bash # 测试录音(5 秒) arecord -D hw:0,0 -f S16_LE -r 16000 -c 1 -d 5 test.wav # 测试播放 aplay -D hw:0,0 test.wav ``` ### 5. 音频模块测试(板端) ```bash # 运行所有测试 /tmp/test_audio # 或单独测试 /tmp/test_audio capture # 录音 5 秒 /tmp/test_audio playback # 播放录音 /tmp/test_audio loopback # 回环测试 ``` --- ## 第五部分:可能遇到的问题 ### 问题 1:ALSA 设备不存在 **现象**: ``` [AudioCapture] Cannot open device 'hw:0,0': No such device ``` **原因**: - Device Tree 未启用音频驱动 - 内核音频模块未编译 - 硬件连接问题 **解决**: 1. 检查 `/sys/class/sound/` 目录 2. 修改 Device Tree 启用 `dmic` 和 `i2s0` 节点 3. 重新编译内核:`make kernel_menuconfig` → 启用 ALSA 驱动 4. 烧录新固件并重启 --- ### 问题 2:录音有声音但有杂音 **可能原因**: - 采样率不匹配 - 缓冲区配置不当 - 硬件增益过高 **解决**: ```bash # 使用 alsamixer 调整音量 alsamixer # 按 F4 选择 Capture,调整麦克风增益 # 或使用 amixer amixer set 'Capture' 50% ``` --- ### 问题 3:播放音质差或卡顿 **可能原因**: - Underrun (缓冲区欠载) - CPU 占用过高 - 缓冲区过小 **解决**: - 增大 Buffer Size:修改 `audio_player.cpp` 中的 `buffer_size` 参数 - 调整线程优先级: ```cpp pthread_setschedprio(pthread_self(), 90); // 实时优先级 ``` --- ### 问题 4:回环测试有严重延迟 **原因**: - 缓冲区过大 - Period 设置不当 **解决**: - 减小 Period Size:从 160 改为 80 (5ms) - 减小 Buffer Size:从 1600 改为 800 (50ms) --- ## 第六部分:下一步计划 ### 阶段 4:摄像头系统(Day 4-5) **优先级**:高(最大技术风险点) **任务清单**: 1. 确认 GC2083 摄像头配置 2. 加载 ISP 参数文件 (`isp_ini_gc2083`) 3. 验证 `sample_virvi` 示例程序 4. 实现 Camera 类 (调用 MPP 库) 5. 实现 JPEG 硬件编码 6. 集成到 WebSocket 视频流 **为何现在进入摄像头阶段**: - 音频系统代码已完成,等待板端验证 - 摄像头涉及 MPP 库和 ISP 配置,技术难度最高 - 预留更多时间处理可能的画质问题(绿屏/过暗) --- ## 🏆 Day 3 成果 **核心成就**: 1. ✅ 完成音频采集类实现 (ALSA + PDM) 2. ✅ 完成音频播放类实现 (ALSA + I2S) 3. ✅ 创建完整的测试程序 (3 种测试模式) 4. ✅ 配置编译脚本和工具链 5. ✅ 文档化所有技术要点和部署流程 **技术栈确认**: - **音频框架**: ALSA (Advanced Linux Sound Architecture) - **采集设备**: PDM 麦克风 (hw:0,0) - **播放设备**: I2S 扬声器 MAX98357A (hw:0,0) - **音频格式**: 16kHz, 16-bit, Mono, PCM **代码质量**: - 完整的错误处理机制 - 自动恢复功能 (Overrun/Underrun/Suspend) - 详细的日志输出 - 优化的缓冲区配置 **音频系统软件开发完成**: 1. ✅ 音频采集和播放类已实现 2. ✅ 测试程序已创建 3. ⚠️ 等待板端 Device Tree 配置才能验证 --- ## 第七部分:I2S 音频硬件配置(✅ 完成) ### 1. 硬件方案确认 **芯片平台**:全志 V821 (sun300iw1p1) **音频输出方案**:I2S0 + MAX98357A 数字功放 **关键引脚**: - **PD12**: I2S0_BCLK (位时钟) - **PD13**: I2S0_LRCK (左右声道时钟) - **PD15**: I2S0_DOUT0 (音频数据输出) --- ### 2. Device Tree 配置 #### 2.1 问题分析 **初始错误**: ``` sunxi:pin-42000000.pinctrl:[ERR]: unsupported function i2s0 on pin PD12 sunxi-snd-plat-i2s: probe of 42032000.i2s0_plat failed with error -22 ``` **根本原因**: 1. Pinctrl 驱动中 PD12/13/15 的 I2S 功能定义为 `i2s0_bclk`、`i2s0_lrck`、`i2s0_dout0` 2. 不支持通用的 `"i2s0"` 函数名 3. Pinctrl 框架不支持为多个引脚指定不同的 function #### 2.2 最终解决方案 创建**三个独立的 pinctrl 节点**,每个引脚一个: ```dts /* I2S0 individual pin definitions for MAX98357A */ i2s0_bclk_pin: i2s0_bclk@0 { pins = "PD12"; function = "i2s0_bclk"; drive-strength = <20>; bias-disable; }; i2s0_lrck_pin: i2s0_lrck@0 { pins = "PD13"; function = "i2s0_lrck"; drive-strength = <20>; bias-disable; }; i2s0_dout0_pin: i2s0_dout0@0 { pins = "PD15"; function = "i2s0_dout0"; drive-strength = <20>; bias-disable; }; i2s0_pins_b: i2s0_pins@1 { pins = "PD12", "PD13", "PD15"; function = "gpio_in"; }; ``` #### 2.3 I2S 平台设备配置 修改 `&i2s0_plat` 节点引用三个独立的引脚配置: ```dts &i2s0_plat { pinctrl-used; pinctrl-names = "default","sleep"; pinctrl-0 = <&i2s0_bclk_pin &i2s0_lrck_pin &i2s0_dout0_pin>; pinctrl-1 = <&i2s0_pins_b>; status = "okay"; }; ``` #### 2.4 MAX98357A Codec 节点 在根节点添加 MAX98357A 驱动: ```dts / { max98357a: max98357a { #sound-dai-cells = <0>; compatible = "maxim,max98357a"; status = "okay"; }; }; ``` 并在 `&i2s0_mach` 中引用: ```dts &i2s0_mach { status = "okay"; i2s0_codec: soundcard-mach,codec { sound-dai = <&max98357a>; }; }; ``` --- ### 3. Pinctrl 驱动调试 #### 3.1 驱动源码分析 **文件位置**:`bsp/drivers/pinctrl/pinctrl-sun300iw1.c` **关键发现**: - PD12/13/15 在非 FPGA 配置块中有完整的 I2S 功能定义 - Function 0x6 对应 I2S 功能 - 必须使用准确的函数名:`i2s0_bclk`、`i2s0_lrck`、`i2s0_dout0` **驱动修改**(已在前期完成): - 注释掉简化版本的 PD12/13 定义(没有 I2S 功能) - 确保使用完整版本的引脚定义 --- ### 4. 编译与烧录 #### 4.1 SDK 配置 添加 alsa-utils 工具: ```bash cd ~/ProgramFiles/AvaotaF1/avaota_sdk/tina-v821-release make menuconfig # 选择: Sound ---> alsa-utils ``` #### 4.2 编译步骤 ```bash source build/envsetup.sh lunch avaota_f1-tina ulimit -n 4096 make -j4 pack ``` #### 4.3 烧录 使用 PhoenixSuit 烧录生成的固件: ``` out/v821_linux_avaota_f1_uart0_nor.img ``` --- ### 5. 硬件验证(✅ 成功) #### 5.1 系统状态检查 ```bash root@(none):/# cat /proc/asound/cards 0 [audiocodec ]: audiocodec - audiocodec 1 [sndi2s0 ]: sndi2s0 - sndi2s0 # ✅ I2S 设备已识别 root@(none):/# cat /proc/asound/pcm 00-00: ... playback 1 : capture 1 01-00: sunxi-snd-plat-i2s-HiFi HiFi-0 : playback 1 # ✅ I2S 播放设备 root@(none):/# ls /dev/snd/ controlC0 controlC1 pcmC0D0c pcmC0D0p pcmC1D0p timer # ✅ 设备节点存在 ``` #### 5.2 Pinctrl 配置验证 ```bash root@(none):/# cat /sys/kernel/debug/pinctrl/42000000.pinctrl/pinconf-pins | grep -A 3 "pin 108\|pin 109\|pin 111" pin 108 (PD12): output drive strength (20 mA) # ✅ 配置已生效 pin 109 (PD13): output drive strength (20 mA) # ✅ pin 111 (PD15): output drive strength (20 mA) # ✅ ``` #### 5.3 音频测试(🎉 成功) **硬件连接**: | MAX98357A | Avaota F1 | |-----------|-----------| | BCLK | PD12 | | LRC | PD13 | | DIN | PD15 | | VIN | 3.3V | | GND | GND | | SD | 3.3V | **测试命令**: ```bash # 播放随机白噪音测试 dd if=/dev/urandom bs=192000 count=1 | aplay -D hw:1,0 -f S16_LE -r 48000 -c 2 ``` **测试结果**: ``` Playing raw data 'stdin' : Signed 16 bit Little Endian, Rate 48000 Hz, Stereo ✅ 扬声器输出刺耳的白噪音 - 证明硬件通路完全正常! ``` --- ### 6. 成功标志 ✅ **I2S 设备已识别**:`sndi2s0` (card 1) ✅ **设备节点已创建**:`/dev/snd/pcmC1D0p` ✅ **Pinctrl 配置生效**:Drive strength 20mA ✅ **I2S 驱动加载**:`sunxi-snd-plat-i2s` mapping ok ✅ **MAX98357A 工作**:音频数据成功解码并放大 ✅ **扬声器输出正常**:能听到测试音(白噪音) **关键成就**: - 从零开始分析并修复 Device Tree 配置 - 调试 pinctrl 驱动,找到正确的函数定义 - 创建三个独立节点,绕过 pinctrl 框架限制 - 验证完整的音频输出硬件通路 --- ### 7. 经验总结 #### 7.1 Device Tree 配置原则 1. **精确匹配函数名**:必须使用驱动源码中定义的准确名称 2. **独立节点策略**:每个引脚不同功能时,必须创建独立节点 3. **Pinctrl 属性**:`pinctrl-0` 可以引用多个节点(phandle 列表) 4. **Drive Strength**:I2S 信号需要较高的驱动强度(20mA) #### 7.2 调试方法 1. **查看内核日志**:`dmesg | grep -i "i2s\|pinctrl"` 2. **检查 pinmux 状态**:`/sys/kernel/debug/pinctrl/*/pinmux-pins` 3. **检查 pinconf 状态**:`/sys/kernel/debug/pinctrl/*/pinconf-pins` 4. **验证设备节点**:`cat /proc/asound/cards`, `ls /dev/snd/` #### 7.3 常见陷阱 ❌ **错误做法**: ```dts pins = "PD12", "PD13", "PD15"; function = "i2s0"; // ❌ 驱动不支持 ``` ❌ **错误做法**: ```dts pins = "PD12", "PD13", "PD15"; function = "i2s0_bclk", "i2s0_lrck", "i2s0_dout0"; // ❌ 框架不支持多值 ``` ✅ **正确做法**: ```dts /* 每个引脚独立定义 */ i2s0_bclk_pin: i2s0_bclk@0 { pins = "PD12"; function = "i2s0_bclk"; // ✅ 一个引脚一个功能 }; ``` --- ## 🏆 Day 3 最终成果 ### 软件开发 1. ✅ AudioCapture 类实现(ALSA 录音) 2. ✅ AudioPlayer 类实现(ALSA 播放) 3. ✅ test_audio 测试程序(3 种测试模式) 4. ✅ 编译脚本和工具链配置 ### 硬件配置 5. ✅ I2S0 引脚 Device Tree 配置(PD12/13/15) 6. ✅ MAX98357A Codec 驱动集成 7. ✅ Pinctrl 驱动调试和修复 8. ✅ 音频输出硬件验证成功 ### 下一步行动 1. 在阶段四中实现音频采集(PDM 麦克风配置) 2. 集成音频模块到主程序网络流 3. 开始摄像头系统开发(阶段 4)