# MP3Player **Repository Path**: beginnerdev/mp3-player ## Basic Information - **Project Name**: MP3Player - **Description**: MP3播放器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-12 - **Last Updated**: 2026-10-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # MP3Player ESP-IDF v6.1 项目,适配 ESP32-S3-DevKitC-N16R8:16 MB Flash、8 MB Octal PSRAM。 当前实现 SD 卡根目录文件列表和 OLED 四行显示,使用旋转编码器逐项选择,超出当前页时自动翻页。 支持 MP3、WAV、FLAC 软件解码及 PCM5102A I²S 音频输出。 ## 接线 | 外设 | 信号 | ESP32-S3 | | --- | --- | --- | | SH1106 128×64 OLED(I²C) | SDA | GPIO16 | | OLED | SCL | GPIO15 | | MicroSD(SPI) | CS | GPIO10 | | MicroSD | SCK | GPIO11 | | MicroSD | MOSI | GPIO12 | | MicroSD | MISO | GPIO13 | | 编码器 | PSH(按压,暂不使用) | GPIO7,功能停用 | | 独立确定按钮 | 一端 / 另一端 | GPIO17 / GND | | 编码器 | TRA(A) | GPIO6 | | 编码器 | TRB(B) | GPIO5 | | 返回键 | BACK | GPIO4 | | PCM5102A | BCK | GPIO18 | | PCM5102A | LCK / LRCK | GPIO3 | | PCM5102A | DIN | GPIO8 | | PCM5102A | SCK | GND | | PCM5102A | VIN | 5V | | PCM5102A | GND | GND | 所有外设共地。裸编码器公共端、返回键另一端接 GND;带板编码器模块使用 3.3V 供电。 OLED 和 SD 适配器按各自规格供电,连接到 ESP32 的信号须为 3.3V 电平。 OLED 内部上拉已启用;若模块无板载上拉,可为 SDA、SCL 各加约 4.7 kΩ 到 3.3V。 SD 卡需要模块提供适当的上拉,SPI 接线尽量短。 PCM5102A 按商家说明使用 5V 供电,A3V3 留空,H1低、H2低、H3高、H4低。 3.5mm 输出接有源音箱 AUX 或外部功放。 ## 文件列表 - 上电挂载 FAT 格式 SD 卡到 `/sdcard`,使用 SPI2、4 MHz;不格式化、不写入测试文件,卡保持挂载。 - 读取根目录中的所有普通文件,按文件名排序;不递归进入文件夹,不限定 MP3 扩展名。 - 一屏最多 4 行,每行 16 像素;最后一页不足 4 行时清除空白行。 - 默认选中第一个文件,选中行使用白底黑字。正向旋转选择下一项,反向选择上一项;越过第 4/5 项等边界时自动翻页。到第一个/最后一个文件后停止,右侧滚动条表示当前页。 - 列表页返回键回到图形主菜单。按下 确定键 播放当前选中的音频文件 并进入播放页。 - 保留编码器四次有效信号变化计一次的规则、1 ms 采样和按键 20 ms 消抖。 - 如旋转方向与期望相反,将 `main/main.c` 中的 `SELECTION_DIRECTION` 从 `1` 改为 `-1`。 启用长文件名和 UTF-8,使用 12 像素文泉驿 GB2312 字体,支持英文及常用中文。 不在内置字库中的字符显示为 `?`。未选中项的长名称截短并加 `...`;选中项超过显示宽度时,停留约 0.8 秒后按每 80 毫秒 2 像素循环横向滚动,文件名之间留 24 像素空隙,每轮回到开头后再次停留。短名称不滚动。 切换选中项时滚动从开头重新开始,滚动仅在该行内绘制。串口 `BROWSER` 日志打印选中序号、文件总数、页码和选中项的完整文件名。 字库和 U8g2 必要源码已放入 `components/u8g2`,编译无需另外安装 Arduino 库。 没有文件时显示 `No files`;SD 卡失败显示 `SD card error`;目录读取失败显示 `File list error`。 卡内容在启动时扫描一次,修改文件后复位刷新。请使用 FAT32 卡测试,当前未启用 exFAT。 ## MP3 播放 按下确定键后,后台播放任务从 SD 卡分块读取和解码文件,I²S 输出至 PCM5102A: BCK=GPIO18、WS/LCK=GPIO3、DOUT/DIN=GPIO8,不输出 MCLK(模块 SCK 接 GND)。 采用标准 Philips I²S,32 位立体声时隙,每音频帧 64 个 BCK;16 位解码样本放在 32 位输出字的高位。 单声道复制到左右声道。8/11.025/12 kHz 文件采用重复采样升到两倍输出率,以适配模块的三线时钟模式;不做高质量重采样。 初始音量为 PCM 幅度的 25%,播放页通过编码器调节,范围 0~100;音量变化使用采样级渐变。 播放页显示播放状态、歌曲名、已播放时间/总时长和进度条,长歌曲名循环滚动。 时间由提交的 PCM 样本累计,未单独补偿编码器延迟和音频 DMA 队列延迟。 歌曲正常结束后自动切换下一曲;测试音结束仍显示“播放结束”。 **播放页按下 BACK:返回列表并保留选中项,歌曲继续播放,暂停状态也保持不变。** 列表中正在播放的歌曲左侧显示三角形,暂停时显示双竖线;播放结束或失败后标识自动消失。 选中当前播放的歌曲按确定键,回到播放页,不从头播放;选中其他音频文件 按确定键,切换到该歌曲。 选择滚动重新从文件名开头开始,可以旋转选择其他文件,并按确定键切换。 播放页按确定键切换暂停/继续,编码器每格调整 5 点音量,范围 0~100,默认 25。 音量在当前开机期间保留;第一行显示播放/暂停图标、循环滚动的歌曲名和 `Vol:25`;第二行显示已播放时间、进度条和总时长。 暂停时保持 I²S 时钟并输出静音,暂停位置与播放计时保持不变;BACK 只切换回列表。 确认键双击切换下一曲,BACK 双击切换上一曲;列表页和播放页都有效。 双击以两次松开间隔不超过 300 毫秒判定,双击不会触发单击功能;单击在松开约 300 毫秒后执行。 歌曲正常播放结束后自动下一曲,按原文件名排序跳过非支持音频格式,最后一曲回到第一曲。 只有一曲时循环该曲;播放失败时停在错误状态,不自动循环错误文件。 切换后开始播放新曲,即使上一曲处于暂停;保持当前音量。 列表页自动切歌保留正在浏览的选中项,左侧标识随当前曲目更新。当前不支持快进。 播放时间显示为“已播放时间 / 总时长”,不显示百分比,进度条按时间计算。 打开歌曲时优先读取 ID3 的 TLEN/TLE 毫秒时长;解码首帧时读取 Xing/Info/VBRI 的总帧数,有有效帧数时优先采用文件头时长。 只有缺少有效时长信息时,才在音频缓冲填充后启动低优先级后台扫描,支持变码率文件。 计算期间总时长显示 `--:--`,完成后自动更新;切换歌曲时会取消旧歌曲的扫描并关闭文件;返回列表时保留后台播放及扫描。 支持 `.mp3`、`.wav`、`.flac`(大小写不敏感),单声道/立体声、8~48 kHz。MP3 支持常见 CBR/VBR;WAV 支持 RIFF/WAVE PCM 与 IEEE 浮点;FLAC 支持原生 FLAC 文件,暂不支持 Ogg FLAC。WAV/FLAC 转换为 16 位 PCM 输出,24/32 位音源的额外精度不会保留。 非支持格式文件显示“请选择音频文件”;打开、读取、解码、I²S 错误会显示播放错误,BACK 仍可返回。 同一个文件途中采样率发生改变会报告错误。MP3 使用 minimp3,WAV/FLAC 使用 dr_libs,库源码与许可证已随工程保存,构建无需联网下载。 ## 构建与烧录 在 VS Code 打开本文件夹,使用 ESP-IDF 扩展构建,选择开发板串口并烧录。 终端也可以使用: ```sh source /Users/beginner/.espressif/tools/activate_idf_v6.1.sh cd /Users/beginner/Documents/MP3Player idf.py build idf.py -p <开发板串口> flash monitor ``` 准备至少 5 个根目录文件即可测试翻页,建议混合英文、中文、长文件名。 检查默认第一项反白、编码器每次选择上一项/下一项、第 4/5 项间自动翻页、首尾不越界、返回键选中第一项;选中长文件名应循环滚动,切换到短名称时停止滚动。 空卡和挂载失败也应显示相应提示。 然后选中 MP3,按确定键:应进入播放页、有源音箱出声、计时及进度递增。 播放中按 BACK 应保持声音并回到列表,歌曲左侧显示标识;选中该歌曲按确定键 应回到原播放位置。 暂停后返回列表应显示双竖线,重新进入播放页仍保持暂停;选中其他音频文件 应切换歌曲。 短文件自然结束应自动下一曲;测试末尾循环、双击确认下一曲、双击 BACK 上一曲,以及单击仍可暂停/返回。 非 MP3 或损坏文件应显示错误且可返回列表。 若有进度但无声音,检查模块 SCK 接地、H3为高、H4为低、5V 供电和音箱 AUX。 ### 无声诊断 PCM5102A 是线路输出,测试时将 3.5mm 接到有源音箱 AUX 或耳放,再由耳放连接耳机。 商家标注的“耳机接口”描述插座形状,不能据此认为模块能直接驱动普通耳机。 烧录当前版本后,在文件列表页按住 BACK,再按一下 确定键,然后松开两键,会播放 5 秒低音量 1 kHz 测试音。 该测试绕过 MP3 文件及解码器,仍需要系统先正常进入文件列表。BACK 可返回列表,测试音仍播放到结束。 若测试音可听见而 MP3 无声,查看串口 `PCM:` 日志的 peak 和 nonzero,确认文件是否解码出非零数据。 若测试音也无声,检查 VIN=5V、共地、SCK 接 GND、XSMT/H3 高电平、FMT/H4 低电平,以及 BCK18、LCK3、DIN8 接线。 仅凭进度增加或 I²S 初始化成功日志,不能确认实际输出波形正常。 ## 源码与验证 - `main/main.c`:OLED I²C 通信和浏览器主流程。 - `main/storage.c`:SD SPI 挂载。 - `main/input.c`:编码器与按键事件。 - `main/file_list.c`:目录扫描、排序、选择边界和分页。 - `main/list_view.c`:UTF-8 字符处理、四行绘制、选中反白和循环滚动。 - `main/audio_stream.c`:MP3 流式解码、标签跳过、PCM 转换。 - `main/player.c`:后台播放任务、I²S 输出、停止和错误清理。 - `main/playback_view.c`:播放状态、计时、进度条。 - `main/sd_test.c`、`main/input_test.c`:前期硬件测试参考,当前不参与编译。 - `tests/host`:本机目录、分页和实际点阵绘制测试,详见 `tests/README.md`。 显示驱动参考:[SH1106 数据手册](https://www.pololu.com/file/0J1813/SH1106.pdf)。 字体及授权说明:[U8g2 文泉驿字体](https://github.com/olikraus/u8g2/wiki/fntgrpwqy),本地来源信息见 `components/u8g2/README.md`。 音频参考:[minimp3](https://github.com/lieff/minimp3)、[PCM5102A 数据手册](https://www.ti.com/lit/ds/symlink/pcm5102a.pdf)。 ## 接线图与稳定播放 ![完整接线图](接线图.png) 当前版本批量补读 SD 数据,I²S 使用 12 个 DMA 缓冲,每个 480 个立体声帧, 44.1 kHz 时可缓冲约 131 毫秒,减少读取延迟引起的噼啪声。 实机已确认解除 XSMT 静音后可出声,扩大缓冲后 MP3 播放听感正常。 接线图的生成脚本见 `tools/draw_wiring.py`,依赖 Pillow 及 macOS 中文字体。 播放页不显示 BACK 提示,BACK 按键仍可返回列表。长文件名停留 800 毫秒后循环滚动,短文件名保持静止,每次进入播放页从名称开头开始。 ## 歌曲名显示 列表和播放页优先显示 MP3 的 ID3 歌曲名(TIT2 / TT2),没有有效歌曲名时显示原文件名。 支持 ID3v2.2、v2.3、v2.4 和 ID3v1;支持标准 UTF-8、UTF-16、Latin-1 编码。 压缩/加密的标题不解析;对于标记为 Latin-1、实际使用 GBK 的旧中文标签,会用原文件名校验候选歌曲名,匹配时转换为 UTF-8;编码有歧义且无法确认时保留标准 Latin-1 解析。长歌曲名继续循环滚动。 歌曲名只用于显示,排序、文件定位及播放标识仍对应原文件,重名歌曲不会混淆文件路径。 标签在首次读取列表时解析一次;封面通过跳转跳过,标题读取和标签遍历都有大小上限。 格式参考:[ID3v2.3](https://id3.org/id3v2.3.0)、[ID3v2.4](https://id3.org/id3v2.4.0-frames)。 GBK 转换表由 `tools/generate_gbk_table.py` 使用 Python 标准 GBK 编码生成,占用约 47 KB Flash。 GPIO17 确定按钮采用内部上拉和 20 毫秒消抖,按下接地;替代原编码器 PSH 的播放、进入播放页、暂停/继续和 BACK 组合测试音功能。PSH 接 GPIO7,暂不配置或读取。 播放/暂停使用图标表示,加载、切换和错误提示保持中文,音量按布局显示为 `Vol:35`;测试音标题也显示中文。串口保留原有诊断信息。 播放页使用两行布局,时间格式为 `0:00`,时长计算完成前显示 `--:--`;加载、切换与错误提示仍使用中文。 ## 实时音乐频谱 播放页底部显示 16 个频段柱形,使用解码后的 PCM 做 512 点 FFT,每 125 帧音频均匀计算 84 次,在 64% 的基础上提高 5%,为原逐帧计算频率的 67.2%;44.1 kHz 下约 26 次/秒,48 kHz 下约 28 次/秒。 左右声道合并频段能量,不会因反相立体声而抵消;使用窗函数、直流移除与平滑回落。 频谱随音量变化,暂停时保持当前柱形,切歌、结束或错误时清空。上方两行信息不受影响。 FFT 工作区静态分配,避免增加播放任务的栈压力;最终刷新速度受 OLED 通信耗时影响。 歌曲信息与时间行紧凑排列在屏幕顶部,频谱区域从第 33 行延伸至底部,柱形最大高度 31 像素。 ## OLED 刷新 SH1106 I²C 使用 400 kHz,仅发送每页变化的列范围;页面切换和清屏也通过同一差分机制完成。 播放页以约 33 毫秒为绘制周期,实际帧率受传输、调度及接线影响;没有变化的画面不重复发送。 名称滚动每 40 毫秒移动 1 像素,保持原来的移动速度并减小台阶。频谱计算也提高更新频率。 串口 `Fast duration (ID3 TLEN/Xing/Info/VBRI)` 表示使用快速时长信息;`Background duration` 表示使用完整扫描。TLEN 可能缺失或已过期,播放仍以文件实际结束为准。 ## 保存计算出的总时长 完整扫描完成后,时长先显示在界面;歌曲结束或切歌关闭文件后,把结果保存到歌曲自身的 ID3v2.4 TLEN 更新标签。 标签带 footer,放在音频后、原 ID3v1 前,仅增加几十字节,不搬动音频或原有封面/歌曲名。 下次优先读取此标签,重复保存同一时长不会继续增长文件。中途取消且未完成计算时不保存不完整结果。 正常播放到结尾也可用实际累计时间保存;已有有效 TLEN 或 Xing/Info/VBRI 的歌曲无需重复写入。 串口 `Duration saved to ID3` 表示保存成功;写入失败只记录警告,不阻止播放。 ## 图形主菜单 启动后默认进入主菜单,两个入口为“歌曲列表”和“设置”,配有音乐列表和齿轮图标。 旋转编码器选择,确定键进入;列表页 BACK 返回主菜单,播放页 BACK 返回列表,设置页 BACK 返回主菜单。 设置页包含“音量”“SD卡信息”“关于”三个入口,旋转编码器选择,确定键进入,子页面 BACK 返回设置菜单。 音量页旋转每格调整 5 点,范围 0~100,默认 25,当前开机期间保留。 文字页面一屏最多四行(标题计入,分割线不计入)。SD 卡信息保留固定标题,每次显示三项内容,旋转编码器上下滚动;右侧滚动条提示内容超出及当前位置。名称、总容量、可用容量和歌曲数量各占一行,标签与数值分列显示;存储卡不可用时显示未挂载提示。可用容量在启动挂载时读取并缓存,避免播放过程中扫描存储卡影响声音。 关于页显示播放器名称、作者 Beginner、当前固件版本、ESP32-S3 型号和 ESP-IDF 版本。标题固定,每次显示三项内容,旋转编码器滚动,右侧显示滚动条。 返回主菜单或进入设置不停止后台播放,歌曲正常结束仍会自动下一曲。 双击切歌仅作用于列表页与播放页;主菜单双击确定按进入处理,设置页双击 BACK 按返回处理。 SD 卡不可用时仍可进入设置;进入歌曲列表会显示存储卡提示。 WAV 与 FLAC 优先从文件头读取总时长,不写入 MP3 的 ID3 时长标签。列表暂以文件名显示 WAV/FLAC,MP3 继续读取 ID3 歌曲名。高于 48 kHz 或多于两声道的文件会提示错误。 FLAC 使用 16KB 压缩数据读取缓存和 24×480 帧 I²S DMA(44.1kHz 时约 261ms),减轻 SD 读取/整块解码耗时波动引起的断流。代价是音频缓冲占用增加约 46KB,操作听感延迟可能增加约 130ms。串口 `FLAC timing` 显示每秒最大读取/解码耗时及缓冲时长;超过缓冲时长会警告,实际噼啪声是否消除需上板确认。 `FLAC I2S: send queue overruns` 统计实际 I²S 发送队列溢出事件(中断内只计数)。它表示某些已发送缓冲的通知未及时消费,能为送音不及时提供直接证据;单独的最大解码耗时或队列计数都不能证明某次听到的噼啪的最终原因。启动、暂停或结束附近需要结合状态判断。