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

目录
- [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。我们把三条证据线对齐到同一时钟:
- hilog(app/core):Wi-Fi 子系统每 18.0 秒出现一次固定序列;
- 内核 kmsg(
hilog -w start -t kmsg开):驱动层同一时刻的动作; - 逐包 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