add flac support and files management app
This commit is contained in:
1 parent
4126ea13a1
commit
45f7b83e7c
12 files changed
+1526
-139
No files matched your search
@@ -1,7 +1,25 @@
|
||||
# HiFi 播放器(ESP32-S3 + LVGL + ES8388 + SD 卡)
|
||||
|
||||
基于正点原子 **DNESP32S3** 开发板标准例程(IDF v5.3.x)移植改造的 HiFi 音乐播放器。
|
||||
使用 **SPI LCD** 作为显示界面、**SD 卡** 存储 WAV 音频、**4 个板载按键** 交互,界面底部实时标注每个按键的功能。
|
||||
基于正点原子 **DNESP32S3** 开发板标准例程移植改造的 HiFi 音乐播放器。
|
||||
使用 **SPI LCD** 作为显示界面、**SD 卡** 存储音频、**4 个板载按键** 交互,
|
||||
界面底部实时标注每个按键的功能。
|
||||
|
||||
当前包含 **3 个应用**:主界面(应用启动器)、音乐播放器、文件管理器。
|
||||
|
||||
音频通路为 **原生位深透传**:源文件是 16/24/32bit,就按对应位宽重新配置 I2S 与 ES8388,
|
||||
不做位深压缩,配合 FLAC 无损解码构成完整的无损链路。
|
||||
|
||||
---
|
||||
|
||||
## 版本记录
|
||||
|
||||
| 版本 | 主要变更 |
|
||||
| :--- | :--- |
|
||||
| V1.0 | 移植 BSP、LVGL 8.4、ES8388 + I2S,WAV 播放 |
|
||||
| V1.2 | 应用启动器框架、全局 K0 返回键、按应用分文件夹 |
|
||||
| V1.3 | iPhone 风格图标网格主界面 |
|
||||
| V1.4 | 增加 FLAC 解码(`esphome/micro-flac`),WAV/FLAC 统一音源层 |
|
||||
| **V1.5** | **原生位深透传 + I2S 位宽/采样率动态重配**;新增**文件管理器**应用;修复 `.flac` 文件无法被扫描到的问题 |
|
||||
|
||||
---
|
||||
|
||||
@@ -9,9 +27,11 @@
|
||||
|
||||
| 功能 | 说明 |
|
||||
| :--- | :--- |
|
||||
| 应用启动器 | 开机进入主界面,可选择并进入「音乐播放器」(预留 SETTINGS / ABOUT) |
|
||||
| 应用启动器 | 开机进入主界面,图标网格选择应用(MUSIC / FILES 可用,SETTINGS / ABOUT 占位) |
|
||||
| 音乐播放器 | **WAV**(PCM 16/24/32bit)与 **FLAC**(原生 FLAC / Ogg FLAC),支持 8k~96k 采样率 |
|
||||
| 文件管理器 | 浏览 SD 卡全部文件,可进出任意层级子文件夹,显示文件大小与类型图标 |
|
||||
| 原生位深输出 | 16/24/32bit 按源文件位深直接输出,**不做位深压缩**;I2S 位宽与采样率随曲目动态重配 |
|
||||
| 图形界面 | LVGL 8.4,320×240 横屏,深色 HiFi 风格 |
|
||||
| 音频播放 | **WAV**(PCM 16/24/32bit)与 **FLAC**(原生 FLAC / Ogg FLAC),I2S + ES8388,支持 8k~96k 采样率 |
|
||||
| 存储 | SD 卡(SPI 模式),FATFS,音乐目录 `/MUSIC` |
|
||||
| 播放控制 | 播放/暂停 / 下一曲 / 音量切换(无上一曲,K0 用于返回) |
|
||||
| 状态显示 | 曲目序号、歌名、格式、播放进度、音量、运行状态 |
|
||||
@@ -20,25 +40,58 @@
|
||||
| 音量记忆 | 音量保存在 NVS,掉电不丢失 |
|
||||
| 自动恢复 | SD 卡拔出/插入、目录为空时自动重新挂载与扫描 |
|
||||
|
||||
界面布局(两个界面):
|
||||
### 界面布局
|
||||
|
||||
```
|
||||
[主界面 HOME · iPhone 风格图标网格] [播放器 PLAYER]
|
||||
┌──────────────────────────┐ ┌──────────────────────────┐
|
||||
│ ☰ APP LAUNCHER 1/4 │ │ ♪ HiFi PLAYER 01/12 │
|
||||
├──────────────────────────┤ ├──────────────────────────┤
|
||||
│ ╭──╮ ╭──╮ ╭──╮ ╭──╮ │ │ ┌────┐ 歌名 │
|
||||
│ │♪ │ │📁│ │⚙ │ │☰ │ │ K3 打开 │ │ ♪ │ WAV 44100Hz 16bit│
|
||||
│ ╰──╯ ╰──╯ ╰──╯ ╰──╯ │ ───────────▶ │ └────┘ ( PLAYING ) │
|
||||
│ MUSIC FILES SETTINGS … │ ◀─────────── │ 00:42 ────●────── 03:35 │
|
||||
│ WAV files in SD /MUSIC │ K0 返回 │ 🔊 ──────●───── 60% │
|
||||
│ ● │ │ [←K0][⏯K1][⏭K2][🔊K3] │
|
||||
│ [◀K1][▶K2][✔K3][→K0] │ │ BACK PLAY NEXT VOL │
|
||||
└──────────────────────────┘ └──────────────────────────┘
|
||||
[主界面 HOME · iPhone 风格图标网格]
|
||||
┌──────────────────────────┐
|
||||
│ ☰ APP LAUNCHER 1/4 │
|
||||
├──────────────────────────┤
|
||||
│ ╭──╮ ╭──╮ ╭──╮ ╭──╮ │
|
||||
│ │♪ │ │📁│ │⚙ │ │☰ │ │ K3 打开选中的应用
|
||||
│ ╰──╯ ╰──╯ ╰──╯ ╰──╯ │
|
||||
│ MUSIC FILES SETTING ABOUT│
|
||||
│ WAV/FLAC files in SD … │
|
||||
│ ● │
|
||||
├──────────────────────────┤
|
||||
│ [◀K1] [▶K2] [✔K3] [→K0]│
|
||||
│ PREV NEXT OPEN SWITCH│
|
||||
└──────────────────────────┘
|
||||
|
||||
[播放器 PLAYER]
|
||||
┌──────────────────────────┐
|
||||
│ ♪ HiFi PLAYER 01/12 │
|
||||
├──────────────────────────┤
|
||||
│ ┌────┐ 歌名 │
|
||||
│ │ ♪ │ FLAC 44100Hz │
|
||||
│ └────┘ 24bit STEREO │
|
||||
│ ( PLAYING ) │
|
||||
│ 00:42 ────●────── 03:35 │
|
||||
│ 🔊 ──────●───── 60% │
|
||||
├──────────────────────────┤
|
||||
│ [←K0] [⏯K1] [⏭K2] [🔊K3] │
|
||||
│ BACK PLAY NEXT VOL │
|
||||
└──────────────────────────┘
|
||||
|
||||
[文件管理器 FILES]
|
||||
┌──────────────────────────┐
|
||||
│ 📁 FILES 12 SD │
|
||||
├──────────────────────────┤
|
||||
│ 0:/MUSIC/Album │
|
||||
├──────────────────────────┤
|
||||
│ 📁 .. <DIR> │
|
||||
│ 📁 Album <DIR> │ ← 选中行带强调色描边
|
||||
│ ♪ song01.flac 24.1M │
|
||||
│ 📄 notes.txt 128B │
|
||||
│ 📄 readme.md 2K │
|
||||
├──────────────────────────┤
|
||||
│ [←K0] [▲K1] [▼K2] [✔K3] │
|
||||
│ PARENT UP DOWN OPEN │
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
> 界面文本使用 LVGL 内置 Montserrat 字体(含图标),因此为 ASCII。
|
||||
> 如需中文界面,见第 9 节“扩展建议”。
|
||||
> 如需中文界面,见第 10 节“扩展建议”。
|
||||
|
||||
---
|
||||
|
||||
@@ -48,13 +101,14 @@
|
||||
| :--- | :--- | :--- |
|
||||
| SPI LCD(ST7789 320×240) | SPI2:SCLK=12, MOSI=11, CS=21, DC=40 | 与 SD 卡共用 SPI2 |
|
||||
| SD 卡 | SPI2:CS=2 | FATFS 挂载点 `/0:` |
|
||||
| ES8388 | I2S0:MCLK=3, BCLK=46, WS=9, DOUT=10, DIN=14 | 16bit 输出 |
|
||||
| ES8388 | I2S0:MCLK=3, BCLK=46, WS=9, DOUT=10, DIN=14 | 位宽随曲目动态配置(16/24/32bit) |
|
||||
| XL9555 | I2C0:SDA=41, SCL=42 | 按键、LCD 电源/复位/背光、喇叭使能 |
|
||||
| 按键 | XL9555 的 KEY0~KEY3 | 低电平有效 |
|
||||
| LED0 | GPIO1 | 播放时点亮 |
|
||||
|
||||
> LCD 电源(SLCD_PWR)、复位(SLCD_RST)、背光(LCD_BL)、喇叭使能(SPK_EN)
|
||||
> 均由 XL9555 扩展 IO 控制,初始化顺序必须是:`my_spi_init → myiic_init → xl9555_init → spilcd_init`。
|
||||
> 均由 XL9555 扩展 IO 控制,初始化顺序必须是:
|
||||
> `my_spi_init → myiic_init → xl9555_init → spilcd_init`。
|
||||
|
||||
---
|
||||
|
||||
@@ -67,6 +121,10 @@
|
||||
| K0 短按 | 在任意 App 内 → **返回主界面**;在主界面 → **切换到下一个应用** |
|
||||
| K0 长按(0.7s) | 任意界面 → **直接切换到下一个应用** |
|
||||
|
||||
> **例外**:实现了“就地返回”的 App 可以先消费掉 K0 短按。目前**文件管理器**就是这样——
|
||||
> 在子目录里按 K0 是返回上一级目录,只有在 `0:` 根目录时才回主界面。
|
||||
> 该行为由 `ui.c` 中调用 `ui_files_back()` 钩子实现,其它 App 不受影响。
|
||||
|
||||
### 主界面(应用启动器)
|
||||
|
||||
| 按键 | 功能 | 界面提示 |
|
||||
@@ -74,7 +132,7 @@
|
||||
| KEY0 | 切换到下一个应用 | `→ K0 SWITCH` |
|
||||
| KEY1 | 选择上一个图标 | `◀ K1 PREV` |
|
||||
| KEY2 | 选择下一个图标 | `▶ K2 NEXT` |
|
||||
| KEY3 | 打开选中的图标 | `✔ K3 OPEN` |
|
||||
| KEY3 | 打开选中的应用 | `✔ K3 OPEN` |
|
||||
|
||||
### 播放器界面
|
||||
|
||||
@@ -83,11 +141,24 @@
|
||||
| KEY0 | **返回主界面**(长按 = 切换应用) | `← K0 BACK` |
|
||||
| KEY1 | 播放 / 暂停 | `K1 PLAY`(播放中图标变为暂停) |
|
||||
| KEY2 | **下一曲** | `K2 NEXT` |
|
||||
| KEY3 | 音量切换(20/40/60/80/100%) | `K3 VOL` |
|
||||
| KEY3 | 音量切档(20/40/60/80/100%) | `K3 VOL` |
|
||||
|
||||
> 已按要求取消「上一首」功能,K0 释放出来作为独立的返回键。
|
||||
|
||||
按键由 `app.c` 的按键服务统一扫描(10ms 周期、30ms 去抖、短按/长按区分),
|
||||
### 文件管理器界面
|
||||
|
||||
| 按键 | 功能 | 界面提示 |
|
||||
| :--- | :--- | :--- |
|
||||
| KEY0 | **返回上一级目录**;已在根目录时回主界面(长按 = 切换应用) | `← K0 UP` / `← K0 HOME` |
|
||||
| KEY1 | 选择上一项(到顶后跳到底部) | `▲ K1 UP` |
|
||||
| KEY2 | 选择下一项 | `▼ K2 DOWN` |
|
||||
| KEY3 | 进入文件夹 / 查看文件详情 | `✔ K3 OPEN` |
|
||||
|
||||
> 退出子文件夹有两条路:按 **K0**,或选中列表最上方的 **`..`** 行按 **K3**。
|
||||
> 按 K3 打开普通文件时,屏幕下方会弹出 2 秒浮层,显示文件名、大小和类型
|
||||
> (`AUDIO` / `IMAGE` / `FILE`)。
|
||||
|
||||
按键由 `app.c` 的按键服务统一扫描(10ms 周期、30ms 去抖、短按/长按区分),
|
||||
`ui.c` 是事件的唯一消费者并负责分发,因此**不会重复扫描按键,也不会因为去抖延时而卡住音频**。
|
||||
|
||||
---
|
||||
@@ -109,7 +180,7 @@ hifi_player/
|
||||
│ ├── SPILCD/ # ST7789 SPI LCD(含 flush 同步)
|
||||
│ ├── SPI_SD/ # SD 卡 FATFS 挂载
|
||||
│ ├── ES8388/ # 音频 DAC 配置
|
||||
│ └── MYI2S/ # I2S 标准模式收发
|
||||
│ └── MYI2S/ # I2S 标准模式收发 + 位宽动态重配
|
||||
└── main/
|
||||
├── CMakeLists.txt
|
||||
├── idf_component.yml # 依赖:LVGL 8.4 + micro-flac
|
||||
@@ -122,6 +193,9 @@ hifi_player/
|
||||
├── home/ # 主界面(应用启动器)
|
||||
│ ├── ui_home.c
|
||||
│ └── ui_home.h
|
||||
├── files/ # 文件管理器 App
|
||||
│ ├── ui_files.c # 浏览 SD 卡目录(滑动窗口列表)
|
||||
│ └── ui_files.h
|
||||
└── player/ # 播放器 App
|
||||
├── ui_player.c/.h # 播放器界面
|
||||
├── player.c/.h # 播放核心(扫描 / I2S / 请求处理)
|
||||
@@ -137,7 +211,8 @@ hifi_player/
|
||||
│
|
||||
界面: ui.c(LVGL 任务,50ms) ────────┘ 唯一消费者
|
||||
├─ K0 → 返回主界面 / 切换应用(全局)
|
||||
└─ K1~K3 → 交给当前 App(ui_home / ui_player)
|
||||
│ └─ 例外:FILES 在子目录时先返回上一级
|
||||
└─ K1~K3 → 交给当前 App(ui_home / ui_files / ui_player)
|
||||
│
|
||||
播放: ui_player → player_request() → player_task 消费并执行
|
||||
(只有 APP_MODE_PLAYER 时才输出 I2S)
|
||||
@@ -149,11 +224,17 @@ hifi_player/
|
||||
|
||||
### 5.1 环境
|
||||
|
||||
* ESP-IDF **v6.1**(本工程已在此版本上编译验证通过,生成固件约 **893 KB**)
|
||||
* ESP-IDF **v6.1**(本工程已在此版本上编译验证通过,固件 `0xdb3f0` = **898,032 字节 ≈ 877 KB**,分区剩余 71%)
|
||||
* 也兼容 **v5.3.x / v5.4**(BSP 已按 IDF 6.x 的驱动组件拆分方式声明依赖)
|
||||
* 目标芯片:`esp32s3`
|
||||
* 首次编译需要联网下载 `lvgl/lvgl` 8.4.0 与 `esphome/micro-flac` 0.3.0
|
||||
|
||||
> **IDF 6.x 注意**:`driver` 这个“伞形”组件不再转发 `esp_driver_*` 子组件,
|
||||
> 因此在 `components/BSP/CMakeLists.txt` 里必须逐个显式列出:
|
||||
> `esp_driver_gpio esp_driver_i2c esp_driver_i2s esp_driver_spi esp_driver_sdspi esp_driver_sdmmc sdmmc esp_lcd fatfs`。
|
||||
> 同时 `driver/i2s.h`、`driver/i2c.h`、`I2S_BITS_PER_SAMPLE_*` 已被移除,
|
||||
> 代码里改用 `esp_driver_i2s` 头文件与纯数字位宽。
|
||||
|
||||
### 5.2 命令
|
||||
|
||||
```bash
|
||||
@@ -176,15 +257,16 @@ idf.py -p COMx flash monitor
|
||||
|
||||
## 6. SD 卡准备
|
||||
|
||||
1. SD 卡格式化为 **FAT32**(建议 ≤ 32GB)。
|
||||
2. 在根目录新建文件夹 **`MUSIC`**(大写),把音频文件放进去:
|
||||
1. SD 卡格式化为 **FAT32**(建议 ≤ 32GB)。**不支持 exFAT**。
|
||||
2. 在根目录新建文件夹 **`MUSIC`**,把音频文件放进去(**不递归子目录**):
|
||||
|
||||
```
|
||||
SD 卡根目录/
|
||||
└── MUSIC/
|
||||
├── canon.wav
|
||||
├── song.flac
|
||||
└── ...
|
||||
├── MUSIC/ ← 播放器只扫描这一层
|
||||
│ ├── canon.wav
|
||||
│ ├── song.flac
|
||||
│ └── ...
|
||||
└── 其他文件/ ← 可以用「文件管理器」应用浏览
|
||||
```
|
||||
|
||||
3. 音频要求:
|
||||
@@ -192,13 +274,32 @@ SD 卡根目录/
|
||||
| 容器 | 编码 | 位深 | 声道 | 采样率 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **WAV**(RIFF/WAVE) | PCM(`WAVE_FORMAT_PCM` 或 `EXTENSIBLE`+PCM) | 16 / 24 / 32bit | 单声道 / 立体声 | 8k ~ 96kHz |
|
||||
| **FLAC**(原生 `fLaC` / `Ogg FLAC`) | FLAC | 任意(4~32bit) | 1 ~ 8 声道 | 8k ~ 96kHz |
|
||||
| **FLAC**(原生 `fLaC` / `Ogg FLAC`) | FLAC | 4 ~ 32bit | 1 ~ 8 声道 | 8k ~ 96kHz |
|
||||
|
||||
4. 说明:
|
||||
* 两种格式都统一**转换为 16bit 立体声**后送给 ES8388:
|
||||
24/32bit 取高位,单声道复制到左右,多声道(FLAC)只取前两声道。
|
||||
* 界面上的 `WAV/FLAC xxxHz xxbit` 显示的是**源文件**参数。
|
||||
* 文件类型按**文件头**自动识别(`RIFF` / `fLaC` / `OggS`),扩展名只用于扫描列表。
|
||||
* 文件名建议**只用 ASCII**(英文 / 数字 / 下划线)。中文文件名能正常播放,
|
||||
但界面上会显示为 `?`(见下方第 5 条)。
|
||||
* 单曲数量上限 **200 首**(`MAX_TRACKS`),扩展名支持 `.wav` / `.flac`(大小写不敏感)。
|
||||
* 文件类型按**文件头**自动识别(`RIFF` / `fLaC` / `OggS`),扩展名只用于筛选列表。
|
||||
* **位深不再统一转 16bit**:源文件是什么位深就按什么位宽输出,映射关系见下表。
|
||||
* 中文文件名是 GBK 编码,LVGL 内置字体无法渲染,`make_display_name()` 会把
|
||||
连续的非 ASCII 字节折叠成一个 `?`,但**底层路径使用原始字节**,不影响播放。
|
||||
|
||||
### 6.1 位深与输出映射(V1.5)
|
||||
|
||||
| 源位深 | 每采样字节 | 每帧字节(立体声) | ES8388 R23 字长 | I2S `data_bit_width` / `ws_width` | `mclk_multiple` |
|
||||
| :--- | :--- | :--- | :--- | :--- | :--- |
|
||||
| ≤ 16bit(8 / 16) | 2 | 4 | 16bit(码 3) | `I2S_DATA_BIT_WIDTH_16BIT` | 256 |
|
||||
| 17 ~ 24bit(20 / 24) | 3 | 6 | 24bit(码 0) | `I2S_DATA_BIT_WIDTH_24BIT` | **384** |
|
||||
| 25 ~ 32bit(32) | 4 | 8 | 32bit(码 4) | `I2S_DATA_BIT_WIDTH_32BIT` | 256 |
|
||||
|
||||
> **为什么 24bit 要用 384 倍 MCLK**:ESP-IDF 的 `i2s_std.c` 有一条硬性校验——
|
||||
> slot 位宽为 24bit 时 `mclk_multiple` 必须是 3 的倍数,否则
|
||||
> `i2s_channel_reconfig_std_clock()` 直接返回 `ESP_ERR_INVALID_ARG`。
|
||||
> `myi2s.c` 的 `i2s_set_samplerate_bits_sample()` 会自动切换:24bit → 384,其余 → 256。
|
||||
>
|
||||
> 例:20bit 的 FLAC 会输出成 24bit 槽位(`out_bytes = 3`),
|
||||
> 界面上显示为 `FLAC 44100Hz 20->24bit`,提示“源 20bit,实际按 24bit 输出”。
|
||||
|
||||
---
|
||||
|
||||
@@ -225,22 +326,35 @@ SD 卡根目录/
|
||||
### 音频解码链路
|
||||
|
||||
```
|
||||
┌─ WAV ── f_read 原始 PCM ── 取高位 / 单声道复制 ─┐
|
||||
audio_source_read┤ ├─▶ 16bit 立体声 ──▶ I2S
|
||||
└─ FLAC ── micro_flac 解码 ── 取高位 / 声道归一化 ─┘
|
||||
┌─ WAV ── f_read 原始 PCM ─────── 单声道复制 ──┐
|
||||
audio_source_read │
|
||||
└─ FLAC ── micro_flac 解码 ── 声道归一化 ────────┘
|
||||
│
|
||||
┌────────────────────────────────┘
|
||||
▼
|
||||
立体声 + 原生位深(16/24/32bit,左对齐)
|
||||
│
|
||||
audio_set_format(rate, out_bits)
|
||||
├─ es8388_i2s_cfg(0, len_code)
|
||||
└─ i2s_set_samplerate_bits_sample(rate, bits)
|
||||
│
|
||||
▼
|
||||
i2s_tx_write(frames * 2 * out_bytes)
|
||||
```
|
||||
|
||||
`audio_source.c` 只对外暴露「给我一块 16bit 立体声 PCM」,因此 `player.c` 的播放循环
|
||||
与容器格式完全无关;新增格式(如 MP3)只需在 `audio_source_open()` 里加一个分支。
|
||||
`audio_source.c` 对外只暴露「给我一块**立体声 + 原生位深** PCM,并告诉我 `out_bytes`」,
|
||||
因此 `player.c` 的播放循环与容器格式完全无关;新增格式(如 MP3)只需在
|
||||
`audio_source_open()` 里加一个分支。
|
||||
|
||||
### 关键实现点
|
||||
|
||||
1. **单任务音频流**
|
||||
一个任务完成“读文件 + 解码 + 写 I2S + 处理按键请求”,每块音频约 46ms,
|
||||
避免多任务同时访问 FATFS/I2S 的竞争。
|
||||
一个任务完成“读文件 + 解码 + 写 I2S + 处理按键请求”,每块音频约 23ms
|
||||
(`AUDIO_CHUNK_FRAMES = 1024` @44.1kHz),避免多任务同时访问 FATFS/I2S 的竞争。
|
||||
发送缓冲按最坏情况分配 8 字节/帧(32bit 立体声):`AUDIO_BUF_SIZE = 1024 × 8 = 8KB`。
|
||||
|
||||
2. **非阻塞按键去抖**
|
||||
`key_scan()` 每次读取 XL9555 输入寄存器(一次 I2C 读 2 字节),
|
||||
`key_task()` 每 10ms 读一次 XL9555 输入寄存器(一次 I2C 读 2 字节),
|
||||
连续 3 次采样一致才确认状态变化,不使用 `vTaskDelay` 阻塞去抖。
|
||||
|
||||
3. **LVGL 与 SPI LCD**
|
||||
@@ -250,45 +364,94 @@ audio_source_read┤ ├─▶
|
||||
* LVGL 的 RGB565 是小端,ST7789 要求高字节在前,因此在 `disp_flush_cb()` 中做原地字节交换。
|
||||
|
||||
4. **I2S 通道管理**
|
||||
`i2s_trx_start()/i2s_trx_stop()` 增加了状态判断,可以重复调用而不会报错;
|
||||
切换采样率时会先关闭通道,重新配置 `data_bit_width / ws_width / sample_rate` 后再打开。
|
||||
`i2s_trx_start()/i2s_trx_stop()` 带状态判断,可重复调用而不报错;
|
||||
切换曲目时会先关闭通道,重新配置 `data_bit_width / ws_width / mclk_multiple /
|
||||
sample_rate` 后再由播放循环按需打开。
|
||||
|
||||
5. **FLAC 解码**
|
||||
* 使用 `esphome/micro-flac`(ESP32-S3 带 Xtensa 汇编优化,16bit 约 30 倍实时)。
|
||||
* 该库是 C++,`flac_dec.cpp` 提供一层纯 C 封装(`flac_dec_open/read/close`),
|
||||
* 该库是 C++,`flac_dec.cpp` 提供一层纯 C 封装(`flac_dec_open/read/close`),
|
||||
播放器侧代码仍是 C。
|
||||
* 用的是 `uint8_t*` 输出重载(**原生字节打包**),而不是 `int32_t*` 的 32bit 归一化重载,
|
||||
这样才能保留源位深。
|
||||
* 码流缓冲 8KB,采用「未消费数据前移 + 追加读取」,避免解码器返回
|
||||
`NEED_MORE_DATA` 时丢数据;同时有“无进展”保护,坏文件不会死循环。
|
||||
* 解码输出缓冲按 STREAMINFO 的 `max_block_size` 动态分配,优先放 PSRAM。
|
||||
|
||||
6. **状态同步**
|
||||
播放器只写 `s_state`(互斥锁保护),UI 每 50ms 读取一次快照,
|
||||
播放器只写 `s_state`(互斥锁保护),UI 每 50ms 读取一次快照,
|
||||
LVGL 的所有控件操作都发生在 LVGL 任务内,天然线程安全。
|
||||
|
||||
---
|
||||
|
||||
## 8. 常见问题
|
||||
## 8. 文件管理器实现要点
|
||||
|
||||
| 现象 | 排查方向 |
|
||||
| :--- | :--- |
|
||||
| 屏幕全白 / 全黑 | 检查 XL9555 是否初始化成功、背光 `LCD_BL` 是否置 1;确认 `sdkconfig.defaults` 的 PSRAM 配置与板子一致 |
|
||||
| 屏幕花屏 | 确认 SPI 时钟 60MHz 未超频,排线接触良好;检查 `LCD_RGB_DATA_ENDIAN_BIG` 与字节交换逻辑 |
|
||||
| 显示 `NO SD CARD` | SD 卡需 FAT32;`my_spi_init()` 必须在 `spilcd_init()` 之前;检查 CS=GPIO2 |
|
||||
| 显示 `PUT WAV IN /MUSIC` | SD 卡根目录需有 `MUSIC` 文件夹且包含 `.wav` / `.flac` 文件 |
|
||||
| 显示 `UNSUPPORTED WAV` / `UNSUPPORTED FILE` | WAV 需为 PCM(16/24/32bit);FLAC 需为标准 `fLaC` 或 Ogg FLAC;APE/MP3 需要额外解码库 |
|
||||
| FLAC 无法播放但 WAV 正常 | 检查串口日志 `flac_dec` 前缀的报错;确认组件已下载(`managed_components/esphome__micro-flac`) |
|
||||
| FLAC 播放有断续 | 提高 `CONFIG_SPIRAM_SPEED_80M`、确认 PSRAM 已启用;或降低 SD 卡 SPI 时钟 |
|
||||
`main/APP/files/ui_files.c` 是一个可直接复用的「列表型 App」模板:
|
||||
|
||||
1. **滑动窗口列表**
|
||||
界面创建时只建 **5 个固定行控件**(`FILES_ROWS`),刷新时把
|
||||
`[s_top, s_top + 5)` 区间映射到这些行上。目录里有几百个文件也不会撑爆
|
||||
LVGL 的 48KB 内存池;也完全避免“清空重建”带来的闪烁。
|
||||
|
||||
2. **排序规则**
|
||||
`qsort` + 自定义比较:`..` 永远第一 → 目录 → 文件,同类按名称
|
||||
`strcasecmp`(不区分大小写)。
|
||||
|
||||
3. **过滤**
|
||||
隐藏(`AM_HID`)、系统(`AM_SYS`)、卷标(`0x08`)属性一律不显示。
|
||||
单目录最多显示 `FILES_MAX_ENTRIES = 96` 条,超出部分截断。
|
||||
|
||||
4. **图标着色**
|
||||
目录 → 黄色 `LV_SYMBOL_DIRECTORY`;音频(`.wav`/`.flac`)→ 青色 `LV_SYMBOL_AUDIO`;
|
||||
图片(`.bmp/.png/.jpg/.jpeg/.gif`)→ 紫色 `LV_SYMBOL_IMAGE`;其它 → 灰色 `LV_SYMBOL_FILE`。
|
||||
|
||||
5. **大小格式化**
|
||||
`files_format_size()` 用纯整数运算输出 `128B` / `2K` / `24.1M`,
|
||||
不依赖浮点 `printf`(省 flash、避免 `%f` 在 newlib nano 下失效)。
|
||||
|
||||
6. **别名安全的路径切换**
|
||||
`files_open_dir()` 先把入参复制到 `target[]` 再写入 `s_path`,
|
||||
因此可以安全地用 `s_path` 自身作为实参调用(`files_go_up()` 就是这么做的)。
|
||||
打开失败会自动回退到原目录并恢复原来的选中项。
|
||||
|
||||
7. **SD 卡挂载策略**
|
||||
先 `f_opendir("0:")` **探测**;只有探测失败且从未尝试过时才调用 `sd_spi_init()`。
|
||||
正常开机时播放器任务已经挂好卡,这里只读探测,绝不和播放器抢挂载
|
||||
(否则正在播放的文件句柄会失效)。
|
||||
|
||||
> FatFs 配置为 `FF_FS_REENTRANT = 1`,因此播放器任务与 LVGL 任务同时访问 SD 卡是安全的。
|
||||
|
||||
---
|
||||
|
||||
## 9. 扩展建议
|
||||
## 9. 常见问题
|
||||
|
||||
### 9.1 新增一个应用(推荐流程)
|
||||
| 现象 | 排查方向 |
|
||||
| :--- | :--- |
|
||||
| 屏幕全白 / 全黑 | 检查 XL9555 是否初始化成功、背光 `LCD_BL` 是否置 1;确认 PSRAM 配置与板子一致 |
|
||||
| 屏幕花屏 | 确认 SPI 时钟 60MHz 未超频,排线接触良好;检查字节交换逻辑 |
|
||||
| 显示 `NO SD CARD` | SD 卡需 **FAT32**(不是 exFAT);`my_spi_init()` 必须在 `spilcd_init()` 之前;检查 CS=GPIO2 |
|
||||
| 显示 `PUT WAV/FLAC IN /MUSIC` | SD 卡根目录需有 `MUSIC` 文件夹且**直接**包含 `.wav` / `.flac`(不递归子目录) |
|
||||
| **FLAC 文件不显示 / 不能播放** | V1.5 已修复 `name_is_audio()` 提前 `return false` 导致 `.flac` 全被过滤的 bug。若仍异常,确认扩展名确实是 `.flac`、文件是标准 `fLaC` 或 Ogg FLAC,并查看串口 `flac_dec` 前缀日志 |
|
||||
| 显示 `UNSUPPORTED FILE` | WAV 必须是 PCM(16/24/32bit);APE / MP3 / M4A 需要额外解码库 |
|
||||
| 界面显示 `24->32bit` 之类 | 这是正常提示:源位深与实际 I2S 输出位宽不同(如 20bit 源按 24bit 输出) |
|
||||
| 24bit 曲目无声 / 报错 | 检查 `mclk_multiple` 是否为 3 的倍数;ESP-IDF 要求 24bit slot 必须满足该条件,`myi2s.c` 已自动设 384 |
|
||||
| FLAC 播放有断续 | 提高 `CONFIG_SPIRAM_SPEED_80M`、确认 PSRAM 已启用;或降低 SD 卡 SPI 时钟 |
|
||||
| 文件管理器看不到某些文件 | 隐藏 / 系统属性的文件被有意过滤;单目录超过 96 条会被截断 |
|
||||
| 文件管理器中文名显示 `?` | 预期行为:GBK 文件名无法用内置字体渲染,功能不受影响 |
|
||||
| 编译报 `'LIST_H' redefined` | 自定义宏名与 FreeRTOS `list.h` 的 include guard 冲突(`-Werror`),改名即可 |
|
||||
| 编译报 `'AM_VOL' undeclared` | 本版 FatFs 未定义该宏,用字面量 `0x08` 代替 |
|
||||
| 编译报 `-Werror=format-truncation` | `FILINFO::fname` 是 `char[256]`,拷入更小缓冲时改用 `strlcpy` 或显式长度校验 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 扩展建议
|
||||
|
||||
### 10.1 新增一个应用(推荐流程)
|
||||
|
||||
工程已经把「界面」「按键」和「应用」解耦,新增应用只需要 4 步,并且**独立文件夹**:
|
||||
|
||||
1. **建立文件夹**:例如 `main/APP/clock/`,放入 `ui_clock.c` / `ui_clock.h`,
|
||||
参考 `player/ui_player.c` 实现 4 个函数:
|
||||
1. **建立文件夹**:例如 `main/APP/clock/`,放入 `ui_clock.c` / `ui_clock.h`,
|
||||
参考 `player/ui_player.c` 或 `files/ui_files.c` 实现 4 个函数:
|
||||
|
||||
```c
|
||||
void ui_clock_create(void); /* 创建屏幕(lv_obj_create(NULL)) */
|
||||
@@ -297,6 +460,14 @@ audio_source_read┤ ├─▶
|
||||
void ui_clock_handle_key(uint8_t key); /* 只处理 K1/K2/K3 */
|
||||
```
|
||||
|
||||
如需 K0 短按在 App 内“就地返回”(像文件管理器那样逐级退出),再额外实现:
|
||||
|
||||
```c
|
||||
bool ui_clock_back(void); /* true = 已就地处理,ui.c 不再回主界面 */
|
||||
```
|
||||
|
||||
并在 `main/APP/ui.c` 的 `ui_dispatch()` 里加一个 `else if` 分支。
|
||||
|
||||
2. **加模式**:在 `main/APP/app.h` 的 `app_mode_t` 中,于 `APP_MODE_NUM` 之前加一项,
|
||||
例如 `APP_MODE_CLOCK`。
|
||||
|
||||
@@ -308,28 +479,30 @@ audio_source_read┤ ├─▶
|
||||
.title = "CLOCK", /* 图标下方名称(建议 ≤ 8 字符) */
|
||||
.subtitle = "Digital clock", /* 选中时下方显示的说明 */
|
||||
.mode = APP_MODE_CLOCK, /* 进入后切到的模式 */
|
||||
.enabled = true, /* 允许进入(false 时半透明显示) */
|
||||
.enabled = true, /* 允许进入(false 时显示为禁用) */
|
||||
.color_from = 0x4FC3F7, /* 图标渐变起始色 */
|
||||
.color_to = 0x2E86DE, /* 图标渐变结束色 */
|
||||
},
|
||||
```
|
||||
|
||||
4. **挂路由**:在 `main/APP/ui.c` 的 `ui_load_mode()` 与 `ui_tick_cb()` 里各加一个
|
||||
`case APP_MODE_CLOCK:` 分支;同时把 `main/CMakeLists.txt` 的 `SRC_DIRS` /
|
||||
`INCLUDE_DIRS` 加上 `"APP/clock"`。
|
||||
4. **挂路由**:在 `main/APP/ui.c` 的 `ui_load_mode()`、`ui_dispatch()`、
|
||||
`ui_tick_cb()` 里各加一个 `case APP_MODE_CLOCK:` 分支;
|
||||
同时把 `main/CMakeLists.txt` 的 `SRCS` 加上 `"APP/clock/ui_clock.c"`、
|
||||
`INCLUDE_DIRS` 加上 `"APP/clock"`(本工程用的是显式文件列表,不是 `SRC_DIRS`)。
|
||||
|
||||
返回主界面、切换应用是**全局自动生效**的(`ui.c` 里统一处理 K0),新 App 不需要关心。
|
||||
|
||||
### 9.2 其他扩展
|
||||
### 10.2 其他扩展
|
||||
|
||||
> 注意:主界面按 iPhone 风格 **一排 4 个图标**,超过 4 个应用会自动分页,
|
||||
> 主界面按 iPhone 风格 **一排 4 个图标**,超过 4 个应用会自动分页,
|
||||
> 屏幕下方出现页码圆点;K1/K2 可在页间连续移动。
|
||||
|
||||
* **中文界面**:在 `menuconfig → Component config → LVGL → Font usage` 中启用
|
||||
`LV_FONT_SIMSUN_16_CJK`(内置 1000 常用汉字),或使用
|
||||
[lv_font_conv](https://github.com/lvgl/lv_font_conv) 生成子集字体,然后把
|
||||
`ui_home.c` / `ui_player.c` 中的英文文案替换为中文。
|
||||
* **图标换成图片**:当前图标是「圆角渐变 + 内置字形」,如需真实图标,
|
||||
各界面里的英文文案替换为中文。文件名同理——启用中文字体后,
|
||||
`make_display_name()` / `files_display_name()` 里的 `?` 折叠逻辑就可以去掉。
|
||||
* **图标换成图片**:当前图标是「圆角渐变 + 内置字形」,如需真实图标,
|
||||
可用 `LV_IMG_DECLARE()` + `lv_img_create()` 替代 `s_glyph[]` 处的标签
|
||||
(图片需要用 LVGL 图片转换工具转成 C 数组,或开启 `LV_USE_PNG` + 文件系统驱动)。
|
||||
* **再增加一种格式(如 MP3 / APE)**:引入对应解码库后,只需在
|
||||
@@ -344,13 +517,18 @@ audio_source_read┤ ├─▶
|
||||
}
|
||||
```
|
||||
|
||||
注意 MP3 等有损格式位深固定,`out_bytes` 填 2(16bit)即可,
|
||||
`audio_set_format()` 会自动把 ES8388 / I2S 配成 16bit。
|
||||
* **专辑封面**:LVGL 支持从文件系统读取 PNG/JPG(需开启 `LV_USE_PNG` / `LV_USE_SJPG`
|
||||
+ 文件系统驱动),可在播放器左侧封面卡片显示 `/MUSIC` 中的同名图片。
|
||||
* **歌词显示**:解析同名 `.lrc` 文件,在进度条下方增加滚动歌词控件。
|
||||
* **文件管理器直接播放**:在 `files_enter()` 里判断选中项是音频文件时,
|
||||
调用 `player_load_path()`(需在 `player.h` 新增该接口)+ `app_set_mode(APP_MODE_PLAYER)`,
|
||||
即可实现「浏览目录 → 点开即播」。
|
||||
|
||||
---
|
||||
|
||||
## 10. 参考例程
|
||||
## 11. 参考例程
|
||||
|
||||
* `10_spilcd` —— SPI LCD 初始化、引脚定义
|
||||
* `30_music` —— ES8388 / I2S / WAV 播放 / XL9555 按键
|
||||
@@ -362,4 +540,4 @@ audio_source_read┤ ├─▶
|
||||
* [esphome/micro-flac](https://github.com/esphome-libs/micro-flac) —— FLAC 解码(Apache-2.0)
|
||||
|
||||
驱动代码从上述例程移植;LVGL 移植层、应用管理器、界面路由、图标式主界面、
|
||||
统一音源层(WAV/FLAC)、播放器状态机与按键交互为本工程重新实现。
|
||||
统一音源层(WAV/FLAC + 原生位深)、文件管理器、播放器状态机与按键交互为本工程重新实现。
|
||||
Reference in new issue
Block a user