起初我真的想过自己单独写一套来着,后来发现时间真的不够,所以有了对scrcpy源码的浅析,服务端我就用scrcpy现有的了,客户端scrcpy采用ffmpeg+sdl2.0进行了跨平台的播放,我准备用Flutter重构客户端部分

Scrcpy与vysor是都是投屏中比较优秀的项目了,非侵入性,不需要设备单独安装软件来配合,能低延迟的控制,较高的fps等等,scrcpy的star更是到了2.6w

scrcpy启动阶段

它到底是怎么做到执行scrcpy命令,在较短的时间内就立马获取到了安卓设备的屏幕的?并没有向设备申请任何的获取屏幕的权限,并且还能对设备进行较低延迟的控制。 有过使用adb经验的开发者,当PC端调试安卓设备时 我们可以输入

adb shell /system/bin/screencap -p PATH

复制代码

就能直接截取手机屏幕,去掉-p这个开关,更改成>,就可以直接截图并重定向到电脑本地,包括使用screenrecorder命令对手机进行录屏。

以上两个操作明明是会用到截取手机屏幕权限的,但是为什么没有向用户申请就能获取到屏幕?

在scrcpy的wiki中也其实提到了,原因就是adb shell的权限是非常高的,去设备的/system/app/shell也能看到shell.apk的uid与gid都是shell,这就可以直接拿到屏幕截取的权限 就好像uid与gid都为root的su命令,可以拿到一切权限一样的。

所以在scrcpy启动时,将自身sdk中的一个jar上传到了安卓设备上,这个jar并不是java的.class文件,是class java字节码经过dx工具转换成了dex文件,所以这个jar解压后就有一个dex,这个是安卓上的字节码,可以直接运行的。

push jar到手机

adb push $sdk/scrcpy-server.jar /data/local/tmp

复制代码

再利用安卓的app_process,直接启动这个jar,不仅是app_process,dalvikvm理论也是能启动的 命令如下

CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process ./ com.genymobile.scrcpy.Server 1.12.1 0 8000000 0 true - true true

复制代码

这样就会 后面的那堆参数是我从源码抠出来的默认参数,也是单独执行scrcpy会跟的参数,这些参数就会由com.genymobile.scrcpy.Server类的main函数接收到 main函数收到参数遍会开启两个socket等待客户端来连接本设备,一个是视频流的socket,一个是设备控制的socket

这个socket为什么能被pc端连接到?

由于adb提供了端口转发的功能,能转发设备本地的端口到pc端,pc端就能跟这个转发的端口进行连接并收发数据。 需要android版本大于5.0 转发端口:

adb forward tcp:5005 localabstract:scrcpy

#PC上所有5005端口通信数据将被重定向到手机端UNIX类型localabstract上

复制代码

上面部分也是所有这类投屏软件的原理,包括vysor

scrcpy运行阶段

上面提到一点,scrcpy的jar在设备上就作为一个服务端,会创建一个本地视频流的socket等待客户端连接,如下

public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {

LocalSocket videoSocket;

LocalSocket controlSocket;

if (tunnelForward) {

LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);

try {

System.out.println("Waiting for video socket connection...");

videoSocket = localServerSocket.accept();

System.out.println("video socket is connected.");

// send one byte so the client may read() to detect a connection error

videoSocket.getOutputStream().write(0);

try {

System.out.println("Waiting for input socket connection...");

controlSocket = localServerSocket.accept();

System.out.println("input socket is connected.");

} catch (IOException | RuntimeException e) {

videoSocket.close();

throw e;

}

} finally {

localServerSocket.close();

}

} else {

videoSocket = connect(SOCKET_NAME);

try {

controlSocket = connect(SOCKET_NAME);

} catch (IOException | RuntimeException e) {

videoSocket.close();

throw e;

}

}

DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);

Size videoSize = device.getScreenInfo().getVideoSize();

connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());

return connection;

}

复制代码

加了一点打印信息

等到有客户端连接这个socket后,它就会立即开启新的线程进行屏幕录制,并及时通过这个socket将录屏编码后的数据传到客户端去,再同时等待一个新的socket来连接,这个新的socket则用来接收客户端对设备的控制信息

如何根据客户端的消息对设备进行控制?

scrcpy自己定义了一套协议,不过里面的注释实在太少了,这些个函数的调用跳来跳去的,半天都看不到个啥,就像有人也用qt的图形界面重写了scrcpy,也是需要从它的源码下手的 我就直接把我最后从源码中解析出来的分享出来了

socket相关的知识:

由于socket传输的是byte[],

1 byte有8位,无符号0255,有符号就会少一个1来表示正负号所以范围就是-128127

数据类型占用位数

1 short = 2 byte = 16位

1 int = 4 byte = 32位

1 long = 8 byte = 64位

在对设备的点击,及滑动控制中:

第一位用来标记此次的控制类型,所以赋值给了type

取一位值给Action,用来表示是按下事件,抬起事件,滑动事件

取8位给PointerId,没搞懂这个id是干嘛的

取12位给Position,其中8位分别每4位表示点击的x,y坐标,另外4位分别两位表示屏幕的宽高,

取两位给presureInt,表示此次按压的力度

取4位给buttons,目前未知作用,后续补上

代码部分

private ControlMessage parseInjectTouchEvent() {

// System.out.println("接收到的remaining长度===>"+buffer.remaining());

if (buffer.remaining() < INJECT_TOUCH_EVENT_PAYLOAD_LENGTH) {

return null;

}

int action = toUnsigned(buffer.get());

long pointerId = buffer.getLong();

Position position = readPosition(buffer);

// 16 bits fixed-point

int pressureInt = toUnsigned(buffer.getShort());

// convert it to a float between 0 and 1 (0x1p16f is 2^16 as float)

float pressure = pressureInt == 0xffff ? 1f : (pressureInt / 0x1p16f);

int buttons = buffer.getInt();

return ControlMessage.createInjectTouchEvent(action, pointerId, position, pressure, buttons);

}

private static Position readPosition(ByteBuffer buffer) {

int x = buffer.getInt();

int y = buffer.getInt();

int screenWidth = toUnsigned(buffer.getShort());

int screenHeight = toUnsigned(buffer.getShort());

return new Position(x, y, screenWidth, screenHeight);

}

复制代码

ControlMessage是自定义的封装类,buffer即为来自socket的缓存区,是Java nio包下的类,可以看到它有get(),getLong(),getShort(),这些方法,会取buffer中指定的字节数来组成int,long,short这些类型 所以当我们需要对设备发起按压事件时(以1080x2280分辨率为例)

下面是dart的代码,其他语言进行控制也能类似

按压:

[

2,

0,

0,0,0,0,0,0,0,0,

x >> 24,x << 8 >> 24,x << 16 >> 24,x << 24 >> 24,

y >> 24,y << 8 >> 24,y << 16 >> 24,y << 24 >> 24,

1080 >> 8,1080 << 8 >> 8,2280 >> 8,2280 << 8 >> 8,

0,

0,

0,

0,

0,

0

]

复制代码

抬起:

[

2,

1,

0,0,0,0,0,0,0,0,

x >> 24,x << 8 >> 24,x << 16 >> 24,x << 24 >> 24,

y >> 24,y << 8 >> 24,y << 16 >> 24,y << 24 >> 24,

1080 >> 8,1080 << 8 >> 8,2280 >> 8,2280 << 8 >> 8,

0,

0,

0,

0,

0,

0

]

复制代码

移动:

[

2,

2,

0,0,0,0,0,0,0,0,

x >> 24,x << 8 >> 24,x << 16 >> 24,x << 24 >> 24,

y >> 24,y << 8 >> 24,y << 16 >> 24,y << 24 >> 24,

1080 >> 8,1080 << 8 >> 8,2280 >> 8,2280 << 8 >> 8,

0,

0,

0,

0,

0,

0

]

复制代码

x,y即为想要点击的屏幕坐标,这次数据被scrcpy服务端接收到会按压屏幕的x,y位置,用了较多的移位运算

如java nio调用getShort来获取屏幕的宽度

int screenWidth = toUnsigned(buffer.getShort());

private static int toUnsigned(short value) {

return value & 0xffff;

}

复制代码

上面提到了1 short有2字节16位 无符号数最大就是

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

复制代码

上限就是2的16次方-1为:65535,用来传分辨率肯定够了

如果屏幕的宽为1080,大于了1 byte中能存的大小,所以我们只有按字节存进byte[],并发送给socket

那如何将屏幕分辨率的宽按字节传输给socket呢? 举个

java 控制 区域投屏,Scrcpy投屏原理浅析-设备控制篇相关推荐

  1. android scrcpy 源代码分析,Scrcpy投屏原理浅析-设备控制篇

    起初我真的想过自己单独写一套来着,后来发现 Scrcpy与vysor是都是投屏中比较优秀的项目了,非侵入性,不需要设备单独 scrcpy启动阶段 它到底是怎么做到执行scrcpy命令,在较短的时间内就 ...

  2. java 连接池技术_java数据库连接池技术原理(浅析)

    在执行数据库SQL语句时,我们先要进行数据连接:而每次创建新的数据库的连接要消耗大量的资源,这样,大家就想出了数据库连接池技术.它的原理是,在运行过程中,同时打开着一定数量的数据库连接,形成数据连接池 ...

  3. 史上最全scrcpy投屏教程(用你的电脑控制手机)

    史上最全scrcpy投屏教程(用你的电脑控制手机) 一.下载投屏所需的资源 网址:scrcpy-win64-v1.17 提取码: yvid : 当然你也可以去github下载最新的或你所需要的资源,网 ...

  4. scrcpy投屏_安卓投屏利器——PC一键控制多台手机

    点击关注,我们共同每天进步一点点! 之前给大家介绍了投屏开源工具scrcpy(Scrcpy投屏,在电脑上流畅操控你的手机!),今天要介绍的投屏工具是在scrcpy的基础上进行了二次开发,使用更加友好. ...

  5. Windows系统使用开源工具scrcpy投屏

    简单地来说,scrcpy就是通过adb调试的方式来将手机屏幕投到电脑上,并可以通过电脑控制您的Android设备.它可以通过USB连接,也可以通过Wifi连接(类似于隔空投屏),而且不需要任何root ...

  6. Scrcpy 投屏神器基本使用

    项目地址与下载 github: https://github.com/Genymobile/scrcpy 选择下载版本 下载操作系统相应的安装包 Scrcpy 基本简介 简单地来说,scrcpy就是通 ...

  7. JAVA软件海豚_[Java教程]海豚星空扫码投屏 Android 接收端 SDK 集成 六步骤

    [Java教程]海豚星空扫码投屏 Android 接收端 SDK 集成 六步骤 0 2020-08-20 12:00:32 扫码投屏,开放网络,独占设备,不需要额外下载软件,微信扫码,发现设备.支持标 ...

  8. Mac 安卓投屏Scrcpy使用

    Mac 安卓投屏Scrcpy使用 1.概述 在Mac电脑上通过无线投屏操作安卓手机对于测试安卓设备非常方便,省去了电脑到安卓设备端来回奔波.下面介绍下Scrcpy一些常用方法. 2.Scrcpy常用操 ...

  9. scrcpy 投屏电脑能正常显示,但是没法用鼠标操作

    scrcpy 投屏电脑能正常显示,但是没法用鼠标操作 使用介绍: scrcpy投屏教程.scrcpy无线投屏.scrcpy命令大全 解决办法 以我的 **小米手机** 为例, 因为没有勾选" ...

最新文章

  1. 【机器视觉案例】(13) 脸部和摄像机间的距离测量,自适应文本大小,附python完整代码
  2. Cissp-【第4章 通信与网络安全】-2021-3-12(446页-475页)
  3. 一图理解JavaWeb项目
  4. c++ 获取时间戳_分布式系统理论基础三-时间、时钟和事件顺序
  5. 语音基础知识-基本语音知识,声谱图,log梅普图,MFCC,deltas详解
  6. 解决 Serverless 落地困难的关键,是给开发者足够的“安全感”
  7. java javah_Java开发网 - 一个javah的问题
  8. 欧拉回路(HDU-1878)
  9. java导入项目存在,如何将预先存在的Java项目导入Eclipse并启动并运行?
  10. Web Service学习总结
  11. ROS学习记录:在ROS的Rviz下完成摄像头的视频显示
  12. 汇编语言王爽---第四版
  13. Word:快速插入水平分隔线(转)
  14. 存储过程实现报表数据源的利弊分析
  15. 基于stm32的智能婴儿床(毕业设计)
  16. php做网站半成品,两小时学会用php做网站购物车
  17. pg高可用之repmgr(一)
  18. 灾害应急管理信息化建设“四步走”——以水旱灾害为例
  19. 职场中14个坏习惯可能让你丢掉工作
  20. PDB Files: What Every Developer Must Know

热门文章

  1. 网站挂 Google 广告能赚多少钱?
  2. 应用软件是计算机运行操作的基础,计算机基础
  3. 捷报!快商通斩获数字中国创新大赛2大重量级奖项
  4. SOLIDWORKS Simulation中如何添加虚拟螺栓
  5. 数据科学中心——首席数据科学家
  6. 论如何在Windows上深度还原MacOS的体验,再推荐几个第三方软件
  7. 全景图的各种制作方法~~
  8. 全球及中国光伏逆变器市场需求与竞争趋势研究报告2022版
  9. eGalax电阻屏的Touch驱动
  10. nginx在linux下安装,nginx在linux下的安装与使用