在嵌入式开发领域,32KB RAM 能做什么?通常不过是几百行 C 代码的栈空间,或者几帧音频缓冲。然而有开发者将 1993 年的经典游戏 Doom 移植到了 Pinebuds Pro 无线耳塞上,更进一步将其接入互联网,允许远程玩家通过浏览器排队操控这台「耳塞主机」。这个名为 DOOMBuds 的项目不仅是对经典游戏的致敬,更是一场关于极致资源约束下工程取舍的技术实践。
硬件约束:寸土寸金的嵌入式困境
Pinebuds Pro 是少数支持开源固件的无线耳塞平台,这为在其上运行复杂应用提供了可能性。然而,硬件本身的限制极为严苛。设备的 UART 串口带宽为 2.4Mbps,这是与外部世界通信的唯一高速通道。Doom 的内部帧缓冲分辨率为 320×200 像素,采用 8 位色深,单帧数据量达 64KB。若直接传输原始帧数据,在 2.4Mbps 带宽下理论最大帧率仅为 3FPS,这一帧率对于可流畅运行的游戏而言远远不够。
CPU 层面同样构成瓶颈。设备默认运行在 100MHz 的 Cortex-M4F 处理器上,虽然该架构具备浮点运算单元,但主频限制了计算密度。RAM 资源更为紧张:设备默认分配 768KB 内存,禁用协处理器后可扩展至 992KB。然而原版 Doom 的运行需要约 4MB 内存,这意味着必须在代码层面进行大规模内存优化。Flash 存储方面,设备提供 4MB 空间,而标准 Doom 共享版 WAD 资源文件高达 4.2MB,超出了存储容量上限。
架构设计:四层分离的传输链路
为解决硬件限制,项目采用了四层架构设计,将计算密集型任务分散到不同平台。耳塞端运行 Doom 移植固件,负责游戏逻辑渲染与帧生成;串口服务器作为桥梁,接收耳塞数据并转码为 MJPEG 视频流;Web 服务器管理玩家队列、转发按键指令并传输 MJPEG 流;浏览器端静态页面负责显示视频并收集用户输入。
这种分离设计的核心考量在于资源差异化利用。耳塞的 300MHz Cortex-M4F 处理器足够运行 Doom 本身,但无法高效完成视频编码。将编码后置到串口服务器,既规避了耳塞的算力瓶颈,又充分利用了 PC 端的解码能力。MJPEG 格式的选择同样经过权衡:相比 H.264 等现代视频编码,MJPEG 实现简单、无需帧间预测,单帧编码的 CPU 开销远低于动辄数十帧缓冲的帧间编码器,非常适合嵌入式场景。
内存优化:从 4MB 到 1MB 的压缩艺术
在 992KB 可用内存条件下运行需要 4MB 内存的游戏,项目采用了多维度的内存优化策略。首先是查找表的预生成与静态化:Doom 大量依赖三角函数、快速平方根等数学运算的预计算表,这些表在编译期生成后存储于 Flash,仅在需要时读取,避免了运行时的重复计算与内存占用。
其次是常量存储策略的调整。游戏中的大量只读数据 —— 纹理查找表、颜色映射、关卡数据 —— 被标记为 const 类型并存储于 Flash 存储器而非 RAM。ARM Cortex-M 架构支持直接从 Flash 读取常量数据,虽然访问速度略慢于 RAM,但换取了宝贵的内存空间。项目中进一步禁用了 Doom 原有的缓存系统,移除了动态纹理加载与释放逻辑,转而采用静态资源布局。
变量层面的优化同样关键。开发者在代码审计中移除了所有未使用的全局变量,将局部变量生命周期尽可能缩短以复用栈空间,并使用位域操作压缩数据结构尺寸。这些改动累积节省了数百 KB 内存,使 Doom 得以在不足 1MB 的 RAM 中运行。
带宽优化:MJPEG 编码与帧率权衡
串口带宽是整个系统的数据传输瓶颈。2.4Mbps 的理论带宽在扣除协议开销后,可用带宽约为 2.2Mbps。若传输 8 位色深的原始帧缓冲,即使按 320×200 的分辨率,理论最大帧率也仅约 3FPS。引入 JPEG 编码后,单帧压缩至平均 11KB 至 13.5KB,理论帧率提升至 22FPS 至 27FPS。
实际运行中,CPU 的 JPEG 编码能力成为新瓶颈。设备将主频提升至 300MHz 并关闭低功耗模式后,编码性能仍不足以达到理论上限,最终稳定在约 18FPS。这一帧率虽不及桌面游戏的 60FPS 标准,但对于一款在耳塞上运行、通过串口传输的远程游戏而言已属可观。
项目选用了 bitbank2/JPEGENC 作为嵌入式 JPEG 编码器,该库专为资源受限设备设计,内存占用极低且不依赖外部库。编码器接收 8 位灰度或索引色图像,输出标准 JPEG 数据流,兼容性良好且解码端无需特殊处理。
存储困境:资源文件的裁剪艺术
Flash 存储的 4MB 容量限制与 Doom 4.2MB 资源文件的冲突,迫使项目寻找替代资源集。开发者最终选用了 fragglet 维护的 Squashware 项目,这是一个针对嵌入式平台裁剪的 Doom 共享版 WAD 文件,体积仅为 1.7MB,移除了大量关卡变体、多语言资源与冗余资产,同时保留了完整的游戏引擎与核心关卡内容。
Squashware 的选择体现了嵌入式开发的典型权衡:保留核心功能的同时大幅削减存储占用。1.7MB 的体积不仅满足 4MB Flash 的容量限制,还为固件本身预留了充足空间。资源文件的精简也间接降低了加载时的内存压力 —— 运行时无需缓存完整资源,可在需要时按需读取。
远程操控:队列与流媒体的工程妥协
互联网远程操控的设计引入了额外的工程挑战。浏览器端需要实时显示耳塞渲染的游戏画面,同时将用户按键指令回传至服务器。传统方案是建立双向 WebSocket 通道传输视频流与控制指令,但上行带宽成本可能随玩家数量线性增长。
项目的优化策略是引入 Twitch 视频流作为后备:当玩家排队位置超过 5 名时,前端播放器自动切换至低延迟 MJPEG 流,避免服务器持续推送视频数据。MJPEG 流本质是连续的 JPEG 帧序列,HTTP 长连接即可传输,无需复杂的 WebSocket 协议栈。这一设计将服务器的视频推送负载与玩家数量解耦,显著降低了带宽成本。
队列管理本身在 Web 服务器层实现,采用先来先服务的公平调度策略。服务器维护玩家状态机,跟踪连接状态与排队顺序,确保每位玩家获得公平的游戏时间。串口服务器同时承担转码职责,将耳塞输出的 MJPEG 流转发给 Web 服务器,再由后者分发给对应玩家。
工程启示:约束驱动的设计哲学
DOOMBuds 项目为嵌入式开发提供了若干可迁移的工程启示。首先是硬件能力的重新评估:提升主频、禁用低功耗模式、释放协处理器内存 —— 这些平台层面的配置调整往往能释放显著性能空间,且改动成本低于软件优化。其次是协议与格式的选择:MJPEG 相比原始帧缓冲节省带宽,相比 H.264 降低编解码复杂度,在特定场景下是更优选择。
资源裁剪贯穿项目始终。从 4MB 到 1.7MB 的资源文件精简,从 4MB 到 992KB 的内存需求压缩,每一次削减都伴随着功能与体验的权衡。嵌入式开发的本质不是追求极致性能,而是在给定约束下找到最优解集。项目的成功不在于运行得有多快,而在于在所有硬性约束内实现了可运行、可交互、可远程访问的完整系统。
最后是架构分离的价值。将计算密集型任务从资源受限设备迁移到能力更强的服务端,是嵌入式系统接入互联网的通用范式。耳塞负责渲染与编码,服务器负责转码与分发,浏览器负责展示与交互 —— 各层各司其职,共同构成端到端的远程游戏体验。这种分层设计不仅解决了硬件瓶颈,也为后续功能扩展提供了清晰的演进路径。
参考资料
- DOOMBuds 项目主页:https://doombuds.com/
- DOOMBuds GitHub 仓库:https://github.com/arin-s/DOOMBuds
- Squashware 精简资源集:https://github.com/fragglet/squashware