【鸿蒙PC GUI适配】chiaki-ng 移植的工程实践:Qt6/QML 与 ArkUI 的混合窗口架构

欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目: https://atomgit.com/OpenHarmonyPCDeveloper


摘要

chiaki-ng 是 PS Remote Play(PS5 串流)的开源客户端:GUI 是 Qt6/QML(几万行 QML + C++ 会话/手柄逻辑),底层是 C 写的 chiaki-lib(ffmpeg 解码 + 自定义协议栈)。把它移植到鸿蒙 PC,最大的难点不是"能不能编译",而是**"一个 Qt 应用怎么变成一个鸿蒙应用"**------鸿蒙应用必须打包成 HAP(ArkUI),而把上游 QML 全部重写成 ArkTS 等于重写整个应用。

本文记录我们选的混合架构路线,以及过程中四类"平台敏感"问题的解法:

  1. 架构:Qt 窗口装进 ArkUI,而不是重写。 ArkUI XComponent 承载 Qt 的 HarmonyOS 平台插件 qohos,Qt 在原生窗口里用 Vulkan 渲染,上游 QML UI 一行不改全部复用;ArkUI 层只做系统壳 + 剪贴板/状态叠加按钮,两层之间用文件桥通信(免权限)。
  2. 构建:CMake 不认 HarmonyOS、Qt 6.12 要打 30+ 文件补丁、ffmpeg NEON 静态链接、.so 还要签名------而 hvigor 打包时会把签名剥掉。 每一步都是一个坑,每一条都只有踩过才知道。
  3. 系统能力:SDL 音频在 OHOS 上必然初始化失败 (SDK 的 SDL 没有 OHOS 后端)→ 改用系统 Audio Kit(OHAudio OH_AudioRenderer);视频走 OH_AVCodec 硬解;手柄盲区键(R1/PS)靠系统键码桥合成 SDL 事件。
  4. 最花时间的其实是"看起来和代码无关"的网络问题。 串流每 ~18 秒顿挫一次:用 hilog + 内核 kmsg + 逐包 ping 三方对齐,定位到 PC 侧 Wi-Fi 子系统(Wifi2/MultiSta 增强)每 18.0 秒重连一次主接口,产生 100--305 ms 纯时延尖峰而 ICMP 0% 丢包 ;另一次"声音卡顿"用 PS5 切有线的对照实验(音频欠载率 100% → 0.07%)证明根源在 PS5 侧 Wi-Fi 链路。

项目地址:


目录

  • [1. 项目概览](#1. 项目概览)
  • [2. 为什么是"Qt 装进 ArkUI",而不是重写?](#2. 为什么是"Qt 装进 ArkUI",而不是重写?)
    • [2.1 不能重写的理由](#2.1 不能重写的理由)
    • [2.2 混合架构:ArkUI 当壳,Qt 当内核](#2.2 混合架构:ArkUI 当壳,Qt 当内核)
  • [3. 构建系统:四个连环坑](#3. 构建系统:四个连环坑)
    • [3.1 CMake 不认 HarmonyOS](#3.1 CMake 不认 HarmonyOS)
    • [3.2 Qt 6.12 三件套补丁(30+ 文件)](#3.2 Qt 6.12 三件套补丁(30+ 文件))
    • [3.3 ffmpeg 静态链接 + 签名](#3.3 ffmpeg 静态链接 + 签名)
    • [3.4 部署](#3.4 部署)
  • [4. 系统能力接入](#4. 系统能力接入)
    • [4.1 音频:SDL 无 OHOS 后端 → OHAudio(系统 Audio Kit)](#4.1 音频:SDL 无 OHOS 后端 → OHAudio(系统 Audio Kit))
    • [4.2 视频:OH_AVCodec 硬件解码](#4.2 视频:OH_AVCodec 硬件解码)
    • [4.3 手柄:系统键码桥 + GCK + 右摇杆条件器](#4.3 手柄:系统键码桥 + GCK + 右摇杆条件器)
    • [4.4 剪贴板:免权限文件桥 + ArkUI 叠加按钮](#4.4 剪贴板:免权限文件桥 + ArkUI 叠加按钮)
  • [5. 平台深度适配](#5. 平台深度适配)
  • [6. 网络问题的工程排查(最花时间、也最值得讲)](#6. 网络问题的工程排查(最花时间、也最值得讲))
    • [6.1 现象与手法](#6.1 现象与手法)
    • [6.2 根因:Wifi2 增强反复重连主接口](#6.2 根因:Wifi2 增强反复重连主接口)
    • [6.3 另一个坑:声音卡顿 ≠ 网络丢包](#6.3 另一个坑:声音卡顿 ≠ 网络丢包)
  • [7. 已知问题与后续](#7. 已知问题与后续)

1. 项目概览

项目 信息
目标平台 HarmonyOS PC(OpenHarmony-7.0.0.105,API 26,aarch64,2in1)
编译器 OHOS SDK llvm clang(ohos.toolchain.cmake)
语言/构建 C(chiaki-lib)+ C++/QML(Qt 6.12)+ ArkTS(HAP 壳);CMake + hvigor
上游版本 chiaki-ng(持续跟随上游 main)
核心依赖 Qt 6.12(qtbase / qtdeclarative / qtshadertools)、ffmpeg 7.1.1、SDL2 2.30、glslang、libplacebo;OHOS NDK(libohaudio / OH_AVCodec / GCK)
链接方式 libchiaki.so 动态库;ffmpeg 静态链接(NEON,-Wl,-Bsymbolic)
产物 HAP(约 17MB,含签名后的 libchiaki.so)
构建状态 ✅ 可连接 PS5 串流并运行游戏(视频硬解 60fps / 音频 OHAudio / DS5 手柄全键位)
移植周期 2026-09 起持续迭代

2. 为什么是"Qt 装进 ArkUI",而不是重写?

2.1 不能重写的理由

chiaki-ng 的 UI 不是"一个界面",而是一整套完整的 Qt6/QML 应用:串流会话管理、手柄按键映射、设置页、自动发现 PS5、延迟/丢包监控面板......全部在 QML + C++ 里,总代码量以万行计。纯 ArkTS 重写 = 重写整个应用,而且上游还在持续更新,重写版本永远追不上。

2.2 混合架构:ArkUI 当壳,Qt 当内核

鸿蒙 PC 应用必须是 HAP(ArkUI 页面 + 可选 native 组件)。于是反过来想:ArkUI 只提供一页 XComponent,把整个 Qt 应用嵌进去。

复制代码
┌─────────────────────────────────────────┐
│ ArkUI 页面(MainWindowNativeNode.ets)    │
│  ┌───────────────────────────────────┐  │
│  │ XComponent(libraryname='qohos')   │  │
│  │  ┌─────────────────────────────┐  │  │
│  │  │ Qt native 窗口(Vulkan)      │  │  │
│  │  │  QML 全部 UI(上游原样复用)    │  │  │
│  │  └─────────────────────────────┘  │  │
│  │  ┌─ 叠加按钮(Paste / 状态条)───┐  │  │
│  └───────────────────────────────────┘  │
└─────────────────────────────────────────┘
  • qohos 是 Qt 的 HarmonyOS 平台插件(类似 Qt 的 linuxfb/xcb 后端),在 XComponent 里创建 native 窗口;
  • Qt 渲染走 Vulkan :nativeResourceForWindow("vkSurface", window) 拿到 VkSurfaceKHR,配合 setVulkanInstance 起渲染;
  • 音视频/手柄全部走系统 NDK(OHAudio / OH_AVCodec / GCK),不进 ArkUI;
  • 两层 UI 的协作用"文件桥" :Qt 侧把会话状态、剪贴板内容写到 $HOME/chiaki_*.txt,ArkUI 侧 300ms 轮询 + 叠加按钮回写------全程零权限申请(见 §4.4)。

这条路绕不开的三个难点:

难点 坑
qohos 平台插件 Qt 官方没有 OHOS 后端,qohos 是移植时自补的插件:EGL/Vulkan surface 创建、键码映射、剪贴板告警降噪都要单独处理
QML ↔ C++ 可见性 Q_INVOKABLE 声明在 private: 段 QML 调用会静默 TypeError,必须放 public: ;且 Qt 报错走 Qt category 而非 chiaki 自己的 tag,hilog -T chiakiohos 过滤会把它藏掉------排查时要按 domain 过滤
聚焦/叠加层协作 ArkUI 的按钮叠在 Qt 窗口上,串流中要自动隐藏(!Chiaki.session 判定),否则挡视线/误触

3. 构建系统:四个连环坑

3.1 CMake 不认 HarmonyOS

CMAKE_SYSTEM_NAME 没有 HarmonyOS 这个值,只能用 ohos.toolchain.cmake + 一个平台补丁模块目录(CMAKE_MODULE_PATH)让 cmake 认识 OHOS------否则 FindXxx.cmake、库路径检测全会给出错误结果。这条与 Neovim 移植(同系列上一篇)踩的是同一个坑。

3.2 Qt 6.12 三件套补丁(30+ 文件)

组件 补丁内容
qtbase 27 个文件:CMake/configure 的 OHOS 平台判定、各模块 CMakeLists、qhash.cpp 的 ARM AES 内建守卫、3 个 qohos 插件文件(键码告警降噪/剪贴板降噪/EGL 句柄 nullptr→0)、src/corelib/private/ 下 8 个软链
qtdeclarative qv4compilationunitmapper_unix.cpp + qv4functiontable_noop.cpp(无 JIT )+ OSAllocatorPosix.cpp
qtshadertools 内置 glslang 的 OSDEPENDENT 路径归到 Unix

3.3 ffmpeg 静态链接 + 签名

  • ffmpeg 以静态库方式链进 libchiaki.so(含 NEON 汇编),必须 -Wl,-Bsymbolic,否则符号冲突;
  • 鸿蒙要求所有 ELF/.so 合法签名才能加载:llvm-objcopy --remove-section=.codesign 剥掉旧段 → binary-sign-tool sign -selfSign 1(注意 outFile 必须预先存在);
  • hvigor 打包 HAP 时会剥掉 .so 的签名且不重新签名------所以签名必须在进 HAP 之前独立完成,这个坑不写下来没人知道。

3.4 部署

sh 复制代码
devecocli run --module entry@default --device "HUAWEI MateBook Pro" --build-mode debug
hdc shell "aa force-stop org.streetpea.chiaking; aa start -a QAbility -b org.streetpea.chiaking"

4. 系统能力接入

4.1 音频:SDL 无 OHOS 后端 → OHAudio(系统 Audio Kit)

  • 现象 :SDL_Init(SDL_INIT_AUDIO) / SDL_OpenAudioDevice 在 OHOS 上必然失败------SDK 的 SDL 2.30 没有 OHOS 音频后端,沙箱里也没有 ALSA/dsp 节点;
  • 修复 :gui/src/ohosaudioout.cpp 用 OH_AudioRenderer(libohaudio.so)实现 SDL 队列式音频 API 的输出半边(Open/Close/Pause/Queue/QueuedBytes/ClearQueue/GetStats),在 streamsession.cpp 用宏把 SDL_OpenAudioDevice/QueueAudio/... 重定向过去;AUDIOSTREAM_USAGE_GAME、S16LE、48kHz;
  • 低延迟快路径 :默认走 fast mode(OH_AudioRenderer_GetFastStatus 判定),回调粒度 5ms、设备延迟 25ms;CHIAKI_OHOS_AUDIO_LATENCY=normal 可切回普通路径(回调 93ms、延迟 45--93ms);
  • 实测稳态 [ohos-audio] under 计数平(无欠载),总延迟 ≈ 2×设置里的 Audio Buffer Size + 25ms。

4.2 视频:OH_AVCodec 硬件解码

  • 软件解码 CPU 86.6%、端到端延迟 60--280ms → lib/src/ffmpegdecoder.c 增加 OHOS 硬解路径(NV12 拷贝/队列/墙钟 pts/统计日志):CPU 降到 50% 、稳定 60fps 、延迟 20--80ms。

4.3 手柄:系统键码桥 + GCK + 右摇杆条件器

  • R1/PS 盲区键 :系统 key event 经 qohos 转成 QKeyEvent(nativeVirtualKey() 保留原始键码)到达 Qt 层,C++ 侧拦截后合成 SDL 事件------不必重编 qohos 也能补全键位;
  • GCK 桥 :OhosGamepadBridge 对接系统手柄服务;顺手修了 ControllerManager::IsConnected()(OHOS 上无手柄对象时误判断开→游戏内无输入);
  • 右摇杆手感 :内置条件器(死区 + 双指数平滑 + 中心校准,参数可热调),逐秒 [ohos-rstick] 日志量化:原始事件 1--16ms 间隔、rest≈0.488(不是 0.5)、修复前冷启动满杆放大 vs 修复后 slow-sweep max_step 0.005--0.03。

4.4 剪贴板:免权限文件桥 + ArkUI 叠加按钮

  • ohos.permission.READ_PASTEBOARD 是受限权限,自签名安装会失败 → 不做原生读剪贴板;
  • 改为:Qt 侧写 $HOME/chiaki_clipboard.txt,ArkUI 页面叠一个「Paste」按钮读取回填输入框;按钮只在非主屏页面显示、串流中自动隐藏 (setPasteOverlayWanted + 300ms 轮询),位置避开返回键。

5. 平台深度适配

项 做法
分层图标 make-icon.py 从仓库素材派生 foreground.png(透明)+ background.png(不透明)+ layered_image.json;AppScope 资源与模块资源同名时 AppScope 生效
启动动画 API 19+ startWindow 增强配置:startWindowIllustration(白字 logo,宽 26.6% 居中)+ startWindowBackgroundImage(Cover)+ 背景色;注意 AppIcon 与 Illustration 互斥 (图标优先),startWindowBrandingImage 渲染尺寸只有窗口 12.2% 宽(太小,弃用)
串流监控面板 [ohos-net](逐秒丢包/丢帧)、[ohos-fps](presented/dropped/队列年龄)、[ohos-render](每帧各阶段耗时)、[ohos-dec](解码帧率/漂移)------所有延迟问题都能从日志直接读数

6. 网络问题的工程排查(最花时间、也最值得讲)

6.1 现象与手法

串流时画面/声音周期性顿挫,但应用自报 packet_loss=0。我们把三条证据线对齐到同一时钟:

  1. hilog(app/core):Wi-Fi 子系统每 18.0 秒出现一次固定序列;
  2. 内核 kmsg(hilog -w start -t kmsg 开):驱动层同一时刻的动作;
  3. 逐包 ping (ping -i 0.2 网关)+ 应用自报的逐秒丢帧统计。

6.2 根因:Wifi2 增强反复重连主接口

复制代码
T+0.0s  WifiEnhanceInfoRequestor: RequestWifi2Network wifi2 is not active, SetWifi2Enable
T+0.1s  HalDeviceManager: CreateStaIface wlan1   ← 建第二条链路
T+1.8s  [kmsg] vap:0 wal_cfg80211_connect::start a new connect   ← 主口被重连!
T+2.0s  ping 尖峰 115--305ms(ICMP 0% 丢包)
T+7.5s  WifiServiceScheduler: AutoStopWifi2Service 1 success!
T+18.0s 下一轮
  • 触发链是华为"Wifi2/MultiSta 增强"(MultiStaStateMachine → GetChbaGroupInfo freq=5220 bw=3 clientNum=1),每次尝试都会顺手把主 Wi-Fi 接口(wlan0/vap:0)重连一次;
  • 驱动层 25 次主口重连中 23 次(92%)在 ±3s 内命中 ping 尖峰 ,尖峰簇间隔中位数 18.0s;
  • 现象是间歇性的:活动期 3.5--7 分钟(每 18.0s 一轮)↔ 静默期 22--27 分钟------这正是"有时卡有时不卡"的原因。

6.3 另一个坑:声音卡顿 ≠ 网络丢包

一次"声音卡顿"投诉,用对照实验定位:同 PC、同 Wi-Fi,唯一变量是 PS5 的接入方式------

PS5 接入 音频欠载率
Wi-Fi 100% (callbacks=4961 underruns=4961,每回调必欠载;视频却 60fps/0 丢包)
切有线瞬间 会话短暂断开(arkui stream state -> 0),ping 单次 146ms 尖峰
有线 0.07% (under=17 后 5 分钟不再增长)

结论:这次声音卡顿的根源在 PS5 与路由器的 Wi-Fi 链路 ,和 PC 的 Wifi2 churn 是两个独立问题 。方法论上的收获:排查串流卡顿要抓"时延尖峰"而不是只抓丢包------ICMP 0% 丢包 + 100--300ms 排队尖峰就足以让 60fps 串流可见顿挫。


7. 已知问题与后续

问题 症状 方案/状态
PC 侧 18s Wi-Fi 顿挫 每 ~18s 一次时延尖峰(主口被 Wifi2 增强重连) 已按华为要求提供全量 hilog + kmsg 取证包(含 hilog_dict);临时缓解:PC/PS5 尽量有线、关闭多设备协同/智能选网
声音卡顿 音频 100% 欠载 PS5 改有线(实测 100%→0.07%)
HAP 内 .so 失效 hvigor 剥签名 binary-sign-tool 独立签名后再进 HAP
第三方依赖补丁不在 git 换机器即丢 已全部收集到依赖目录 patches/(qtbase/qtdeclarative/qtshadertools/glslang/sdl2/libplacebo),README 记录逐文件说明

后续方向:向上游提 PR(qohos 平台插件、OHAudio 输出、OH_AVCodec 硬解均条件编译隔离,不影响其它平台)、继续压低端到端延迟、适配更多鸿蒙 PC 机型。


本文首发于【鸿蒙PC】系列专栏

最后更新:2026-09-25

文章版本:v1.0

相关推荐
Dream-Y.ocean10 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
行者-全栈开发10 小时前
【共创稿事节】HarmonyOS 应用上架全流程中的隐私合规:权限最小化、数据本地化与 AppAnalyzer 体检
harmonyos·鸿蒙·隐私合规·应用上架·agc·appanalyzer
七牛云行业应用10 小时前
DeepSeek Super-agent 与 Grok Bot:开源 Agent 架构和聊天助手有何区别
人工智能·ai·架构·ai编程
天远Date Lab11 小时前
零信任架构实战:基于天远租凭分期报告构建自动化汽车以租代购免押风控网关
网络·人工智能·架构·自动化
贾伟康11 小时前
HarmonyOS API 26 UKey PIN 授权门禁:状态查询、失败归因与资源闭环
华为·harmonyos
深圳行云创新14 小时前
智能体架构之争:单Agent加Skills,还是多加几个Agent
人工智能·架构·软件工程
这个DBA有点耶14 小时前
内核级AI与多模融合:AI数据库在海外场景的落地要点
数据库·人工智能·架构
FII工业富联科技服务16 小时前
工业智能体技术架构解析:六级部署、核心模块与制造场景实践
人工智能·架构·制造