最近一个项目中需要用到 screen mapping、remote control 等功能,仔细对比之后选择了 scrcpy 这个方案。scrcpy 是一种主要用于 display 和 control 的简单易行的联机方案,主要特点是非侵入性(不需要主动安装应用程序),结构简单,方法易行。下面,让我们来简要分析一下它的工作原理。

我们以 Win 版为例,解压文件后,可以看到如下这么几个文件。

adb.exe

AdbWinApi.dll

AdbWinUsbApi.dll

avcodec-58.dll

avformat-58.dll

avutil-56.dll

scrcpy-noconsole.exe

scrcpy-server

scrcpy.exe

SDL2.dll

swresample-3.dll

swscale-5.dll

仔细阅读源码,发现工作原理很简单,那就是:从 server 端,这里指定为 PC 端,发送一个 jar 文件到 client 端。然后在 client 端执行,利用的是 Android 平台中 classloader 执行命令行的特性,先将 jar 包利用 adb 推送到 /data/local/tmp,然后执行,但是整个过程用户除了运行一个可执行命令、都是无感知的,这操作好骚气~~~

另外一点,前几期阶段,server 文件只是一个 jar 包,而后期 scrcpy 改变了策略,由这个文件来生成 jar 包,这样做是为了什么?小编我先在这里卖个关子。

上述文件执行之后,会在 server / client 之间生成一个 socks 连接,该 socks 连接中传输的是什么?没错,看到 ffmpeg 没有,传输的就是 stream 流,最后经 ffmpeg 解码还原成图像。至此,图像传输的基本过程结束。下面贴出一段代码,可以看到虽然 scrcpy 这个小工具结构简单、但是还是能够打印输出/调试信息,功能还是蛮全的。

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;

}

需要注意的是,这个过程每次都需要执行一遍,也就是说,每次都会推送一遍这个 file....

效果图如下

scrapy投屏为什么没声音(投屏原理简单解析)(1)

,