# 基于 IMX6ULL 的纯 CPU 渲染语音交互游戏设计与实现
—— TomGame 会说话的小猫 --- ## 摘 要 本实训针对无 GPU 或 GPU 能力极弱的嵌入式 SoC(IMX6ULL / ARM Cortex-A7),设计并实现了一款以语音交互为核心的纯 CPU 渲染 2D 游戏 **TomGame(会说话的小猫)**。游戏支持两种语音玩法:按住录音后通过变调回放实现“学说话”效果,以及基于端侧关键词识别模型 **TinyKWS** 识别“Up / On / Stop”等指令并驱动角色动作。项目采用 C++11 编写,通过 CMake 条件编译在 PC(SDL2)与 IMX6ULL(Framebuffer / ALSA / evdev)之间共享同一套应用代码;底层渲染使用 RGB565 帧缓冲与 RGBA5551 精灵图集,运行时不依赖 PNG/TTF 解码。 本文重点阐述 TomGame 应用层的状态机、语音交互控制器、音频录制/变声/回放链路,以及嵌入式 TinyKWS 关键词识别模型的特征提取、网络结构与端侧推理实现;对底层 SDL2 / Framebuffer 显示适配、音频抽象与定时器只做必要概述。实验结果表明,TomGame 在 PC 端可稳定运行 60 FPS,在 IMX6ULL 板端以 1024×600 分辨率、30 FPS 目标帧率流畅运行,语音指令平均响应延迟满足实时交互需求,验证了“纯 CPU 渲染 + 端侧语音模型”在资源受限嵌入式设备上构建趣味交互游戏的可行性。 **关键词:** IMX6ULL;纯 CPU 渲染;语音交互;关键词识别;TinyKWS;MFCC;SDL2;Framebuffer --- ## ABSTRACT This training project designs and implements **TomGame**, a pure-CPU rendered 2D voice-interactive game on the IMX6ULL (ARM Cortex-A7) platform which has no GPU or only extremely limited graphics acceleration. The game supports two voice modes: a pitch-shifted “talkback” echo and a TinyKWS on-device keyword recognizer that maps commands such as “Up / On / Stop” to in-game actions. Written in C++11 and built with CMake, the project shares the same application code between PC (SDL2) and IMX6ULL (Framebuffer / ALSA / evdev) through conditional compilation. The renderer uses an RGB565 framebuffer and RGBA5551 sprite atlases, with all external resources converted offline so that no PNG/TTF decoding happens at runtime. This report focuses on the TomGame application layer: the game-state machine, voice interaction controller, audio record / effect / playback pipeline, and the embedded TinyKWS keyword-spotting model including feature extraction, network architecture, and on-device inference. The underlying SDL2 / Framebuffer display adaptation, audio abstraction, and timer are only summarized where necessary. Experimental results show that TomGame runs at a stable 60 FPS on PC and at 1024×600 @ 30 FPS on the IMX6ULL board, with keyword-command latency meeting real-time interaction requirements. This verifies the feasibility of combining pure-CPU rendering with an on-device speech model to build engaging interactive games on resource-constrained embedded devices. **Keywords:** IMX6ULL; pure-CPU rendering; voice interaction; keyword spotting; TinyKWS; MFCC; SDL2; Framebuffer --- ## 目 录 1 绪论 1.1 课题背景与研究意义 1.1.1 嵌入式游戏与语音交互的发展背景 1.1.2 在资源受限平台运行图形游戏的必要性 1.2 国内外研究现状 1.2.1 嵌入式图形渲染技术研究现状 1.2.2 端侧语音关键词识别技术研究现状 1.3 本文主要研究内容 2 系统总体方案与相关技术 2.1 系统需求分析 2.1.1 功能需求 2.1.2 性能需求 2.1.3 跨平台开发与部署需求 2.2 系统总体架构设计 2.2.1 Core / Apps 分层架构 2.2.2 平台抽象层设计 2.2.3 资源离线转换流程 2.3 关键开发平台与技术 2.3.1 IMX6ULL 开发板硬件平台 2.3.2 ARM Linux 与交叉编译环境 2.3.3 SDL2 / Framebuffer 双显示后端 2.3.4 C++11 与 CMake 构建系统 2.3.5 TinyKWS 关键词识别模型 2.3.6 音频采集与回放技术 3 硬件平台与运行环境设计 3.1 IMX6ULL 核心板硬件资源 3.1.1 CPU、内存与存储 3.1.2 LCD 显示接口与 framebuffer 3.1.3 音频输入输出接口 3.1.4 触摸与按键输入 3.2 开发环境搭建 3.2.1 Windows / Linux 主机开发环境 3.2.2 ARM 交叉编译工具链 3.2.3 板端运行环境配置 3.3 跨平台硬件抽象策略 4 系统软件设计 4.1 软件总体架构 4.1.1 模块划分与依赖关系 4.1.2 主循环与时间源设计 4.1.3 绘制调用链 4.2 TomGame 应用层设计 4.2.1 TomGameApp 状态机 4.2.2 角色动画系统(TomAnimator) 4.2.3 HUD 与设置面板 4.3 语音交互子系统设计 4.3.1 音频采集与回放 4.3.2 音频处理与变声 4.3.3 语音交互控制器 4.4 嵌入式关键词识别模型 4.4.1 识别流程与接口抽象 4.4.2 TinyKWS 模型结构 4.4.3 端侧推理实现 4.4.4 命令路由与游戏反馈 4.5 底层渲染与平台适配层 4.5.1 FrameBuffer 与 DrawContext 统一绘制接口 4.5.2 IDisplay 与 SDLDisplay / FBDisplay 4.5.3 IAudioInput / IAudioOutput 与 ALSA / SDL 后端 4.5.4 ITimeSource 与固定步长 Timer 4.6 离线资源转换工具链 4.6.1 Tom Atlas 自动生成流程 4.6.2 BitmapFont 生成 5 系统测试与结果分析 5.1 测试环境 5.2 跨平台编译测试 5.3 功能测试 5.4 性能测试 5.5 测试结果分析 6 总结与展望 6.1 工作总结 6.2 存在问题与改进方向 致谢 参考文献 附录 附录 A 关键源代码清单 附录 B 系统运行截图 附录 C 硬件连接示意图 --- # 1 绪论 ## 1.1 课题背景与研究意义 ### 1.1.1 嵌入式游戏与语音交互的发展背景 随着嵌入式处理器性能不断提升,以及 LCD、触摸屏、麦克风阵列等外设在教学级开发板上的普及,传统意义上“只能跑控制程序”的嵌入式平台也开始承载更丰富的多媒体交互应用。语音作为一种自然、无需双手的输入方式,在儿童陪伴、智能家居、车载交互等场景中得到广泛关注。将语音交互下沉到无 GPU 或弱 GPU 的低成本嵌入式设备,能够在不依赖云端的情况下完成本地唤醒、指令识别与即时反馈,既降低了网络依赖,也保护了用户隐私。 ### 1.1.2 在资源受限平台运行图形游戏的必要性 IMX6ULL 是一颗典型的低功耗嵌入式 SoC,集成 ARM Cortex-A7 单核 CPU,未配备独立 GPU,显示输出依赖 CPU 直接向帧缓冲写入像素。在这样的平台上运行图形游戏,必须放弃传统的 OpenGL / GPU 渲染管线,改用纯 CPU 软光栅化;同时要在音频采集、语音推理、动画更新之间合理分配 CPU 预算,才能保证画面帧率与交互实时性。研究如何在资源受限平台上构建“可玩、可看、可交互”的语音游戏,对于嵌入式 Linux 应用开发、端侧 AI 部署以及软硬件协同设计均具有教学与工程价值。 ## 1.2 国内外研究现状 ### 1.2.1 嵌入式图形渲染技术研究现状 在 PC 与移动平台,现代图形 API(OpenGL、Vulkan、Metal)已经高度成熟;但在无 GPU 的 MCU / MPU 上,研究重点转向轻量级软渲染器、固定点运算、Tile-based 局部刷新和专用 2D 加速库。学术界与工业界常见的路线包括:使用 RGB565 / RGBA5551 等紧凑像素格式降低显存带宽,采用 Sprite 图集替代独立纹理,使用整数定点数替代浮点运算,以及在编译期将资源烘焙为 C 数组以避免运行时解码。这些思路与本项目的 `Core` 底层渲染库设计高度一致。 ### 1.2.2 端侧语音关键词识别技术研究现状 端侧关键词识别(Keyword Spotting, KWS)近年来逐渐从云端向设备端迁移。以 Google 的 MicroNet、ARM 的 CMSIS-NN、Edge Impulse 的 TinyML 工作流为代表,研究人员通过深度可分离卷积(Depthwise Separable Convolution)、量化和剪枝,将神经网络压缩到数百 KB 甚至数十 KB,能够在 Cortex-M / Cortex-A 系列处理器上实时运行。TinyKWS 是一类面向嵌入式设备的轻量级 KWS 模型,通常以 MFCC 作为前端特征,以少量卷积层提取时频特征,最后通过全连接层输出关键词类别概率。本项目将类似的网络结构移植到 IMX6ULL 端侧,直接驱动游戏角色动作。 ## 1.3 本文主要研究内容 本文围绕“基于 IMX6ULL 的纯 CPU 渲染语音交互游戏”展开,主要研究内容包括: 1. **TomGame 应用层设计与实现**:状态机驱动的角色动画、双模式语音交互(变调回放 / 关键词识别)、HUD 与设置面板、配置持久化。 2. **端侧语音关键词识别模型**:基于 MFCC 特征提取与轻量 CNN(Conv2D + Depthwise Separable Conv + Global Average Pooling + FC + Softmax)的 TinyKWS 模型,及其在 IMX6ULL 上的 C++ 推理实现。 3. **音频录制、变声与回放链路**:ALSA / SDL 双后端音频输入输出、录音缓冲、静音检测、变调(pitch-up)、音量增益与播放进度管理。 4. **跨平台渲染与部署**:RGB565 帧缓冲、RGBA5551 精灵图集、SDL2 / Framebuffer 可切换显示后端、ARM 交叉编译与板端运行验证。 > **说明:** 本项目为小组协作完成,底层 SDL2 / Framebuffer 显示适配、音频平台抽象与 `Core` 渲染库由同组成员共同维护;本文作者主要负责 TomGame 应用层、语音交互控制与 TinyKWS 端侧识别模型的设计与实现,因此报告内容按约 70% 侧重游戏应用与语音识别、30% 侧重底层渲染与平台适配进行组织。 --- # 2 系统总体方案与相关技术 ## 2.1 系统需求分析 ### 2.1.1 功能需求 TomGame 的功能需求围绕“会说话的小猫”这一核心玩法展开: - **角色展示**:屏幕中央显示卡通小猫 Tom,根据游戏状态切换 idle、录音倾听、说话、跳跃等动画。 - **变调学说话**:用户按住录音键/按钮录音,松开后 Tom 以更高音调重复播放刚才的录音,形成“学舌”效果。 - **关键词指令识别**:切换到“识别模式”后,用户说出“Up / On / Stop”等关键词,Tom 执行跳跃或停止动作。 - **输入方式**:支持物理按键、触摸屏点击以及语音三种输入方式。 - **设置面板**:可调节录音增益与播放音量,配置在本地持久化。 - **退出与返回**:提供退出按钮与设置按钮,支持随时中断语音流程。 ### 2.1.2 性能需求 - **画面帧率**:PC 端不低于 60 FPS;IMX6ULL 板端以 1024×600 分辨率稳定 30 FPS。 - **音频延迟**:录音启动到开始采集的延迟小于 100 ms;变调回放启动延迟小于 50 ms。 - **识别延迟**:关键词录音结束后,端侧推理与命令响应总延迟控制在 300 ms 以内。 - **资源占用**:运行时避免动态内存频繁分配;二进制体积与内存占用适配 IMX6ULL 的 256 MB DDR。 ### 2.1.3 跨平台开发与部署需求 - 同一套源码能够在 Windows(MSVC + SDL2)、Linux x86_64(GCC + SDL2)和 ARM Linux(交叉编译 + SDL2 或 Framebuffer)上编译运行。 - 平台相关代码集中在 `src/Core/Platform/`,应用层不直接依赖 SDL、ALSA、evdev 等具体 API。 - 图片、字体资源在构建前离线转换为 C++ 头文件,板端无需部署原始 PNG/TTF。 ## 2.2 系统总体架构设计 ### 2.2.1 Core / Apps 分层架构 项目采用严格单向依赖的分层架构,总体依赖方向为 `Apps -> Core -> Platform`: - **Platform 层**:位于最底层,封装显示、时间、音频输入输出、按键、指针输入等硬件差异,以纯虚接口形式向上提供服务。 - **Core 层**:建立在 Platform 接口之上,提供 `FrameBuffer`、`DrawContext`、`Rasterizer`、`Sprite`、`Tilemap`、`BitmapFont`、`Timer` 等可复用的 2D 渲染与定时组件。 - **Apps 层**:位于最顶层,TomGame 位于 `src/Apps/Game/`,包含游戏专属的状态机、动画、语音交互和关键词识别逻辑。 [图 1:TomGame 系统分层架构图] ```text ┌─────────────────────────────────────────────┐ │ Apps/Game:TomGameApp / 语音交互 / TinyKWS │ ├─────────────────────────────────────────────┤ │ Core:DrawContext / FrameBuffer / Sprite │ ├─────────────────────────────────────────────┤ │ Platform:IDisplay / IAudioInput/Output │ │ IButtonInput / IPointerInput │ │ ITimeSource / Timer │ └─────────────────────────────────────────────┘ ``` ### 2.2.2 平台抽象层设计 平台抽象层的核心思想是:应用层只与接口打交道,不关心底层是 SDL2 还是 Linux 原生设备节点。关键接口包括: - `Platform::IDisplay`:显示后端抽象,实现为 `SDLDisplay`(PC)或 `FBDisplay`(IMX6ULL /dev/fb0)。 - `Platform::IAudioInput / IAudioOutput`:音频输入输出抽象,实现为 `SdlAudioInput / SdlAudioOutput` 或 `AlsaAudioInput / AlsaAudioOutput`。 - `Platform::IButtonInput / IPointerInput`:物理按键与触摸/鼠标指针抽象,实现为 `SdlKeyboardButtonInput / SdlPointerInput` 或 `EvdevButtonInput / EvdevTouchInput`。 - `Platform::ITimeSource`:独立时间源,提供单调递增的整数毫秒时钟。 `DefaultHardware.h` 通过 `typedef` 按平台选择默认后端,使 `TomGameApp` 中几乎不存在 `#ifdef TARGET_IMX` 分支。 ### 2.2.3 资源离线转换流程 为了降低运行时负担,所有图片与字体资源在构建前完成离线转换: - Tom 游戏的精灵素材由 `tools/asset_pipeline/SpriteAssetTool.cpp` 从 `src/Apps/Game/assets/raw/` 读取 PNG,打包成 `src/Apps/Game/generated/tom_atlas.h`。 - 通用测试 sprite 使用 `tools/png_to_header.py` 转换为 RGBA5551 C 数组。 - 像素字体使用 `tools/gen_font_atlas.py` 生成 1-bit mask 位图字体头文件。 板端运行时不依赖 PNG/TTF 解码库,所有资源直接以编译期常量的形式链接进可执行文件。 ## 2.3 关键开发平台与技术 ### 2.3.1 IMX6ULL 开发板硬件平台 目标硬件为正点原子 IMX6ULL 教学开发板,核心资源如下: - CPU:NXP i.MX6ULL,ARM Cortex-A7 @ 792 MHz(部分版本 800 MHz)。 - 内存:256 MB DDR3L。 - 存储:8 GB eMMC / NAND Flash。 - 显示:7 英寸 RGB LCD,分辨率 1024×600,通过 framebuffer 接口输出。 - 音频:板载 WM8960 音频编解码器,通过 I²S 连接,Linux 下以 ALSA 设备暴露。 - 输入:电阻/电容触摸屏(evdev)、用户按键、麦克风。 ### 2.3.2 ARM Linux 与交叉编译环境 开发主机运行 Windows / Ubuntu,交叉编译工具链为 `arm-linux-gnueabihf-gcc/g++`。CMake 工具链文件位于 `cmake/toolchain-arm-linux-gnueabihf.cmake`,通过 `-DCMAKE_TOOLCHAIN_FILE` 指定后即可生成 ARM Makefile / Ninja 工程。 ### 2.3.3 SDL2 / Framebuffer 双显示后端 - `SDL2 后端`:PC 主路径,用于快速迭代调试;IMX6ULL 上若已部署 SDL2 库,也可作为对照路径。 - `Framebuffer 后端`:IMX6ULL 极简路径,直接写 `/dev/fb0`,避免 SDL2 依赖。 CMake 选项 `TARGET_IMX` 控制软件栈: ```bash -DTARGET_IMX=OFF # PC / ARM-SDL2:SDLDisplay + SdlAudio + SdlInput -DTARGET_IMX=ON # IMX6ULL:FBDisplay + AlsaAudio + EvdevInput ``` ### 2.3.4 C++11 与 CMake 构建系统 项目采用 C++11 标准,以兼容较老的嵌入式交叉工具链。顶层 `CMakeLists.txt` 定义了 `imx6u_core` 静态库与多个应用 target(Game、Demo、LightGame、Desktop),并通过 `imx6u_configure_app_target` 函数统一处理链接、输出目录与平台依赖。 ### 2.3.5 TinyKWS 关键词识别模型 TinyKWS 是本项目的端侧语音核心。它以 16 kHz 单声道 PCM 为输入,通过预加重、分帧、加汉宁窗、FFT、Mel 滤波 bank 与 DCT 提取 49×10 维 MFCC 特征,再送入一个轻量卷积神经网络得到 12 类关键词概率。模型权重以 `int8_t / float` 数组形式嵌入 `TinyKwsModelData.cpp`,无需运行时加载外部模型文件。 ### 2.3.6 音频采集与回放技术 音频链路抽象为 `IAudioInput`(录音)与 `IAudioOutput`(播放)两个接口。PC 端由 SDL2 音频回调驱动,板端由 ALSA `snd_pcm_readi / snd_pcm_writei` 直接读写。游戏层通过 `VoiceRecorder` 与 `VoicePlayer` 管理环形缓冲、播放进度与结束判定,避免在主循环中直接处理平台音频细节。 --- # 3 硬件平台与运行环境设计 ## 3.1 IMX6ULL 核心板硬件资源 ### 3.1.1 CPU、内存与存储 IMX6ULL 采用 ARM Cortex-A7 单核,支持 VFPv4 浮点单元;NEON 可用但项目核心渲染路径优先使用整数运算以控制功耗与延迟。板载 256 MB DDR3L 与 8 GB eMMC,足够容纳本项目生成的二进制与资源数据。由于缺少 GPU,所有图形计算必须由 CPU 完成。 ### 3.1.2 LCD 显示接口与 framebuffer LCD 控制器通过 IPU / PxP 或简单 DMA 将系统内存中的帧缓冲内容扫描输出。Linux 内核将显存映射为 `/dev/fb0` 字符设备,用户空间可通过 `ioctl(FBIOGET_FSCREENINFO / FBIOGET_VSCREENINFO)` 获取像素格式与行步长,再用 `mmap` 直接写入。IMX6ULL 教学板常见面板格式为 RGB565 或 ARGB8888;本项目的 `FBDisplay` 会读取运行时格式并选择对应转换路径。 ### 3.1.3 音频输入输出接口 板载 WM8960 通过 I²S 与 IMX6ULL 连接,Linux 内核加载 `snd_soc_imx_wm8960` 后,用户空间可通过 ALSA 访问 `hw:0,0` 等设备节点。麦克风用于录音,耳机/扬声器接口用于播放变调后的语音。本项目默认使用 16 kHz、16 bit、单声道配置,以保证 MFCC 特征与 TinyKWS 模型输入一致。 ### 3.1.4 触摸与按键输入 触摸屏通过 I²C 或 USB 连接到 IMX6ULL,内核生成 `/dev/input/eventX` 事件节点。`EvdevTouchInput` 通过 `EV_ABS` 坐标事件读取触摸按下/释放状态,并将原始坐标映射到 1024×600 屏幕坐标。用户按键同样通过 evdev 读取 `EV_KEY` 事件。 ## 3.2 开发环境搭建 ### 3.2.1 Windows / Linux 主机开发环境 - Windows:安装 Visual Studio / MSVC,仓库已自带 `libs/Win/SDL2` 与 `libs/Win/SDL_image`。 - Linux:安装 `libsdl2-dev`、`libsdl2-image-dev`、`cmake`、`g++`;交叉编译时额外安装 `gcc-arm-linux-gnueabihf`。 ### 3.2.2 ARM 交叉编译工具链 交叉编译命令示例: ```bash cmake -B build-arm-fb \ -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain-arm-linux-gnueabihf.cmake \ -DTARGET_IMX=ON . cmake --build build-arm-fb ``` > 注意:Tom 图集头文件生成工具 `TomAtlasTool` 是主机侧工具,依赖 PC/Linux 上的 SDL2_image;ARM 交叉编译前需先在主机构建目录执行 `GenerateTomAtlasHeader`。 ### 3.2.3 板端运行环境配置 将生成的 `IMX6U-Game` 二进制通过 `scp` 复制到板端 `/tmp/` 或 `/opt/imx6u-game/`,并确保: - `/dev/fb0` 可读写(Framebuffer 后端)。 - ALSA 设备节点存在且声卡驱动已加载。 - 触摸屏/按键事件节点权限正确。 ## 3.3 跨平台硬件抽象策略 跨平台的关键在于将平台相关代码收敛到 `src/Core/Platform/`,并通过 `DefaultHardware.h` 的 `typedef` 在编译期选择后端。`TomGameApp` 只持有接口指针,因此同一套应用代码在 PC 与板端均可编译运行。主循环中通过 `CreateDisplay()` 在 `TARGET_IMX` 与 `TARGET_PC` 之间二选一创建显示后端,是应用层唯一的显式平台分支。 --- # 4 系统软件设计 ## 4.1 软件总体架构 ### 4.1.1 模块划分与依赖关系 TomGame 的软件模块可划分为以下层次: | 层级 | 模块 | 主要职责 | |------|------|----------| | 应用层 | `TomGameApp` | 状态机、输入处理、设置管理 | | 应用层 | `TomAnimator` | 角色动画帧选择与绘制定位 | | 应用层 | `TomHud` / `TomSettingsPanel` | HUD 按钮与设置滑块 | | 应用层 | `VoiceInteractionController` | 录音/变声/播放/识别流程调度 | | 应用层 | `VoiceRecorder / VoicePlayer / VoiceEffect` | 音频缓冲与数字信号处理 | | 应用层 | `TinyKwsRecognizer` / `KeywordCommandRouter` | 关键词识别与指令映射 | | 底层库 | `Core::DrawContext / FrameBuffer / Sprite` | 2D 渲染 | | 平台层 | `IDisplay / IAudioInput / IAudioOutput / IButtonInput / IPointerInput / ITimeSource` | 平台适配 | ### 4.1.2 主循环与时间源设计 `src/Apps/Game/Main.cpp` 中的主循环遵循“轮询输入 → 固定步长更新 → 绘制 → 提交 → 睡眠等待”的结构: ```cpp timer.begin_frame(time_source.get_time_ms()); display->poll_events(should_quit); app.update(timer.fixed_delta_ms()); app.draw(ctx); ctx.present(display); SleepRemainingFrameTime(timer, time_source); ``` `Platform::SteadyTimeSource` 在 Linux 下基于 `clock_gettime(CLOCK_MONOTONIC)`,在 Windows 下基于 `std::chrono::steady_clock`,返回 `uint32_t` 毫秒。`Core::Timer` 采用余数累积法生成固定时间片,例如 30 FPS 下依次产生 33、33、34 ms 的 tick,保证长时间运行无帧率漂移。 [图 2:TomGame 主循环与模块交互流程图] ### 4.1.3 绘制调用链 每帧绘制流程如下: 1. `TomGameApp::draw(ctx)` 调用 `ctx.clear_color(...)` 清屏。 2. 绘制背景精灵 `TomAtlas::background`。 3. `TomAnimator::draw(ctx, state)` 根据当前状态选择并绘制 Tom 角色精灵。 4. `TomHud::draw(ctx)` 绘制底部模式切换按钮与录音按钮。 5. 绘制左上角退出按钮 `ui_tom` 与右上角设置按钮 `ui_i`。 6. 若设置面板打开,绘制 `TomSettingsPanel`。 7. `ctx.present(display)` 将 RGB565 帧缓冲提交到显示后端。 所有绘制调用均通过 `Core::DrawContext` 统一入口,应用层不直接操作 framebuffer 内存。 ## 4.2 TomGame 应用层设计 ### 4.2.1 TomGameApp 状态机 `TomGameApp` 维护一个四级状态机: - **Idle( idle )**:等待用户触发录音或识别。 - **Recording(录音中)**:根据当前语音模式,分别进入变调回放录音或关键词识别录音。 - **Speaking(说话中)**:播放变调后的录音,并循环播放说话动画。 - **Jumping(跳跃中)**:响应“Up / On”关键词或相应指令,播放跳跃动画。 状态转换由 `VoiceInteractionController` 返回的事件驱动,例如 `RecordingStarted`、`SpeakingStarted`、`SpeakingFinished`、`KeywordRecognized`、`IdleRequested`。 [图 3:TomGameApp 状态机转换图] ```text Idle --(record trigger)--> Recording Recording --(finish pitch recording)--> Speaking Recording --(keyword recognized)--> Jumping Recording --(no keyword)--> Idle Speaking --(playback finished)--> Idle Jumping --(animation finished)--> Idle ``` 输入处理逻辑在 `update_input()` 中完成: - 物理按键按下触发录音。 - 触摸屏点击退出或设置按钮进入对应流程。 - HUD 上的模式按钮切换 `VoiceMode`(PitchRepeat / KeywordRecognition)。 - 设置面板打开时,主游戏逻辑暂停,只处理设置滑块与保存按钮。 ### 4.2.2 角色动画系统(TomAnimator) `TomAnimator` 负责根据状态选择并定位 Tom 的精灵帧: - `Idle`:显示 `tom_stand`。 - `Recording`:显示 `tom_listhen`。 - `Speaking`:循环播放 `tom_say1 ~ tom_say4` 序列,每帧 90 ms。 - `Jumping`:顺序播放 `tom_jump1 ~ tom_jump6`,每帧 150 ms,结束后由应用层切回 Idle。 Tom 的水平位置始终居中,垂直位置根据屏幕高度与精灵高度计算底部留空 `TomBottomPadding = 72`。 ### 4.2.3 HUD 与设置面板 `TomHud` 在屏幕底部提供三个按钮: - 左侧“变调学说话”模式按钮 - 右侧“关键词识别”模式按钮 - 中间录音按钮 当前激活模式通过按钮高亮状态反馈。`TomSettingsPanel` 提供两个滑块:录音增益(`record_gain`)与播放音量(`speaker_volume`),并支持保存到本地文件 `tom_settings.cfg`。配置加载发生在 `TomGameApp` 构造阶段,保存时写入 `key = value` 文本格式。 [图 4:TomGame 主界面布局示意图] ## 4.3 语音交互子系统设计 ### 4.3.1 音频采集与回放 **VoiceRecorder** 封装了录音缓冲与静音检测: - 可配置采样率、通道数、最大录音时长、静音阈值与最小录音时长。 - 每帧从 `IAudioInput` 读取一定数量样本,累积到 `std::vector` 中。 - 当检测到连续静音样本数超过阈值,或达到最大录音时长时,自动标记 `finished`。 - 提供 `get_last_volume()` 用于 HUD 音量反馈。 **VoicePlayer** 封装了播放缓冲与进度管理: - 接收 `int16_t` 样本、采样率、通道数。 - 每帧向 `IAudioOutput` 写入一定数量的样本,更新 `playPosition`。 - 播放结束后标记 `finished`。 ### 4.3.2 音频处理与变声 `VoiceEffect` 提供了一系列静态工具函数: - `amplify`:对样本乘以增益并裁剪到 `int16_t` 范围。 - `trim_silence`:去除头尾低于阈值的静音段。 - `pitch_up`:使用线性插值或重采样实现升调效果(默认 `pitchFactor = 1.45`)。 - `resample`:线性插值重采样,用于适配不同的输入/输出采样率。 - `convert_channels`:单声道与立体声互转。 在“变调学说话”模式下,录音结束后依次执行: ```text 录音原始样本 → amplify(inputGain) → trim_silence → pitch_up(1.45f) → amplify(outputGain) → 播放 ``` 默认输出增益 `outputGain = 1.15f`,使变调后的声音保持足够响度。 ### 4.3.3 语音交互控制器 `VoiceInteractionController` 是语音交互的中央调度器,向上层返回统一的事件枚举 `VoiceInteractionEvent`。其核心流程如下: 1. `start_recording()`:停止当前播放,初始化音频输入,启动 `VoiceRecorder`。 2. `update_pitch_repeat_recording(deltaMs)`:持续读取样本,超时或静音结束后进入 `repeat_last_recording()`。 3. `update_keyword_recording(deltaMs)`:持续读取样本,超时或静音结束后调用 `try_recognize_keyword()`。 4. `repeat_last_recording()`:对录音应用变调与增益,然后调用 `start_speaking()`。 5. `try_recognize_keyword()`:将样本送入 `TinyKwsRecognizer`,返回识别结果。 6. `update_speaking(deltaMs)`:持续向 `IAudioOutput` 写入样本,播放结束返回 `SpeakingFinished`。 [图 5:语音交互控制器数据流与状态转换图] ## 4.4 嵌入式关键词识别模型 ### 4.4.1 识别流程与接口抽象 为了便于后续替换不同识别后端,`KeywordRecognizer.h` 定义了抽象接口 `IKeywordRecognizer`: ```cpp class IKeywordRecognizer { public: virtual bool init() = 0; virtual KeywordRecognitionResult recognize( const std::vector& samples, uint32_t sampleRate, uint32_t channels) = 0; virtual void set_input_gain(float gain) {} virtual float get_input_gain() const { return 1.0f; } }; ``` `KeywordCommand` 枚举定义了 9 条可识别指令:`Up、Down、Left、Right、Go、Stop、Yes、No、On`。识别结果包含命令与置信度两个字段。 ### 4.4.2 TinyKWS 模型结构 本项目的 TinyKWS 模型以 Google Speech Commands 数据集中常见的轻量关键词识别网络为参考,结构如下: **前端特征提取:** - 输入音频:16 kHz 单声道 PCM,预处理为统一采样率与通道数。 - 语音活动检测:基于能量进行头尾静音裁剪。 - 分帧:帧长 512 点(32 ms),帧移 320 点(20 ms),共 49 帧。 - 加窗:汉宁窗(Hann Window)。 - FFT:512 点复数 FFT,取 257 维幅度谱。 - Mel 滤波 bank:40 个 Mel 滤波器,覆盖 20 Hz ~ 4000 Hz。 - DCT:取前 10 个 MFCC 系数,最终特征维度为 `49 × 10`。 **神经网络结构:** | 层 | 输入 | 输出 | 说明 | |---|---|---|---| | Conv2D | 49×10×1 | 25×5×64 | 卷积核 10×4,stride 2×2,ReLU | | DepthwiseConv2D + Conv1×1 | 25×5×64 | 25×5×64 | 重复 4 组深度可分离块 | | Global Average Pooling | 25×5×64 | 1×1×64 | 空间平均 | | Fully Connected | 64 | 12 | 输出 logits | | Softmax | 12 | 12 | 类别概率 | 12 个输出类别与关键词映射关系如下: | 类别索引 | TinyKWS 输出 | 映射命令 | |---|---|---| | 0 | Down | Down | | 1 | Go | Go | | 2 | Left | Left | | 3 | No | No | | 5 | On | On | | 6 | Right | Right | | 7 | Stop | Stop | | 8 | Up | Up | | 9 | Yes | Yes | | 4 / 10 / 11 | Off / Silence / Unknown | None(忽略)| [图 6:TinyKWS 网络结构示意图] ```text MFCC 49×10 ↓ Conv2D(10×4, s=2) → 25×5×64 ↓ DepthwiseConv2D + PointwiseConv × 4 ↓ Global Average Pooling → 64 ↓ FC → 12 ↓ Softmax ``` ### 4.4.3 端侧推理实现 `TinyKwsRecognizer` 的实现完全位于 `src/Apps/Game/src/recognition/TinyKwsRecognizer.cpp`,不依赖任何外部推理框架。主要步骤: 1. **PrepareAudio**:将输入样本重采样到 16 kHz 单声道,并做静音裁剪。 2. **NormalizeWindow**:对 1 秒窗口(16000 点)做最大幅值归一化,再乘以输入增益。 3. **ExtractMfccFeatures**:按前述流程提取 49×10 维 MFCC。 4. **run_embedded_model**:执行 Conv2D、DepthwiseConv2D、全局平均池化、全连接与 Softmax。 5. **置信度阈值判定**:默认阈值 `confidenceThreshold = 0.75`,只有最佳类别概率超过阈值且映射到非 `None` 命令时才返回识别结果。 对于超过 1 秒的录音,采用滑动窗口策略:窗口 16000 点、步长 8000 点,取所有窗口中置信度最高的有效结果。 模型权重以 `extern const` 数组形式存放在 `TinyKwsModelData.cpp` 中: - `Conv0Weights` 等卷积权重使用 `int8_t` 量化并配合 `WeightsScale` 反量化; - Depthwise 权重与全连接权重使用 `float`; - 偏置统一使用 `float`。 这种设计使模型参数直接编译进可执行文件,板端无需文件系统支持即可运行推理。 ### 4.4.4 命令路由与游戏反馈 `KeywordCommandRouter` 将识别到的关键词映射为游戏动作: - `Up` / `On` → `KeywordGameAction::Jump` - `Stop` → `KeywordGameAction::BackToIdle` - 其他命令 → `KeywordGameAction::None` `TomGameApp` 收到 `KeywordRecognized` 事件后,通过 `handle_keyword_action()` 触发跳跃或停止。当前仅实现了跳跃反馈,后续可扩展为方向移动、暂停、确认/取消等更多动作。 [图 7:关键词识别 → 命令路由 → 游戏反馈链路图] ## 4.5 底层渲染与平台适配层 ### 4.5.1 FrameBuffer 与 DrawContext 统一绘制接口 `Core::FrameBuffer` 在构造时一次性分配 `width × height` 个 `uint16_t` 像素作为 RGB565 缓冲,运行期不再扩容。`Core::DrawContext` 封装了 framebuffer、depthbuffer、线段光栅化、三角形光栅化,对外提供 `clear_color`、`draw_sprite`、`draw_text`、`fill_rect`、`present` 等接口。TomGame 主要使用 2D sprite 绘制路径,因此不启用 depth buffer。 Sprite 绘制热路径采用 RGBA5551 1-bit 透明:alpha 为 0 的像素直接跳过,为 1 的像素覆写到 RGB565 帧缓冲。该路径不含 alpha 混合,每像素仅一次读取、一次位测试和一次写入。 ### 4.5.2 IDisplay 与 SDLDisplay / FBDisplay `Platform::IDisplay` 定义四个纯虚方法:`init`、`present`、`poll_events`、`shutdown`。`SDLDisplay` 在 PC 上创建 RGB565 streaming texture,通过 `SDL_UpdateTexture + SDL_RenderCopy + SDL_RenderPresent` 提交;`FBDisplay` 在板端通过 `mmap /dev/fb0` 并一次性 `memcpy` 提交。两种后端使应用层无需关心“像素如何上屏”。 ### 4.5.3 IAudioInput / IAudioOutput 与 ALSA / SDL 后端 `IAudioInput` 抽象了 `init(device, sample_rate, channels)`、`read_samples`、`shutdown` 等操作;`IAudioOutput` 抽象了 `init`、`write_samples`、`play_wav`、`shutdown`。PC 端由 SDL2 音频设备回调驱动,板端由 ALSA 直接读写 PCM。`DefaultAudioInput / DefaultAudioOutput` 通过 `DefaultHardware.h` 按平台 typedef 选择具体实现。 ### 4.5.4 ITimeSource 与固定步长 Timer `Platform::ITimeSource` 独立于显示层,提供单调整数毫秒时钟。`Core::Timer` 支持 30 / 45 / 60 FPS 三档,采用余数累积法避免 `1000 / fps` 的截断误差,保证每 1000 ms 内 tick 总和精确等于 1 秒。 ## 4.6 离线资源转换工具链 ### 4.6.1 Tom Atlas 自动生成流程 Tom 游戏的全部精灵被打包到一个 atlas 中,由 `SpriteAssetTool` 在构建主机上离线生成: - 源文件位置:`src/Apps/Game/assets/raw/` - 生成文件:`src/Apps/Game/generated/tom_atlas.h` - 运行方式:`cmake --build build-win --config Release --target GenerateTomAtlasHeader` 生成的头文件包含 `tom_atlas_pixels` 像素数组以及每张精灵的 `RenderData::Sprite` 描述(`background`、`tom_stand`、`tom_listhen`、`tom_say1~4`、`tom_jump1~6`、`ui_tom`、`ui_i` 等)。 ### 4.6.2 BitmapFont 生成 像素字体通过 `tools/gen_font_atlas.py` 从 TTF 生成 1-bit mask 位图字体,输出 `assets/font/font_atlas.h`。运行时 `DrawContext::draw_text` 直接将 mask 中 bit=1 的像素写成指定颜色,不保留灰度或 alpha 混合。 --- # 5 系统测试与结果分析 ## 5.1 测试环境 | 项目 | PC 端 | 板端 | |---|---|---| | CPU | x86_64 / AMD Ryzen / Intel | NXP i.MX6ULL Cortex-A7 | | OS | Windows 11 / Ubuntu 22.04 | Linux 4.x / 5.x | | 显示 | SDL2 窗口 | /dev/fb0 RGB565 / SDL2 | | 音频 | SDL2 Audio / PulseAudio | ALSA + WM8960 | | 输入 | 键盘 / 鼠标 | 触摸屏 / 用户按键 | | 编译器 | MSVC 2022 / GCC 11 | arm-linux-gnueabihf-gcc | ## 5.2 跨平台编译测试 分别在以下三种配置下完成编译: 1. Windows x86_64 + MSVC + SDL2:`cmake -B build-win .` → 成功。 2. Linux x86_64 + GCC + SDL2:`cmake -B build-linux .` → 成功。 3. ARM Linux + GCC + Framebuffer:`cmake -B build-arm-fb -DCMAKE_TOOLCHAIN_FILE=... -DTARGET_IMX=ON` → 成功。 所有配置均未出现编译错误,验证了 CMake 条件编译与 `DefaultHardware` 抽象的正确性。 ## 5.3 功能测试 ### 5.3.1 显示后端切换测试 同一套 TomGame 代码在 PC SDL2 窗口与板端 `/dev/fb0` 上均正常显示背景、Tom 角色、HUD 按钮与设置面板,画面内容一致。 ### 5.3.2 精灵动画与 UI 交互测试 - Idle 状态显示站立动画。 - 按下录音键后切换为倾听表情。 - 变调回放期间循环播放说话动画。 - 关键词识别成功后播放跳跃动画。 - 设置按钮可正常打开/关闭面板,滑块可拖动并保存配置。 ### 5.3.3 录音变声回放测试 - 录音启动延迟 < 100 ms。 - 最大录音时长 5 s,静音自动结束。 - 回放音调明显高于原声,且无明显爆音。 - 录音增益与播放音量设置实时生效。 ### 5.3.4 关键词识别功能测试 - 对“Up / On / Stop”等训练关键词,PC 端识别准确率 > 85%(安静环境、阈值 0.75)。 - 识别成功后 Tom 执行跳跃或停止动作。 - 阈值可通过命令行 `--kws-threshold` 调节,增益可通过 `--kws-input-gain` 或设置面板调节。 ## 5.4 性能测试 ### 5.4.1 PC 端帧率与绘制耗时 PC Release 构建下,TomGame 稳定运行在 60 FPS,`update + draw` 总耗时约 1~2 ms,大部分时间消耗在 `SleepRemainingFrameTime` 等待。 ### 5.4.2 IMX6ULL 端 framebuffer 提交性能 板端 Release 构建、Framebuffer 后端、1024×600 分辨率下: - 整帧时间约 33 ms,满足 30 FPS 目标。 - `present()` 耗时约 3~6 ms(RGB565 直接行拷贝路径)。 - TinyKWS 单次推理耗时约 20~40 ms,发生在录音结束后的空闲帧,不影响画面帧率。 > 注:Debug 构建下同一轻量 2D 场景曾出现 `Frame: 81 ms / Present: 69 ms` 的劣质表现,切换到 Release 后帧率提升一个数量级,说明 ARM 端性能测试必须基于 Release 构建。 ### 5.4.3 Release 与 Debug 构建对比 | 构建类型 | PC 帧率 | 板端帧率 | 说明 | |---|---|---|---| | Debug | 60 FPS | ~12 FPS | 含调试 UI、未优化代码 | | Release | 60 FPS | 30 FPS | 优化后满足目标 | ## 5.5 测试结果分析 - TomGame 的交互流程在 PC 与板端表现一致,验证了分层架构与平台抽象的有效性。 - 语音变调回放链路稳定,端侧关键词识别能够在录音结束后快速给出结果。 - 板端性能受 Release 构建影响显著,后续部署与测试必须强制使用 Release。 - TinyKWS 推理耗时对 30 FPS 画面不构成瓶颈,因为推理发生在非每帧路径上。 --- # 6 总结与展望 ## 6.1 工作总结 本次实训完成了 TomGame 这一面向 IMX6ULL 的语音交互小游戏,主要工作包括: 1. **应用层交互设计**:实现了 Idle / Recording / Speaking / Jumping 四级状态机,以及变调学说话、关键词识别两种语音玩法。 2. **音频链路**:完成了录音、静音检测、变调、增益、回放全链路的跨平台实现,支持 SDL2 与 ALSA 双后端。 3. **端侧关键词识别**:将 TinyKWS 模型(MFCC + 轻量 CNN)以纯 C++ 方式移植到 IMX6ULL,模型权重嵌入可执行文件,无需外部模型加载。 4. **命令路由与游戏反馈**:通过 `KeywordCommandRouter` 将“Up / On / Stop”等关键词映射为跳跃/停止动作。 5. **跨平台部署**:基于 `Core / Platform` 分层与 CMake 条件编译,实现了 PC 与 ARM 板端共用同一套应用代码。 通过本次实训,作者加深了对嵌入式 Linux 音频采集、端侧神经网络推理、无 GPU 平台 2D 渲染以及 C++11 跨平台工程实践的理解。 ## 6.2 存在问题与改进方向 1. **关键词类别有限**:当前仅将少量关键词映射为跳跃/停止,后续可扩展方向指令(Left / Right / Go)与菜单确认/取消语义。 2. **模型量化可进一步优化**:当前部分权重仍使用 `float`,全 `int8` 量化或对称量化可减少推理耗时与内存占用。 3. **语音激活检测(VAD)较简单**:当前基于能量阈值做静音裁剪,复杂噪声环境下鲁棒性不足,可引入更稳健的端点检测。 4. **设置面板视觉表现**:RGB565 帧缓冲不支持 alpha 混合,半透明遮罩效果受限,后续可增加 dither 或整数混合路径。 5. **底层渲染性能优化**:虽然当前 30 FPS 已达标,但仍可探索 tile/sprite 不透明行拷贝、dirty rect 局部刷新等优化。 6. **Launcher 与多应用切换**:当前 TomGame 是独立可执行文件,后续可按照 `docs/APP_AND_CORE_ARCHITECTURE.md` 规划,实现 Launcher 单进程多应用切换。 --- ## 致谢 感谢指导老师在实训期间的悉心指导,感谢同组成员在底层渲染与平台适配方面的协作与支持。本次实训不仅锻炼了嵌入式软件开发能力,也加深了对软硬件协同设计的理解。 --- ## 参考文献 [1] Freescale Semiconductor. *i.MX 6UltraLite Applications Processor Reference Manual*. Rev. 2, 2016. [2] NXP Semiconductor. *I.MX 6ULL Applications Processor Reference Manual*. [3] Linux Kernel Documentation. *fb/api.txt — The Linux Frame Buffer Device API*. https://www.kernel.org/doc/Documentation/fb/api.txt (accessed 2026-07). [4] Linux Kernel Documentation. *alsa-project.org — Advanced Linux Sound Architecture*. https://www.alsa-project.org (accessed 2026-07). [5] SDL Community. *Simple DirectMedia Layer 2.0 — Documentation Wiki*. https://wiki.libsdl.org/SDL2/ (accessed 2026-07). [6] Pete Warden. *Speech Commands: A Dataset for Limited-Vocabulary Speech Recognition*. arXiv:1804.03209, 2018. [7] TensorFlow Lite Micro. *MicroNet Keyword Spotting Model*. https://github.com/tensorflow/tflite-micro (accessed 2026-07). [8] Edge Impulse. *Keyword Spotting with Deep Learning on Embedded Devices*. https://docs.edgeimpulse.com (accessed 2026-07). [9] 正点原子. *I.MX6U 嵌入式 Linux 驱动开发指南*. 2020. [10] 唐佐林. *现代 C++ 嵌入式实战*. 电子工业出版社, 2019. --- ## 附录 ### 附录 A 关键源代码清单 | 路径 | 说明 | |---|---| | `src/Apps/Game/Main.cpp` | 程序入口、主循环、命令行参数解析 | | `src/Apps/Game/src/app/TomGameApp.cpp/h` | 游戏状态机、输入处理、设置管理 | | `src/Apps/Game/src/gameplay/TomAnimator.cpp/h` | Tom 角色动画帧选择与绘制 | | `src/Apps/Game/src/gameplay/TomHud.cpp/h` | HUD 按钮 | | `src/Apps/Game/src/ui/TomSettingsPanel.cpp/h` | 设置面板与滑块 | | `src/Apps/Game/src/audio/VoiceRecorder.cpp/h` | 录音缓冲与静音检测 | | `src/Apps/Game/src/audio/VoicePlayer.cpp/h` | 播放缓冲与进度 | | `src/Apps/Game/src/audio/VoiceEffect.cpp/h` | 变调、增益、重采样、静音裁剪 | | `src/Apps/Game/src/gameplay/VoiceInteractionController.cpp/h` | 语音交互流程调度 | | `src/Apps/Game/src/recognition/KeywordRecognizer.cpp/h` | 识别接口与命令枚举 | | `src/Apps/Game/src/recognition/TinyKwsRecognizer.cpp/h` | TinyKWS 端侧推理实现 | | `src/Apps/Game/src/recognition/TinyKwsModelData.cpp/h` | 模型权重数据 | | `src/Apps/Game/src/gameplay/KeywordCommandRouter.cpp/h` | 关键词到游戏动作映射 | | `src/Core/Draw2D/DrawContext.cpp/h` | 统一绘制入口 | | `src/Core/Core/FrameBuffer.cpp/h` | RGB565 帧缓冲 | | `src/Core/Platform/IDisplay.h` | 显示后端接口 | | `src/Core/Platform/IAudioInput.h / IAudioOutput.h` | 音频接口 | | `src/Core/Platform/Timer.h / TimeSource.h` | 固定步长计时 | | `CMakeLists.txt` | 顶层构建配置 | ### 附录 B 系统运行截图 [图 B-1:TomGame PC 端 Idle 状态运行截图] [图 B-2:TomGame 录音中(tom_listhen)状态截图] [图 B-3:TomGame 关键词识别成功后跳跃状态截图] [图 B-4:TomGame 设置面板截图] [图 B-5:IMX6ULL 板端运行实拍图] ### 附录 C 硬件连接示意图 [图 C-1:IMX6ULL 教学板硬件连接示意图] [图 C-2:LCD、触摸屏、麦克风、扬声器接线示意]