content_interaction_device 是运行在 RK3588 平台上的内容交互设备软件,用于在游戏机主机和触摸显示屏之间透明插入一层交互能力。
设备接收游戏机 HDMI 输出并显示到触摸屏,同时读取触摸屏 Linux input event;
- 直通模式下,视频全屏显示,触摸事件通过
USB Gadget多点触摸HID透传给游戏机; - 叠加模式下,屏幕划分为
HTML内容区和游戏画面区,内容区由本机Chromium/Qt WebEngine处理,游戏区触摸坐标换算后转发给游戏机。
一、概述
1.1 功能概览
HDMI IN通过V4L2流式采集,使用MMAP缓冲并导出DMA-BUF。- 当前工业主链路不经过
Qt搬运视频帧。 - 当前可运行显示路径为
RGA转换后输出统一NV12 DMA-BUF,再交给DRM Plane显示。 - 真实触摸屏输入读取
/dev/input/eventX,支持EVIOCGRAB独占设备。 - 无
USB Gadget设备时程序仍可运行,只禁用转发给游戏机主机的触摸输出。 - 直通模式:全屏显示
HDMI输入,右下角保留模式触发按钮。 - 叠加模式:
1080P横屏:左侧HTML内容区 20%,右侧游戏区 80%。4K竖屏:上方HTML内容区 10%,下方游戏区 90%。
- 15 秒无操作自动恢复直通模式。
USB Gadget模拟多点触摸屏,描述符和应用报文均支持最多 10 点触摸。
1.2 目录
text
.
├── CMakeLists.txt
├── app/
├── config/
├── docs/
├── scripts/
├── system/
├── touch/
├── ui/
└── video/
1.3 模块划分
1.3.1 模块边界
app:应用装配层,负责程序入口、运行期服务装配和跨模块协调。config:读取config/app.conf,保存静态配置和运行态共享状态。system:日志、信号、udev等通用系统能力。touch:读取Linux input event、处理SYN_DROPPED、坐标映射和USB Gadget HID写入。ui:Qt主窗口、浏览器内容区、模式按钮和状态提示。video:V4L2 HDMI IN、DRM显示路径。
1.3.2 模块文档索引
app/README.md:运行期服务装配、布局协调、HDMI管线控制与线程生命周期。config/README.md:静态配置、运行期共享状态,以及两者的职责边界。system/README.md:日志、状态事件、udev与进程级系统能力。video/README.md:RK3588 HDMI IN采集、DRM Plane显示、硬件视频链路。ui/README.md:Qt控制层、浏览器层、交互布局和本机内容区触摸注入。touch/README.md:真实触摸屏输入、热插拔监听、触摸路由和USB Gadget转发。
这些文档重点说明:
- 模块为什么这样拆;
- 关键事件流怎么走;
- 每一层的职责边界在哪里;
- 后续继续重构时哪些地方不能退回旧实现。
1.3.3 模块依赖关系
当前工程按 app / ui / touch / video / system / config 分层。推荐依赖方向如下:
text
config -> 提供静态配置读取
system -> 提供日志、信号、udev 等通用系统能力
app
-> config
-> system
-> ui
-> touch
-> video
ui
-> config
-> system
touch
-> config
-> system
video
-> config
-> system
更直观地看:
text
+------------------+
| app |
| 装配 / 协调层 |
+------------------+
| | |
v v v
+----------+ +------+ +-------+
| ui | |touch | | video |
| Qt控制层 | |触摸链路| |视频链路|
+----------+ +------+ +-------+
\ | /
\ | /
v v v
+------------------+
| config / system |
| 配置与通用系统层 |
+------------------+
依赖规则:
app是唯一允许同时看到ui、touch、video的装配层。ui只关心Qt控件、模式切换、布局应用和状态显示,不能直接控制V4L2/DRM线程生命周期。touch只关心input event、坐标映射和HID写入,不能直接依赖ui控件或video后端实现。video只关心HDMI IN、V4L2、DRM和硬件显示后端,不能直接依赖ui文案、按钮或窗口对象。system只提供通用能力,不依赖业务模块。config只提供配置和共享状态,不直接访问硬件。
二、架构说明
2.1 运行期服务装配
2.1.1 app_runtime_services
app_runtime_services 是运行期服务装配层,负责"创建哪些服务对象、由谁持有、信号如何连接"。它由 main_window 创建,但内部集中创建并连接:
video_pipe_thread:HDMI IN/V4L2采集和硬件显示线程。touch_thread:真实触摸输入读取、本机内容区事件注入和USB Gadget HID转发。udev_thread:统一分发udev设备事件。hdmi_pipeline_controller:HDMI IN物理连接状态、轮询兜底和视频管线启动控制。touch_input_hotplug_monitor:真实触摸屏input event热插拔监听。usb_host_hotplug_monitor:USB Host连接状态监听。app_lifecycle_controller:后台服务启动/停止顺序控制器。
app_runtime_services 会把模块内部事件转换为 app_status_event,再交给 main_window 显示状态浮层或写日志。
2.1.2 app_lifecycle_controller
app_lifecycle_controller 只负责已经创建好的服务对象什么时候启动和停止。它不创建对象,也不做业务信号连接。
启动顺序:
- 启动
udev_thread。 - 调用
hdmi_pipeline_controller::start_if_connected()。 - 启动
touch_thread。
停止顺序:
- 停止
udev_thread。 - 停止
touch_thread。 - 停止
hdmi_pipeline_controller。
两者关系如下:
text
main_window
-> app_runtime_services # 创建服务对象、连接信号、转换状态事件
-> video_pipe_thread
-> touch_thread
-> udev_thread
-> hdmi_pipeline_controller
-> touch_input_hotplug_monitor
-> usb_host_hotplug_monitor
-> app_lifecycle_controller # 只负责 start/stop 顺序
2.2 关键事件流
2.2.1 浏览器内容区点击链路
text
touchscreen /dev/input/eventX
-> touch_thread
-> app_runtime_services 转发本机内容区触摸信号
-> main_window::inject_local_touch()
-> interaction_ui::inject_local_touch()
-> touch_injector::inject()
-> browser_panel 持有的 QWebEngineView
-> WebEngine 内部 DOM 命中和页面点击处理
说明:
touch_thread读取真实触摸屏事件,并结合touch_frame_router、touch_router和touch_layout_mapper判断当前触摸点属于浏览器区还是游戏区。app_runtime_services只负责连线,不做坐标变换。main_window::inject_local_touch()只负责转发,不自己构造Qt鼠标事件。interaction_ui再委托touch_injector把逻辑坐标映射到浏览器控件局部坐标,并构造QMouseEvent发给QWebEngineView。
2.2.2 游戏区触摸透传链路
text
touchscreen /dev/input/eventX
-> touch_thread
-> hid_touch_report_codec::encode_multi_touch_report()
-> hid_touch_writer
-> /dev/hidg0
-> game console / host
说明:
- 这条路径完全绕过
main_window、interaction_ui和Qt WebEngine。 - 浏览器区和游戏区的分流判断必须稳定,否则会出现本地内容区和主机透传串路。
2.2.3 模式切换链路
text
mode button clicked / idle timeout
-> interaction_mode_controller
-> app_runtime_state::set_interaction_mode()
-> interaction_mode_controller::mode_applied()
-> main_window::apply_interaction_layout()
-> display_layout_controller::apply_layout()
-> interaction_ui::apply_layout()
-> display_layout_controller::sync_video_output_rect()
-> video_pipe_thread::set_output_rect()
2.2.4 HDMI 物理状态到视频线程控制链路
text
extcon / hdmi_hotplug_monitor poll
-> hdmi_pipeline_controller
-> start_if_connected() / stop() / sync_video_rect()
-> video_pipe_thread start or stop
-> display_layout_controller::sync_video_output_rect()
2.3 子模块要点
2.3.1 视频架构
视频主链路不再走 Qt 绘制:
video_pipe_thread:线程入口,只负责V4L2取帧、提交当前硬件显示边界、确保缓冲区回队列。v4l2_dev:自动打开HDMI RX设备,读取驱动实际格式,申请MMAP缓冲并导出DMA-BUF。hardware_video_presenter:当前硬件视频显示门面,负责协调DRM pipeline、framebuffer缓存和plane提交。video/inc/drm、video/src/drm:集中管理DRM card/connector/CRTC/plane选择、DMA-BUF framebuffer缓存和DRM Plane提交。
video 模块内部建议按"采集 -> 恢复 -> 渲染 -> DRM"理解:
text
app / ui
-> video_pipe_thread
-> hdmi_capture_session
-> v4l2_dev
-> capture_recovery_controller
-> frame_timeout_recovery
-> video_presenter
-> hardware_video_presenter
-> drm_device_finder
-> drm_fb_cache
-> drm_plane_presenter
-> hdmi_hotplug_monitor
2.3.2 触摸架构
当前链路只读取 /dev/input/eventX:
touch_thread读取EV_ABS、EV_KEY、EV_SYN- 多点触摸使用
ABS_MT_SLOT、ABS_MT_TRACKING_ID、ABS_MT_POSITION_X/Y - 内容区事件发给
main_window::inject_local_touch() - 游戏区事件通过
hid_touch_report_codec::encode_multi_touch_report()写入/dev/hidg0
2.3.3 UI 架构
ui 模块专注于可见的 Qt 内容:
- 创建和持有浏览器内容区与模式按钮
- 把应用布局层算好的结果应用到
Qt控件 - 接收本机内容区触摸事件,并把它们注入到浏览器控件
三、配置与运行
3.1 配置
业务参数只从固定配置文件读取:
bash
config/app.conf
说明:
HDMI IN分辨率、像素格式和帧率由输入信号与HDMI RX驱动决定,程序只读取实际值,不强制设置。HDMI IN设备节点由程序自动识别stream_hdmirx/rk_hdmirx对应的/dev/video*。- 保留的采集配置只有:
hdmi.buffer_counthdmi.frame_timeout_mshdmi.state_poll_interval_ms
browser.default_url用于配置叠加模式默认网页。- 视频显示固定使用硬件路径,应用固定全屏运行。
3.2 构建
推荐使用:
bash
scripts/build.sh build
手动构建:
bash
cmake -S . -B build
cmake --build build
依赖项:
Qt WidgetsQt WebEngineWidgetslibudevlibdrmlibrga/im2dQt Designer(编辑.ui时)
如果开发板缺少 Qt5WebEngineWidgetsConfig.cmake,Qt5 系统通常需要安装:
bash
sudo apt update
sudo apt install qtwebengine5-dev libqt5webenginewidgets5 libudev-dev libdrm-dev
librga 需要使用 RK3588 板端系统或 BSP 提供的 RGA 头文件和库。
3.3 运行
先初始化 USB Gadget 触摸屏:
bash
sudo scripts/hid/hid_touch.sh start
启动应用:
bash
scripts/build.sh run
或直接运行:
bash
./build/content_interaction_device
其他脚本:
bash
scripts/build.sh build
scripts/build.sh run
scripts/build.sh start
scripts/build.sh stop
scripts/build.sh restart
scripts/build.sh status
如果日志提示 /dev/input/eventX 权限不够,需要使用有 input 设备读取权限的用户运行,或把当前用户加入 input 组后重新登录。/dev/hidg0 不存在时,程序会继续运行但不会向游戏机转发触摸,需要先执行 sudo scripts/hid/hid_touch.sh start。
3.4 USB Gadget
bash
sudo scripts/hid/hid_touch.sh start
sudo scripts/hid/hid_touch.sh stop
当前触摸 HID 报告:
report_length=62- 第 0 字节为
Report ID,当前固定为0x01 - 第 1 字节为
Contact Count,表示当前报告中的触点数量 - 后续为 10 个固定触点槽,每个触点槽 6 字节:状态、触点 ID、X 低字节、X 高字节、Y 低字节、Y 高字节
应用侧 hid_touch_report_codec::encode_multi_touch_report() 输出格式与 scripts/hid/hid_touch.sh 的描述符一致。
四、维护说明
4.1 命名规范
项目自定义类、函数、变量和文件名统一采用下划线命名,例如:
app_configmain_windowinteraction_uitouch_threadudev_event_monitorvideo_pipe_threadv4l2_devhardware_video_presenterdrm_device_finderdrm_fb_cachedrm_plane_presenter
Qt、V4L2 等外部 API 保留原始命名,例如 QMainWindow、showEvent()、VIDIOC_DQBUF。
4.2 代码审查
架构审查、单一职责、代码复用、模块化设计和命名规范的详细建议见:
bash
docs/code_review.md
4.3 后续方向
4K 竖屏 2160x3840@60 场景下,后续优化仍围绕硬件显示链路展开:减少 Qt 参与主视频帧搬运,保持 HDMI IN 帧沿 DMA-BUF / DRM 路径提交,并继续验证不同 RK3588 内核和显示拓扑下的 Plane 选择、缩放和恢复策略。
4.4 免费 AI 注释助手
仓库已预置一套免费的线上 AI 注释方案,使用 VS Code Continue 插件配合 OpenRouter 免费模型。