Files
Docs/DevLogs/Day03.md
2026-01-05 18:06:08 +08:00

752 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 # 回环测试
```
---
## 第五部分:可能遇到的问题
### 问题 1ALSA 设备不存在
**现象**
```
[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