Files
Docs/DevLogs/Day3.md
2025-12-31 16:18:28 +08:00

19 KiB
Raw Blame History

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

核心功能

  • 基于 ALSA (Advanced Linux Sound Architecture)
  • 支持 PDM 麦克风采集
  • 音频格式16kHz, 16-bit, Mono (单声道)

关键实现

  1. 设备初始化 (init())

    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())

    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.haudio_player.cpp

核心功能

  • 支持 I2S 扬声器 (MAX98357A)
  • 音频格式16kHz, 16-bit, Mono
  • 与音频采集模块使用相同的参数配置

关键实现

  1. 播放设备初始化 (init())

    snd_pcm_open(&m_pcm_handle, device, SND_PCM_STREAM_PLAYBACK, 0);
    // 配置相同的音频参数 (16kHz, S16_LE, Mono)
    
  2. PCM 数据写入 (write())

    snd_pcm_sframes_t ret = snd_pcm_writei(handle, buffer, frames);
    
  3. Underrun 处理

    • 检测到 -EPIPE 错误时自动恢复
    • 重新准备设备并重试写入
    • 避免播放卡顿和杂音

3. 测试程序( 完成)

3.1 功能实现

创建了独立的音频测试程序 test_audio.cpp,包含三个测试模式:

测试 1音频采集 (录音)

  • 录音时长5 秒
  • 输出文件:test_recording.pcm (RAW PCM 格式)
  • 用途:验证麦克风和 ALSA 采集功能
/tmp/test_audio capture

测试 2音频播放

  • 播放录制的 PCM 文件
  • 用途:验证扬声器和 ALSA 播放功能
/tmp/test_audio playback

测试 3回环测试

  • 同时进行录音和播放
  • 用途:验证全双工音频能力,检测延迟
  • 现象:应听到自己的声音(有轻微延迟)
/tmp/test_audio loopback

3.2 使用说明

# 运行所有测试(交互式)
./test_audio

# 运行单个测试
./test_audio capture    # 或 1
./test_audio playback   # 或 2
./test_audio loopback   # 或 3

4. 编译配置( 完成)

4.1 编译脚本

创建了专用的编译脚本 build_phase3.sh

关键配置

# 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

编译步骤

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 的设计预留接口:

3.1 音频采集上行 (WebSocket)

// 主程序中的音频采集线程
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)

// 主程序中的音频播放线程
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 端)

cd ~/path/to/AvaotaF1/src
./build_phase3.sh

# 验证输出
file test_audio
# 预期ELF 32-bit LSB executable, UCB RISC-V, statically linked

2. 部署SD 卡方式)

# 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. 前置检查(板端)

# 检查声卡设备
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 测试(板端)

# 测试录音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. 音频模块测试(板端)

# 运行所有测试
/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 启用 dmici2s0 节点
  3. 重新编译内核:make kernel_menuconfig → 启用 ALSA 驱动
  4. 烧录新固件并重启

问题 2录音有声音但有杂音

可能原因

  • 采样率不匹配
  • 缓冲区配置不当
  • 硬件增益过高

解决

# 使用 alsamixer 调整音量
alsamixer
# 按 F4 选择 Capture调整麦克风增益

# 或使用 amixer
amixer set 'Capture' 50%

问题 3播放音质差或卡顿

可能原因

  • Underrun (缓冲区欠载)
  • CPU 占用过高
  • 缓冲区过小

解决

  • 增大 Buffer Size修改 audio_player.cpp 中的 buffer_size 参数
  • 调整线程优先级:
    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_bclki2s0_lrcki2s0_dout0
  2. 不支持通用的 "i2s0" 函数名
  3. Pinctrl 框架不支持为多个引脚指定不同的 function

2.2 最终解决方案

创建三个独立的 pinctrl 节点,每个引脚一个:

/* 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 节点引用三个独立的引脚配置:

&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 驱动:

/ {
    max98357a: max98357a {
        #sound-dai-cells = <0>;
        compatible = "maxim,max98357a";
        status = "okay";
    };
};

并在 &i2s0_mach 中引用:

&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_bclki2s0_lrcki2s0_dout0

驱动修改(已在前期完成):

  • 注释掉简化版本的 PD12/13 定义(没有 I2S 功能)
  • 确保使用完整版本的引脚定义

4. 编译与烧录

4.1 SDK 配置

添加 alsa-utils 工具:

cd ~/ProgramFiles/AvaotaF1/avaota_sdk/tina-v821-release
make menuconfig
# 选择: Sound ---> alsa-utils

4.2 编译步骤

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 系统状态检查

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 配置验证

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

测试命令

# 播放随机白噪音测试
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 StrengthI2S 信号需要较高的驱动强度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 常见陷阱

错误做法

pins = "PD12", "PD13", "PD15";
function = "i2s0";  // ❌ 驱动不支持

错误做法

pins = "PD12", "PD13", "PD15";
function = "i2s0_bclk", "i2s0_lrck", "i2s0_dout0";  // ❌ 框架不支持多值

正确做法

/* 每个引脚独立定义 */
i2s0_bclk_pin: i2s0_bclk@0 {
    pins = "PD12";
    function = "i2s0_bclk";  // ✅ 一个引脚一个功能
};

🏆 Day 3 最终成果

软件开发

  1. AudioCapture 类实现ALSA 录音)
  2. AudioPlayer 类实现ALSA 播放)
  3. test_audio 测试程序3 种测试模式)
  4. 编译脚本和工具链配置

硬件配置

  1. I2S0 引脚 Device Tree 配置PD12/13/15
  2. MAX98357A Codec 驱动集成
  3. Pinctrl 驱动调试和修复
  4. 音频输出硬件验证成功

下一步行动

  1. 在阶段四中实现音频采集PDM 麦克风配置)
  2. 集成音频模块到主程序网络流
  3. 开始摄像头系统开发(阶段 4