用 EC600M 做了个 "能在线流媒体" 的便携音乐播放器

最近用 QuecPython(MicroPython)在移远 EC600M-CNLE 上做了个便携音乐播放器,纯 Python 写的,没碰 C。功能比较普通但做完挺有成就感:本地 Flash 曲库播放 + 从 Gitee 公开仓库边下边播不落盘,支持暂停 / 断点续播 / 进度条 / 自动切歌。

先发个开篇介绍一下硬件和整体设计,后面两篇会分别讲核心架构和踩过的坑。

硬件清单(极简单)

部件 型号 / 接法 说明
主控 Quectel EC600M-CNLE 4G Cat.1 模组,自带 QuecPython 固件
屏 ST7789 240×240,LCM 接口 13MHz,4 线 SPI,LVGL 渲染
功放 NS4160 单端输入,PA_GPIO=9 使能
按键 2 颗,ExtInt 下降沿 选择键 GPIO13 / 确认键 GPIO12,50ms 硬件消抖
音频 板载 DAC + 外接喇叭 audio.Audio 驱动
存储 模组自带 Flash,曲库放 /usr/music 在线歌不落盘

整个交互就靠两颗按键:SELECT 切焦点,CONFIRM 执行。主界面 7 个焦点位(上一首 / 播放暂停 / 下一首 / 曲库 / 音量 -/ 音量 +/ 模式),曲库页 4 个焦点位。圆屏 240×240 配合 LVGL,做出来挺有小玩具那味儿。

最终效果

  • 本地歌直接扫 /usr/music,扩展名 + 文件头魔数双重校验(防止把改名的假文件放进去)

  • 在线歌不用手动填 URL,开机自动扫 Gitee 公开仓库目录,mp3/amr/wav 自动拼成直链

  • 4G 联网失败自动降级纯本地,开不了机这种事不会发生

  • 进度条、时长、暂停恢复、自动切下一首都有

  • 上传新歌到 Gitee 后,打开曲库 1 秒内原地刷新出来,不用重启设备

整体架构

一句话总结:UI 只渲染,状态机是唯一真状态;所有会阻塞的网络请求全部丢后台线程,后台线程绝不碰 LVGL,只写缓存字典,主循环 50ms tick 回来消费。

┌─────────────────────────────────────────────────┐

│ 主循环 (50ms tick) │

│ ├─ player.tick() ← 唯一真状态改变者 │

│ ├─ poll_online_refresh()← 消费后台线程的产物 │

│ ├─ inp.consume_events() ← 按键事件 │

│ └─ ui.refresh(state) ← 唯一碰 LVGL 的地方 │

└──────┬───────────────┬───────────────┬──────────┘

│ │ │

┌───▼────┐ ┌───────▼──────┐ ┌───▼────────┐

│播放worker│ │online子线程 │ │Gitee扫描线程│

│(切歌) │ │(HTTP下载+喂流)│ │(列目录API) │

└───┬────┘ └───────┬──────┘ └───┬────────┘

│ │ │

└──── 只写缓存字典 / 事件队列 ───┘

绝不碰 LVGL

几个关键约定:

  1. LVGL 只在主线程碰。任何后台线程改 UI,轻则花屏重则硬 fault。

  2. MicroPython 是单 GIL,纯 CPU 密集循环会饿死主循环 —— 所以所有 “扫描 / 喂流” 都必须做 “只扫一次 / 分块 / 水位停手”。网络 request.get 在等待时会让出 GIL,所以挪后台真的有效。

  3. 每个后台任务带防重标志,避免重复发请求。

下一篇讲这个状态机具体怎么设计的,特别是 QuecPython playStream 那个反直觉的坑 —— 它每喂 4KB 数据就吐一对假 START/FINISH 事件,把我进度条搞疯了好久。

感谢分享,EC600M-CNLE + QuecPython + LVGL 这套组合做便携播放器,架构思路很清晰,几点补充供参考:

架构层面

  • "LVGL 只在主线程碰、后台线程只写缓存"这一约定完全正确。QuecPython 下 LVGL 非线程安全,跨线程刷新易花屏或硬 fault,你这个分层是稳妥做法。
  • 主循环 50ms tick 消费事件队列 + 后台分块喂数据,符合单 GIL 下的调度原则,避免纯 CPU 循环饿死 UI。

在线流媒体几点建议

  • 边下边播建议明确缓冲水位(高低水位 + 停手/续喂阈值),4G 抖动时能显著减少卡顿与断流。
  • HTTPS 拉流注意 TLS 握手的瞬时内存峰值,EC600M 内存有限,建议复用连接、避免并发多路请求。
  • 网络异常建议区分 DNS 失败 / 连接超时 / 读超时,分别做重试与降级,你已有的"联网失败自动降级纯本地"是好设计。
  • Gitee 目录扫描做原地刷新时,注意加防重标志与结果落地时机,避免与正在播放的任务抢资源。

关于 playStream 事件
你提到的回调事件语义问题,具体行为请以移远官方 QuecPython audio 模块文档为准,不建议依赖未公开的时序假设做进度计算,稳妥做法是用自身喂流字节数或时间戳独立维护播放进度。

期待后续核心架构与踩坑篇。

本回答仅供参考。若问题已解决,可标记为 :white_check_mark:解决方案 ;若仍有疑问,请等待官方技术人员回复,最终请以专业解答为准。