自动化工具 硬货系列 (二)!!使用 python 实现高帧率、低延时、支持多个 iOS 设备同时屏幕共享的工具

YueChen · 2021年03月17日 · 最后由 YueChen 回复于 2024年02月29日 · 22682 次阅读
本帖已被设为精华帖!

前段时间发布了一个 python 获取 iOS 性能数据的文章,也算开了个小口子能在获取 iOS 测试数据上更加方便了些,如果对 iOS 性能相关兴趣可以移步:https://testerhome.com/topics/27159

屏幕共享和远控平台现在发展也比较火热,但是 iOS 设备在画面同步和视频录制上,一直都没有一个比较不错的方案,也简单调研了下之前可以获取到 iOS 屏幕数据的方法:

  • iOS-minicap stf 团队实现的帧率非常不错,缺点无法多台设备
  • Airplay mirror 苹果多媒体多屏互动技术,效果都很好,有一点点缺陷就是同一个 wifi 下每次需要手机主动触发屏幕镜像才可以同步画面
  • com.apple.mobile.screenshotr 协议,这个也挺慢的
  • XCTest 和 XCUITest 二次改造的 wda 可以支持多台设备,但是帧率延时还是偏低
  • 基于 WebRtc iOS 屏幕共享,这个需要 SDK 嵌入 APP 支持,而且需要端上主动触发,操作起来不是很方便。
  • 等等...

本项目介绍

该项目是 python 实现可以通过 USB 连接 iOS 设备进行屏幕共享,支持:

  • 高帧率(30〜60fps)
  • 高画质
  • 低延迟
  • 秒启动
  • 非侵入性 (无需任何安装和代码嵌入)
  • 支持 iOS 多设备并行

项目地址:https://github.com/YueChen-C/ios-screen-record 先点个小星星吧

Mac OSX 安装

  1. brew install libusb pkg-config
  2. 如需使用 gstreamer 媒体服务则需要安装 brew install gstreamer gst-plugins-bad gst-plugins-good gst-plugins-base gst-plugins-ugly
  3. python install -r requirements.txt

使用

usb 连接你的 iOS 手机,解锁并信任喲 (手机锁屏不行)

# 可以使用 vlc 工具播放udp地址: udp/h264://@:8880
# 直接转发 h264 到 udp 广播,因为 mac 限制 udp 大小,要切割包,所以延时会变高,暂时仅作为测试使用
$ main.py --udid=xxxx udp

# 录制 h264/wav 文件, 使用 vlc 工具打开文件
$ main.py --udid=xxxx record -h264File=/home/out.h264  -wavFile=/home/out.wav

# gstreamer 媒体流工具渲染显示画面,推荐方式
$ main.py --udid=xxxx gstreamer

基本原理

usb 相关说明

每个 usb 连接设备时都会有一些配置信息,我们数据交互时,会使用某个配置与 usb 设备进行交互,这里用个 iOS 设备举例:

当我们使用 LibUsb 这个库 https://libusb.info/ 获取 iOS USB 设备信息时可以获取到配置信息 bNumConfigurations 5 个, 下面部分信息片段:

DEVICE ID 05ac:12a8 on Bus 020 Address 031 =================
 bLength                :   0x12 (18 bytes)
 bDescriptorType        :    0x1 Device
 bcdUSB                 :  0x200 USB 2.0
 bDeviceClass           :    0x0 Specified at interface
 bDeviceSubClass        :    0x0
 bDeviceProtocol        :    0x0
 bMaxPacketSize0        :   0x40 (64 bytes)
 idVendor               : 0x05ac
 idProduct              : 0x12a8
 bcdDevice              : 0x1208 Device 18.08
 iManufacturer          :    0x1 Apple Inc.
 iProduct               :    0x2 iPhone
 iSerialNumber          :    0x3 *********************
 bNumConfigurations     :    0x5
 CONFIGURATION 1: 500 mA ==================================

如何开启隐藏配置

事实上在 iOS USB 级别上还有个隐藏配置信息,用来传输屏幕音视频相关数据,pyhton 开启方式 device.ctrl_transfer(0x40, 0x52, 0, 2, b'') 发送了这个指令之后,再次获取配置信息时,就会发现 bNumConfigurations 的数量变成了 6 个,多出来这个配置信息就是我们要使用的,使用这个 USB 配置,并连接相应端口后,就能传输音视频画面了

接口端点定位

虽然我们使用这个音视频传输配置,但是这个配置下面还有多个 INTERFACE 接口,但是只有 bInterfaceSubClass=0x2A 这个接口才是需要用的,因此要还需要定位到这个配置下,然后会看到 INTERFACE 下面还有两个端口 ENDPOINT 0x86: Bulk IN(用来接收数据) 和 ENDPOINT 0x5: Bulk OUT(用来发送数据),到此 usb 设置相关基本完成了

INTERFACE 2: Vendor Specific ===========================
    bLength            :    0x9 (9 bytes)
    bDescriptorType    :    0x4 Interface
    bInterfaceNumber   :    0x2
    bAlternateSetting  :    0x0
    bNumEndpoints      :    0x2
    bInterfaceClass    :   0xff Vendor Specific
    bInterfaceSubClass :   0x2a
    bInterfaceProtocol :   0xff
    iInterface         :   0x11 Valeria
     ENDPOINT 0x86: Bulk IN ===============================
      bLength          :    0x7 (7 bytes)
      bDescriptorType  :    0x5 Endpoint
      bEndpointAddress :   0x86 IN
      bmAttributes     :    0x2 Bulk
      wMaxPacketSize   :  0x200 (512 bytes)
      bInterval        :    0x0
     ENDPOINT 0x5: Bulk OUT ===============================
      bLength          :    0x7 (7 bytes)
      bDescriptorType  :    0x5 Endpoint
      bEndpointAddress :    0x5 OUT
      bmAttributes     :    0x2 Bulk
      wMaxPacketSize   :  0x200 (512 bytes)
      bInterval        :    0x0

如果想分析 usb 数据的话执行:sudo ifconfig XHC20 up 命令后使用 wiershark 抓网卡 XHC20 就可以看到 部分 usb 数据交互

开始传输数据

大概流程

  1. 启用隐藏设备配置信息
  2. 锁定开启传输端点
  3. 等待接收 PING 包
  4. 用 PING 包响应
  5. 等待 SYNC CWPA 数据包接收设备音频 时间戳 >>>开始音频交互
  6. 创建本地时间戳记录,将该时间戳放入 SYNC CWPA 并发送
  7. 发送 ASYN_HPD1(参数参考 ios 的 CoreAudio 框架)
  8. 发送 ASYN_HPA1(参数参考 ios 的 CoreAudio 框架)和在步骤 6 中接收到的设备音频 时间戳
  9. 接收同步 AFMT 并返回没有错误信号 (表示准备就绪)
  10. 接收 CVRP 视频 时间戳 >>>开始视频交互
  11. 使用本地视频 时间戳 回复
  12. 使用步骤 10 的时间戳 发送 NEED 消息
  13. 接收两个 ASYN
  14. 接收 CLOK 消息,创建新的时间戳记录并回复消息
  15. 接收 TIME 消息,使用 14 步创建的时间回复消息

前面交互完成后就能正式接收音视频消息了

如果想具体了解相关传输报文协议可以查看下方链接,本项目是参考这个大佬文章,最终使用 python 来实现的 https://github.com/danielpaulus/quicktime_video_hack/blob/master/doc/technical_documentation.md

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 34 条回复 时间 点赞
陈恒捷 将本帖设为了精华贴 03月18日 00:05

赞,已 star

这个会有什么限制么,比如 iOS 版本之类的?

陈恒捷 回复

测试 10 ~ 14,目前都可以连接传输数据

大佬能看下这个错误怎么解决吗😥 ,试着拿一台 iOS12.0.1 的 6sp 跑了一下一直报 Resource busy

$ python3 main.py -u xxxx udp
2021-03-18 15:21:28,603 - util.py[line:53] - INFO: Find Device UDID: xxxx
2021-03-18 15:21:28,604 - consumer.py[line:100] - INFO: listen UDP: udp/h264://127.0.0.1:8880
2021-03-18 15:21:28,604 - util.py[line:84] - INFO: Enabling hidden QT config
Traceback (most recent call last):
  File "main.py", line 60, in <module>
    main()
  File "main.py", line 56, in main
    args.func(args)
  File "main.py", line 21, in cmd_record_udp
    start_reading(consumer, device, stopSignal)
  File "/home/curtain/ios-screen-record/screen/util.py", line 147, in start_reading
    device.set_configuration()
  File "/home/curtain/.local/lib/python3.6/site-packages/usb/core.py", line 905, in set_configuration
    self._ctx.managed_set_configuration(self, configuration)
  File "/home/curtain/.local/lib/python3.6/site-packages/usb/core.py", line 113, in wrapper
    return f(self, *args, **kwargs)
  File "/home/curtain/.local/lib/python3.6/site-packages/usb/core.py", line 159, in managed_set_configuration
    self.backend.set_configuration(self.handle, cfg.bConfigurationValue)
  File "/home/curtain/.local/lib/python3.6/site-packages/usb/backend/libusb1.py", line 812, in set_configuration
    _check(self.lib.libusb_set_configuration(dev_handle.handle, config_value))
  File "/home/curtain/.local/lib/python3.6/site-packages/usb/backend/libusb1.py", line 604, in _check
    raise USBError(_strerror(ret), ret, _libusb_errno[ret])
usb.core.USBError: [Errno 16] Resource busy
Curtain 回复

重置配置确实有一定概率失败,尤其是第一次,多试几次,要不重启手机下试试?

太强了吧,支持,follow 到天涯海角😝

太猛了,兄弟,有没有兴趣,再搞个 Android 的?

JamesChung 回复

Android 这个就很棒了
https://github.com/Genymobile/scrcpy

仅楼主可见

期待个 win 版的

期待个 win 版的 +1,如果能在 Windows 上安装获取 ios 的手机屏幕就好了

想咨询下大神,这个只能通过 vlc 工具进行播放,不能像 stf 那样直接通过浏览器访问?

kisom 回复

这个现在只是提供了一个底层能力,vlc 播放的就已经是 h264 裸流了。如果你想用浏览器访问,就自建个什么媒体服务器把这个 h264 流转发到 web 前端播放就好了。

本地设备在播放音乐时经常出现 [Errno 5] Input/Output Error 导致的视频转发或录制停止, 不知道大神遇到过这种情况么?

Tiper 回复

我测试了下用 gstreamer 模式和 record 模式测试了下 qq 音乐,和一些本地一些视频,看起来都挺正常的。
运行时长?或者特殊音频数据可以给我测试下。

我把 quicktime hack 移植到 windows 平台了(这是作者本人都没搞定的工作),并且用 qt+ffmpeg 重写了显示客户端。可以流畅播放,可惜这里不能贴视频。群主可以搜索一下有个工具叫 “虫洞”,但是它用的是 libusb-win32 这个驱动,我还是用的 winusb 驱动。

prife 回复

不开源有卵用

prife 回复

windows 上我也实现了,不过需要重置 Apple 的驱动成 libusb 或者 winusb 确实可以录制和同屏
但是有个问题就是驱动重置之后 iTunes 就找不到 iPhone 设备了,会导致其他功能不能用了,iPhone 本身驱动又不开源,这就很恶心了

prife 回复

“虫洞” 你说的这个工具我去试了下,同样会影响 iTunes 正常使用

22楼 已删除
23楼 已删除

这个我处理下,不需要 gstreamer 服务的话不加载

YueChen 回复

windows 能提供参考下吗?最近也在研究这个,一直没有进展

woshi123 回复

先提供方法吧,使用 https://zadig.akeo.ie/ 将 iPhone 驱动给替换成 libusb 或者 winusb ,然后使用对应的三方库,同样还是上述流程,只不过中间有些三方库的使用区别。

能支持跨平台不?必要用 macos?win 上可以不

冒个泡泡 回复

看 windows 分支

YueChen 回复

有办法不重置 USB 吗?我一直没解决这个问题。我发现只要 quicktime 启动录制设备时,就可以不重置 USB 也能录制视频。但是不知道如何处理多个 iOS 设备录制

zhangpei 回复

重置 USB 会导致 WDA 服务挂掉。。。

zhangpei 回复

重置之后不关闭就好了,保持激活状态,需要录制的时候主动发个 ping 消息就可以了

pip_.zip/pip install --ignore-installed --no-user --prefix /private/var/folders/km/8hs4jth149jbtyzq3j66c68h0000gn/T/pip-build-env-twbzbepb/overlay --no-warn-script-location --no-binary :none: --only-binary :none: -i https://pypi.org/simple -- setuptools wheel pycairo Check the logs for full command output.
Collecting ioscreen
Using cached ioscreen-1.0.0.dev9-py3-none-any.whl (24 kB)
Using cached ioscreen-1.0.0.dev4-py3-none-any.whl (22 kB)
ERROR: Cannot install ioscreen==1.0.0.dev4, ioscreen==1.0.0.dev9 and ioscreen==1.1.0.dev9 because these package versions have conflicting dependencies.

The conflict is caused by:
ioscreen 1.1.0.dev9 depends on PyGObject==3.38.0
ioscreen 1.0.0.dev9 depends on PyGObject==3.38.0
ioscreen 1.0.0.dev4 depends on PyGObject==3.38.0

To fix this you could try to:

  1. loosen the range of package versions you've specified
  2. remove package versions to allow pip attempt to solve the dependency conflict

ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/user_guide/#fixing-conflicting-dependencies
WARNING: You are using pip version 21.2.4; however, version 21.3.1 is available.
You should consider upgrading via the '/usr/local/opt/python@3.9/bin/python3.9 -m pip install --upgrade pip' command.

有方法骗过手机,认为另一端是 Mac 吗?其实是其他的 os

我试了下 centos 7.9 下,没法获取到视频。ping 包发送出去之后,就没有任何数据返回了

iOS 的 usb 投屏,需要一个 windows 端,有没有大佬接单的?条件优厚

YueChen 回复

录制保存的 wav 播放不了吗?我看 gstreamer 上是要加 wav 头的 和 pps ppt 的,record 直接写到一个后缀是.wav 的文件,这个不行吧

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册