鸿蒙投屏实现

在做鸿蒙 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 上即可,最终的效果如下图:

相关推荐
勿念4362 小时前
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
华为·harmonyos
在人间耕耘3 小时前
unipp---HarmonyOS 应用开发实战
华为·harmonyos
libo_20254 小时前
语音交互设计:为HarmonyOS5应用添加多模态控制方案
harmonyos
kangyouwei4 小时前
鸿蒙开发:18-hilogtool命令的使用
前端·harmonyos
全栈若城4 小时前
105.[HarmonyOS NEXT 实战案例:新闻阅读应用] 高级篇 - 高级布局技巧与组件封装
harmonyos
rookiefishs4 小时前
如何控制electron的应用在指定的分屏上打开🧐
前端·javascript·electron
zhanshuo4 小时前
弱网也不怕!鸿蒙分布式数据同步的4大“抗摔“秘籍,购物车实战解析
harmonyos
勿念4365 小时前
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
harmonyos
Georgewu13 小时前
【HarmonyOS 5】鸿蒙中Stage模型与FA模型详解
harmonyos