# Harmony Connector:仅通过 HDC 的连续投屏 调研日期:2026-09-10。产品约束:最终用户只启用 USB 调试并授权 Mac,不注册开发者账号、不安装 HAP、不修改手机安全策略。目标是可交互的连续画面,不限定 H.264;帧率和延迟必须在真机上测量。本篇研究未操作设备。 ## 当前结论 找到了一条此前漏查的公开分发路线:**使用华为 Hypium 的公开 PyPI 依赖包中原样分发的 UITest agent,由设备内置 `uitest` 加载,通过 HDC 转发持续 JPEG 与触控 RPC。** 该路线无需用户编译、签名或安装手机应用;它仍会向设备临时推送一个 agent 文件,作用类似 scrcpy 的临时设备端服务。不能把自编 unsigned ELF 或 addon 被 XPM 拒绝,推广为所有官方签名 agent 均无法加载。 Hypium 的公开说明支持 USB/Wi-Fi 调试连接,并明确 Driver 模式可作为 SDK 集成到其他工具。其 `xdevice-devicetest` 包公开包含原生 agent;对应的 `xdevice-ohos` 包提供选择、推送、启动和转发实现。[Hypium 官方发布页](https://pypi.org/project/hypium/6.1.0.210/)、[设备测试资源包](https://pypi.org/project/xdevice-devicetest/6.1.0.210/)、[设备连接包](https://pypi.org/project/xdevice-ohos/6.1.0.210/) **主任务真机进展:**官方 `v1.2.2.so` 使用唯一 `--extension-name` 已成功加载,Unix socket 能返回 JPEG,`Driver.create`、屏幕尺寸和旋转查询成功;不需要安装 HAP、开发者账号或录屏确认弹窗。初次收到的是 630 × 1422 黑帧,首帧约 195–235 ms,15 秒仅收到 3 帧;当时可能处于锁屏状态,**这些数字只证明链路连通,不能作为动态画面流畅度或端到端延迟的结论**。本文作者未操作手机,真机结果来自主任务当轮报告。 ## 精确来源与许可证据 本轮从 PyPI 元数据取得 wheel 下载地址,在内存读取并校验 SHA256,没有安装或执行其中代码。 | 资源 | 版本、路径及 SHA256 | | --- | --- | | xdevice-devicetest wheel | `6.1.0.210`;`1c80f0da8d497d4e034f9873e966e8b2d8ef23ecc376c64c839c1493a813980e` | | 新版 ARM64 agent | `devicetest/res/prototype/native/uitest_agent_v1.2.2.so`;`e1b8e75fad983aa29640784ee4b457fe7ac1c916a15f72c8b899d5a7716da651` | | 旧版 ARM64 agent | `devicetest/res/prototype/native/uitest_agent_v1.1.10.so`;`1c9286456fb003ee15d86fef04e8c93f004027349e6dd2ef972c792e0a6d4bf8` | | xdevice-ohos wheel | `6.1.0.210`;`548a09bb6fc9fcfd92a7dbb5b69180de0159ce8959057e133dec34774054bb12` | 资源包的 `METADATA` 明确列出 `License-File: LICENSE`,`xdevice_devicetest-6.1.0.210.dist-info/LICENSE` 是完整 Apache License 2.0;本次清单未发现额外 EULA、NOTICE 或对 agent 的另行许可文件。Apache 2.0 提供商业使用及 object form 再分发条件;打包时保留该许可证、来源版本和校验值,保留现有版权声明,修改自有封装代码应说明。不要以第三方仓库的许可证替代原始发布包的许可。[原始 wheel](https://files.pythonhosted.org/packages/a2/b5/55bdfe4d669558331499848dbc68269506abb9b4a771e3524a65f9845127/xdevice_devicetest-6.1.0.210-py3-none-any.whl)、[Apache 2.0 条款](https://www.apache.org/licenses/LICENSE-2.0) 原始 `v1.2.2.so` 尾部包含 ELF 签名块标记,并含 Huawei CBG Software Signing Service CA 证书链字符串。这里只确认原件附带签名结构,**没有进行独立密码学验签,也没有据此声称目标设备必然接受**。部署必须保持字节不变,不 strip、不重链接、不截掉 ELF 尾部。该 wheel 没有提供 agent 的 C++ 源码,因此应称“公开分发的官方 agent”,不能称为设备端也能完全自行重建的开源实现。 ## 真实选择与启动路径 当前手机 UITest 版本由主任务报告为 `6.0.2.3`。官方 `xdevice-ohos 6.1.0.210` 内: - `ohos/environment/device.py::check_if_unix_socket_mode`:UITest 版本高于 `6.0.2.1` 且设备不是 x86_64 时使用 Unix socket。 - `ohos/proxy/proxy_base.py::_init_so_resource`:此分支选择 `uitest_agent_v1.2.2.so`;旧分支才使用 v1.1.10/v1.1.5/v1.1.3。 - `ohos/proxy/ui_proxy.py`:默认推送到 `/data/local/tmp/agent.so`,运行 `/system/bin/uitest start-daemon singleness`,连接 `localabstract:uitest_socket`。旧协议为设备 TCP 8012。 因此不能把“设备 6.x”硬编码为旧 TCP 8012,也不能只依据文件名相似选择第三方 agent。[对应公开 wheel](https://files.pythonhosted.org/packages/78/a6/8ac621bb958813cde648d515069c30188939b492ffcdd29e8d1e234550cf/xdevice_ohos-6.1.0.210-py3-none-any.whl) 以下最小命令形态已由主任务用本轮官方原件在当前手机验证;参数仍必须来自所选设备与本机会话: ```text hdc -t file send /data/local/tmp/ hdc -t shell uitest start-daemon singleness --extension-name hdc -t fport tcp: localabstract:uitest_socket ``` 独有 `--extension-name` 来自 OpenHarmony UITest 的扩展加载器:它将名称解析到 `/data/local/tmp/` 并加载扩展。官方 Python 默认使用 `agent.so`,独有名已在本轮商业手机上验证,其他固件仍需兼容验证。不要覆盖其他工具已有的 `agent.so`;官方框架内部的通配清理、强杀其他进程不应照搬到插件。[扩展加载器](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/extension_executor.cpp) ## 持续 JPEG 与控制协议 公开客户端已有实际代码实现,不依赖教程中的口头描述。本节以 `devhelmkit` 锁定提交 `05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00` 为协议参考,设备 agent 原件仍从官方 wheel 获取。该项目的 `device/hdc.py`、`rpc/proxy_v2.py`、`screenshot_stream.py` 分别实现传输、RPC 和连续画面。[传输实现](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/device/hdc.py)、[RPC](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/rpc/proxy_v2.py)、[连续帧](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py) 客户端发送请求的 TCP 封包顺序: ```text ASCII "_uitestkit_rpc_message_head_" u32 big-endian session ID u32 big-endian UTF-8 JSON byte length JSON body ASCII "_uitestkit_rpc_message_tail_" ``` 初始化和控制使用独立连接。调用 `Driver.create` 后使用返回的实际引用值,不能把 `Driver#0` 当成所有连接的固定保证。外层 `request_id` 为本次唯一字符串,transport session ID 是另一字段。 ```json { "module": "com.ohos.devicetest.hypiumApiHelper", "method": "callHypiumApi", "params": { "api": "Driver.create", "this": null, "args": [], "message_type": "hypium" }, "request_id": "" } ``` 后续只替换 `params` 中的 API、引用与参数,外层保持相同。以下是主任务确认的**解析后结果值**,不代表复制完整回包结构: | API | `this` / `args` | 当前手机结果 | | --- | --- | --- | | `Driver.create` | `null` / `[]` | `"Driver#0"` | | `Driver.getDisplaySize` | 返回的 Driver 引用 / `[]` | `{"displayId":0,"x":1260,"y":2844}` | | `Driver.getDisplayRotation` | 返回的 Driver 引用 / `[]` | `0` | | `Driver.wakeUpDisplay` | 返回的 Driver 引用 / `[]` | `null` | | `Driver.getDeviceInfo` | 返回的 Driver 引用 / `[]` | `No handler found`,当前手机不可用,不用于探活。 | 物理输入坐标应依据屏幕尺寸与旋转转换,不能直接使用 JPEG 缩小后的像素坐标。`getDisplaySize` 可作为独立控制连接的低频探活;无新图不等于连接已断。 在独立采集 socket 发送: ```json { "module": "com.ohos.devicetest.hypiumApiHelper", "method": "Captures", "params": { "api": "startCaptureScreen", "args": { "options": { "displayId": 0, "scale": 0.5 } } }, "request_id": "" } ``` 注意 `Captures.args` 是对象,与 `callHypiumApi.args` 数组不同。JPEG 被推送到发送采集请求的 socket。**当前官方 v1.2.2 真机回包实测为 JSON 行 `{"result":true,"pts":...}\n` 与裸 JPEG `FF D8 … FF D9` 加换行,不使用上述 HEAD/TAIL 请求封包包住 JPEG。**不能只实现带 HEAD/TAIL 的接收器。参考客户端兼容协议封装与裸 JPEG;实现应依据实际格式使用有界长度及完整帧解析,不能假设一次 `recv` 对应一张图,也不能忽略 ACK/错误后无限等待。[采集请求与分帧](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L677) 实时触控使用相同外层与 `method: "Gestures"`,内层为 `api: "touchDown" | "touchMove" | "touchUp"`、`args: {"x": ..., "y": ...}`。客户端实现按下、移动、抬起接口,适合替换拖动后才发送整段 swipe 的延迟;必须限制 move 排队,并保证取消、失焦时结束本会话已按下触点。键盘、文字可继续使用现有已验证的 UITest 输入路径。[触控实现](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/driver.py#L687) 未找到 `Gestures.keyDown`、`Gestures.keyUp` 或 `Gestures.text` 的公开调用依据。普通按键使用 `callHypiumApi`:`Driver.triggerKey` 的 `args` 为 `[keyCode]`;组合键 `Driver.triggerCombineKeys` 为 `[key1,key2]` 或三个键;Home/Back 分别是 `Driver.pressHome` / `Driver.pressBack`、`args:[]`。不要使用不存在的 `Driver.pressKey`。官方 `hypium/uidriver/arkuidriver.py` 声明 `triggerKey`、`triggerCombineKeys`、`pressBack`;其 Home 包装存在 `UiDriver.pressHome` 命名差异,因此仍应按当前设备能力验证客户端使用的 `Driver.pressHome`。[官方 Hypium wheel](https://files.pythonhosted.org/packages/b4/dc/f3b0abf2c518d11d57d8e9487026c133cc7eb0e3479af4b90d13c6db6169/hypium-6.1.0.210-py3-none-any.whl)、[客户端按键调用](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/driver.py#L712) 官方 Python 的 `Driver.inputText` 参数为 `[{"x":x,"y":y},"text"]`,有定位输入的语义;它并不是已确认的 `Gestures` 当前光标文字接口。公开客户端对当前焦点使用 `uitest uiInput text `。当前阶段保留已有验证的文字通道,可避免引入未经验证的光标、追加与清空语义。[当前焦点输入](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/driver.py#L1487) 停止时在采集连接发送 `method: "Captures"`、`params: {"api":"stopCaptureScreen","args":{}}`,随后关闭 socket、停止本机接收线程、移除本会话转发。若启用设备守护进程清理,仅结束自己创建并持有身份的进程,不使用 `killall uitest`。主机解码与渲染只保留最新完整帧;静止画面没有新帧不应误判断线,可用独立控制通道探活。[停止实现](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L565) `startCaptureScreen` 是 agent 提供的 RPC 名称,**不是**设备 `uitest help` 中能直接运行的 CLI 子命令。设备帮助没有它,不证明公开 agent 不能提供它。 ## 画面变化、帧率与保活 公开 UITest `screen_copy.cpp` 的采集线程调用 `DisplayManager::GetScreenshot`,通过 `memcmp` 比较前后像素,只有变化才通知 JPEG 编码线程。屏幕关闭时提供黑色画面并暂停常规采集,50 ms 的 `usleep` 仅用于检查关闭的屏幕是否重新亮起,**不能据此推算正常采集被限制在 20 fps**。常规采集循环没有显式 fps 计时上限,编码线程取最新帧。商业 agent 未提供其 C++ 源码,此处解释公开 UITest 实现,不能当作官方 v1.2.2 每一项内部行为均相同的证明;本机静止画面不连续发帧与这套行为一致。[变化检测与屏幕关闭](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp#L153)、[消费最新帧](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp#L245) 核查的公开扩展与客户端只暴露 `displayId`、`scale`,没有可据以实现的 fps 或 JPEG quality 参数。`scale` 在获取原屏截图后缩放像素,能降低输出尺寸与编码、传输成本,但不消除原屏截图成本。Devhelmkit 的 `MAX_RECORD_FPS=30` 只约束事后 MP4 合成,与实时采集上限无关。不要把虚构的 `fps:60` 参数加入产品后声称已提速。[缩放位置](https://github.com/openharmony/testfwk_arkxtest/blob/b04e30c40cf266c9abfbd52933e43a0456eb99a2/uitest/addon/screen_copy.cpp#L221)、[实际采集参数](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L670) 参考客户端的采集 socket 连接后设为阻塞读取,没有周期发送采集心跳;尚无“必须在采集 socket 定期发送心跳”的源码证据。可保留本机读取超时用来检查取消状态,但超时本身不应标为失败;用独立控制连接的 `Driver.getDisplaySize` 区分静态画面与设备失联。若控制连接也无响应、TCP 关闭或 HDC 确认失联,再终止会话。[socket 建立与持续读取](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/src/devhelmkit/harmony/screenshot_stream.py#L651) ## 两个开源客户端的来源核对 | 项目 | 本轮源码与文件核对 | 结论 | | --- | --- | --- | | `yabi-zzh/devhelmkit` | Apache 2.0;有连续 JPEG、独立控制 socket、down/move/up 和采集帧率统计;仓库 `agent_v2.so` SHA256 与本次官方 wheel 的 agent 不一致。 | 可参考其客户端协议,不把其未核清来源的 blob 作为发行原件。 | | `yiyifred/Cloud-Phone` | Apache 2.0;包含 `uitest_agent_v1.1.10.so`,其 SHA256 **完全匹配**官方 wheel v1.1.10;`Gestures` 代码注释中的 ECHO/hdckit 指同类 RPC 操作,并不是设备原生内置 H.264 服务。 | 证明实际客户端采用 Hypium agent;其旧 agent/TCP 假设不能直接用于当前设备。 | [devhelmkit 许可](https://github.com/yabi-zzh/devhelmkit/blob/05c9bc0f7f9ea763d61c8b4b5d1cfbb076626b00/LICENSE)、[Cloud-Phone agent](https://github.com/yiyifred/Cloud-Phone/blob/98d130b20474cf8b18d51161d3bb0b65472c4778/backend/assets/harmony/uitest_agent_v1.1.10.so)、[Cloud-Phone RPC](https://github.com/yiyifred/Cloud-Phone/blob/98d130b20474cf8b18d51161d3bb0b65472c4778/backend/node/src/services/harmony-cast/uitest-rpc.js)、[Cloud-Phone 许可](https://github.com/yiyifred/Cloud-Phone/blob/98d130b20474cf8b18d51161d3bb0b65472c4778/LICENSE) ## H.264 与其他路径的边界 同一官方资源 wheel 包含 `devicetest/res/recorder/libscrcpy_server1.z.so` 到 `libscrcpy_server4.z.so`。其 `controllers/tools/recorder/record_agent.py` 推送为 `/data/local/tmp/libscreen_recorder.z.so`,通过 UITest extension 启动,参数为 `-p -m 1 -screenId `,默认 gRPC 端口 5001;对应 `rpc_manager.py` 和生成的 protobuf 定义含 `ScrcpyService/onStart`、`onEnd`、`onRequestIDRFrame`。当前 Python 调用方主要用于步骤视频,停止后拉取 `mytest.mp4`。**仅凭资源名不能认定它是可直接使用的实时 H.264 投屏服务**;格式、stream payload 与当前固件兼容性仍需专门验证。它不是本轮 JPEG 路线的前置条件。[官方资源 wheel](https://pypi.org/project/xdevice-devicetest/6.1.0.210/) 设备内置 UITest 公开 CLI 的 screenCap 是单张 PNG,uiRecord 是操作记录,start-daemon 是测试服务入口;不提供已确认的直接视频 stdout 命令。官方系统录屏组件的公开流程是录制后导出文件。对已经确认不存在的 `screenrecord`,不应反复尝试 Android 参数。[UITest 命令](https://github.com/openharmony/docs/blob/f41b9345badd47c7ab0c263344cd7f4b5a549afb/zh-cn/application-dev/application-test/uitest-guidelines.md)、[官方系统录屏](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-screen-recording) 自编 ELF 的执行权限错误与自编 addon 的 XPM 加载拒绝已由主任务复现;它们不应作为继续尝试关闭 XPM、SELinux、root 或借用私人签名的理由。公开 Hypium 原件是另一条正常工具分发与加载链路,需按原件、版本、协议重新验证。 ## 产品验收与明确未知 - 当前证据确认公开来源、许可文件、官方选择和加载方式,以及开源客户端连续帧/触控协议;主任务已验证当前手机原件加载、Unix socket、Driver 初始化、JPEG 解码与屏幕尺寸查询。 - 初测黑帧只证明连通,仍需设备解锁后的真实内容与交互画面验证;不得把收到 JPEG 等同于已达到 scrcpy 的流畅体验。 - 用手机实际动画/滑动测量收到与呈现的帧率,独立记录排队及输入往返;静止画面频率不能当作最大帧率。没有实际结果前不承诺 30/60 fps。 - 验证拖动跟手、旋转后的物理坐标、拒绝/断开、停止后不再采集与控制,以及重复启动清理;不影响其他使用 UITest 的工具。 - 未确认 `Captures/Gestures` 跨版本稳定性;应固定已验收的 agent,报告能力与错误,避免无提示降为旧截图模式。 最终用户流程可以保持“连接 USB → 允许调试 → 开始投屏”。官方 agent 的获取、校验、部署、转发和清理应由插件完成,不能再把开发者账号或 HAP 签名作为该流程的要求。 ## 2026-09-10:主任务重连验证补充 实际商业手机拒绝普通 `kill `,返回 `Permission denied`;同一身份的 `kill -9 ` 成功,进程随即消失。此前残留代理能响应 Driver RPC,却无法恢复采集,因此不将无条件复用残留代理作为恢复策略。官方 `xdevice-ohos 6.1.0.210` 的 `ohos/proxy/ui_proxy.py::stop_ui_rpc` 与 `proxy_base.py::_kill_uitest` 均使用 `kill -9`;默认销毁代理对象可不杀进程,显式重连则先停止。插件只借鉴结束自己代理的方式,绝不照搬按名称批量结束所有工具进程的逻辑。[官方连接包](https://pypi.org/project/xdevice-ohos/6.1.0.210/) 生产后端现已连续通过三轮真机开始、解码 JPEG、停止和重新开始,每轮都验证代理和转发清理完成。正常画面 JPEG 宽度实测含尾部对齐填充(640 × 1422 对应逻辑半尺寸 630 × 1422),UI 应裁掉该部分,不能按 JPEG 宽度直接映射物理触点。该结果只证明本轮设备的连通与重连,不代表动态帧率、实时手势或其他固件兼容性全部验收。