鸿蒙投屏实现

在做鸿蒙 HDC 桌面应用 ECHO 时,希望像 ADB 版一样提供投屏功能。研究了下,目前在鸿蒙上并没有像 Android Scrcpy 一样的开源实现,无法直接拿来使用。想着官方是不是有提供类似功能,查找了下的确存在一个 IDE 测试插件 Hypium 封装了投屏功能。果断下载安装上,试用了下,虽然没有跟 Scrcpy 一样的流畅度,效果也还过得去,便想着能不能将这个功能在 ECHO 上也复刻下。

Hypium 插件投屏原理

首先找了下这个 Hypium 插件有没有开源,没有找到相关仓库,那就只能通过其它方法来挖掘下了。

不管投屏是什么方法实现的,它总要跟手机通信,这个大概率是通过 HDC 工具来实现的。然而,在启动投屏时,通过抓包(HDC 默认端口 8710)却没有看到大量类似视频流或图片的数据。没有通过 HDC Server 传递数据,那有可能是像 Android Scrcpy 那样先在手机上启动了某个服务端,再通过 HDC 的端口映射功能直接与服务端进行通信。执行下 hdc fport ls,果然看到了一个映射到 8012 端口的项。那么问题来了,这个监听 8012 的服务端是怎么来的?

联想到 Android Scrcpy 会往手机上推文件然后启动的情况,执行 hdc shell "ls /data/local/tmp" 查看目录下确实有个叫 agent.so 的文件。为了验证下这个文件是否确实是投屏功能推过来的文件,我将这个文件给删除掉同时重启了手机,然后再次重新启用投屏功能。通过抓包 HDC 可以发现投屏时确实推送了 agent.so 的文件,同时还能看到执行了一次 uitest start-daemon singleness,这应该就是启动这个 so 的命令了。uitest 工具是有开源的,翻看了下,在其源码中也确实能够找到有关 agent.so 的执行代码。

cpp 复制代码
bool ExecuteExtension(string_view version, int32_t argc, char *argv[])
{
    // ... 
    const char *name = "agent.so";
    if (argc > 1 && string_view(argv[0]) == "--extension-name") {
        name = argv[1];
        used_argc = TWO; // argv0,1 is consumed, donot pass down
    }
    string extensionPath = string("/data/local/tmp/") + name;
    // ...
}

总结下投屏大概的流程:

  1. 推送 agent.so 到 /data/local/tmp/ 目录下
  2. 执行 uitest start-daemon singleness 加载 agent.so 启动服务
  3. 使用 HDC 端口映射到 8012
  4. 跟 8012 端口建立连接开始互传数据

投屏协议解析

这个就比较简单直接了,直接抓包映射到 8012 的端口,分析下传递的数据包。

首先是数据包的格式,抓包后直接就能发现每条消息的开头跟结尾都有明确的标识符:_uitestkit_rpc_message_head__uitestkit_rpc_message_tail_。中间除了实际的消息数据外前边还有八个字节,前四个字节通过发送跟回传的消息一样可以推测出是 id 之类用于标识响应哪条命令,后四个字节则应该是数据的长度,转十六进制到十进制,计算到 tail 的长度果然对得上。

再分析下投屏时发送的具体内容,可以发现投屏前会先发送开始截屏的指令,大概格式如下:

json 复制代码
{
  "module": "com.ohos.devicetest.hypiumApiHelper",
  "method": "Captures",
  "params": {
    "api": "startCaptureScreen",
    "args": {
      "options": {
        "scale": 0.5
      }
    }
  }
}

之后客户端画面发生变动时会回传一系列的 jepg 格式截图,把这个截图不断绘制出来就可以了。

具体实现

由于 ECHO 是 electron 应用,因此需要在 Node.js 上实现这整个流程。为了复用和方便维护,我将这个封装在了 hdckit 中,只要安装这个包后直接调用相关接口即可。

javascript 复制代码
;(async () => {
    const target = clinet.getTarget(connectKey)
    const uiDriver = await target.createUiDriver()
    await uiDriver.stopCaptureScreen()
    uiDriver.startCaptureScreen(function (image) {
        // 拿到图片数据后做处理
    })
})()

最后只要将图片数据绘制到 canvas 上即可,最终的效果如下图:

相关推荐
lili-felicity3 小时前
React Native 鸿蒙跨平台开发:LayoutAnimation 实现鸿蒙端按钮点击的缩放反馈动画
react native·react.js·harmonyos
哈__5 小时前
React Native 鸿蒙跨平台开发:Dimensions 屏幕尺寸获取
react native·华为·harmonyos
奋斗的小青年!!5 小时前
Flutter跨平台开发适配OpenHarmony:手势识别实战应用
flutter·harmonyos·鸿蒙
搬砖的kk6 小时前
Cordova 适配鸿蒙系统(OpenHarmony) 全解析:技术方案、环境搭建与实战开发
华为·开源·harmonyos
不爱吃糖的程序媛6 小时前
OpenHarmony 通用C/C++三方库 标准化鸿蒙化适配
c语言·c++·harmonyos
程序猿追6 小时前
鸿蒙PC应用开发深度实战:一次开发、多端适配的沉浸式音乐播放器迁移实践
华为·harmonyos
行者967 小时前
Flutter跨平台开发:安全检测组件适配OpenHarmony
flutter·harmonyos·鸿蒙
小雨下雨的雨8 小时前
Flutter 框架跨平台鸿蒙开发 —— GridView 控件之多维网格美学
flutter·华为·交互·harmonyos·鸿蒙系统
贺今宵8 小时前
electron-vue无网络环境,读取本地图片/文件展示在页面vue中protocol
前端·javascript·electron
xiaoyan20158 小时前
2026原创Electron39.2+Vue3+DeepSeek从0-1手搓AI模板桌面应用Exe
vue.js·electron·deepseek