跳至内容
返回

在 M5Stack Cardputer 上实现远程桌面串流(基于 H.264)

发布于:  at  06:55 上午

背景

几年前,我曾试过在 ESP32 上实现串流操作。 当时硬件搭建在面包板上,一快 1.14 寸的屏幕分辨率为 240x135,主控是初代 ESP32。 文章较为详细地描述了实现细节。

时隔五年,我决定重新制作一次,将硬件环境迁移到 M5Stack 家的 Cardputer。

M5Stack Cardputer H264 Streaming

额外说明, 关于Cardputer PSRAM升级

我的这块 Cardputer 被我手动升级过一次。原版主控使用的是 ESP32-S3FN8(内置 8MB Flash,无 PSRAM),我将其更换成了 ESP32-S3FH4R2。 牺牲了 4MB Flash,换来了 2MB 的 PSRAM。

Manual replacement of ESP32-S3 chip on M5Stack Cardputer using heat gun and aluminum foil tape

上图是更换芯片后的特写。为了保护周围精细的贴片元件,我动用了铝箔胶带进行隔热。可以看到焊点周围还有一些未清理干净的助焊剂残留,虽然卖相一般,但它确实赋予了这台小机器“新生”。

无 PSRAM 的“软压榨”实验

在决定更换芯片之前,我曾深度尝试过在 无 PSRAM 的原版 Cardputer 上跑通 H.264 解码,但这几乎是一次“不可能完成的任务”。

H.264 软件解码器对内存有刚性需求。即便在 240x136 这种极低分辨率下,DPB (Decoded Picture Buffer) 机制加上 SPS 序列参数集的激活,依然需要大量连续的、无碎片的内存块。而 ESP32-S3 的内部 RAM (SRAM) 在运行了 WiFi 协议栈、TCP/IP、WebSocket 服务之后,留给应用的连续空闲空间所剩无几。

为了省下内存,我曾尝试了以下极致操作:

然而,即便通过这些手段腾出了超过 100KB 的额外空间,esp-h264 库在初始化时依然会因为无法申请到足够的连续工作内存而报错。 结论: 对于 H.264 这种对参考帧有依赖的编码格式,PSRAM 是 ESP32-S3 设备的“续命药”。如果你也想玩实时视频流,手动焊接一颗带 PSRAM 的芯片是最高效的解决方案。

架构设计

由于有了额外的 PSRAM,我甚至可以直接在装置上进行 H.264 流的解码。 乐鑫官方提供了一个名为 esp_h264 的组件,能够在 ESP32-S3 上执行软件解码。据实际测试,2MB 的 PSRAM 刚好足以支撑 240x136 分辨率的解码工作(画面原本是 240x135,但 H.264 编码器要求分辨率必须是偶数)。

系统链路

为了保证极低的延迟,我重新设计了整个通信架构:

  1. 发送端 (Browser): 利用浏览器的 getDisplayMedia 捕获桌面,调用 WebCodecs 进行硬编码,输出 H.264 Annex B 格式流(设置为 GOP=1,即全 I 帧模式),最后通过 WebSocket 发送二进制数据。
  2. 接收与缓冲 (ESP32-S3): Cardputer 作为 WebSocket Server 接收数据,并存入 RingBuffer (环形缓冲区) 进行平滑。
  3. 解码核心 (Decode Pipeline): 专门的解码任务从 RingBuffer 取出原始字节流,通过 esp_h264 解码出 YUV420P 格式的图像。
  4. 颜色转换与显示: 解码后的图像实时转换为 RGB565 格式,随后通过 SPI DMA 直接写入 ST7789 屏幕。

核心挑战与解决方案

1. 多核调度与 WDT (看门狗) 报错

在最初的版本中,解码、颜色转换、屏幕刷新和 LVGL UI 更新都在同一个核心上运行,导致 CPU 占用率瞬间爆表,频繁触发 Task Watchdog 系统复位。 解决方案

Cardputer Streaming Menu UI

2. 内存管理

esp_h264 软件解码器需要大量的连续内存块。即便开启了 GOP=1,解码器依然会申请一定的空间用于参考帧管理。 我将解码器的所有的工作缓冲区都分配到了 PSRAM 中,而对实时性要求极高的双显示缓冲区与 SPI 描述符则保留在速度更快的 Internal RAM

3. “跳帧追赶”算法解决延迟

网络波动是串流的大敌。如果数据由于瞬时带宽不足积压在缓冲区,显示的画面就会产生越来越大的延迟。 由于采用的是全 I 帧串流,我设计了一个简单的“追赶算法”: 解码任务在处理数据前,会先探测 RingBuffer 的积压情况。如果发现积攒了多个待解码帧,则直接丢弃所有旧帧,只解码并显示最后一帧。这个技巧让设备在网络恢复后能瞬间重回实时画面。

上位机设计

这次我没有使用 Python,而是直接在 Cardputer 上启动了一个精简的 Web Server 提供前端控制台。前端采用了 DaisyUI 构建界面,能够实时显示当前发送的比特率和流量统计。 为了适配 ESP32-S3 的算力,我将 WebCodecs 的编码配置调整为:

总结

从最初的 WDT 频繁崩溃,到如今能够实现稳定的 240x135 @ 15-20 FPS 串流,这个项目探索了 ESP32-S3 的极限负载能力。 相比于五年前的面包板方案,现在的 Cardputer 串流方案不仅更加完整(无需 Python 脚本作为桥接),而且在多核优化和内存分配上有了质的提升。即便在微控制器上,也能玩出非常极致的流媒体体验。


硬件建议:必须使用带有 PSRAM 的 ESP32-S3 型号。


在以下平台分享此文章: