【投屏】Scrcpy源码分析四(最终章 - Server篇)

这篇具有很好参考价值的文章主要介绍了【投屏】Scrcpy源码分析四(最终章 - Server篇)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Scrcpy源码分析系列
【投屏】Scrcpy源码分析一(编译篇)
【投屏】Scrcpy源码分析二(Client篇-连接阶段)
【投屏】Scrcpy源码分析三(Client篇-投屏阶段)
【投屏】Scrcpy源码分析四(最终章 - Server篇)

在前两篇我们探究了Scrcpy Client的连接和投屏逻辑,本篇我们就要继续探究Server端的逻辑了。

1. 入口函数

我们先来回忆下,还记得Server端是怎么运行起来的么?

答:由Client端执行adb push把Server程序上传到设备侧,然后执行app_process将Server端程序运行起来的。完整的命令是adb -s serial shell CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 1.25 [PARAMS]

app_process的好处一个是方便我们在安卓侧运行一个纯java程序(是dalvik的字节码,不是jvm字节码),二个是提权,使程序拥有root权限或者shell同等权限。

因为Client指定的类是com.genymobile.scrcpy.Server,所以Server的入口方法就是Server.java类的main()方法,其关键代码是:

// Server.java
public static void main(String... args) {
	// 解析参数
	Options options = createOptions(args);
	// scrcpy方法
	scrcpy(options);
}

private static void scrcpy(Options options) {
	// 调用DesktopConnection的open函数
	DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte);
	// 控制逻辑
	Controller controller = new Controller(device, connection,);
	startController(controller);
	// 投屏逻辑
	ScreenEncoder screenEncoder = new ScreenEncoder();
	screenEncoder.streamScreen(device, connection.getVideoFd());
}

我们看到,入口函数里主要的逻辑有:

  1. createOptions - 解析参数。

  2. DesktopConnection.open - 连接PC端(第二篇有提到,所以业务上安卓设备是Server,PC是Client,但网络层面安卓设备的Client, PC是Server):

    // DeskopConnection.java
    private static final String SOCKET_NAME = "scrcpy";
    
    public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) {
    	videoSocket = connect(SOCKET_NAME);
    	controlSocket = connect(SOCKET_NAME);
    	return new DesktopConnection(videoSocket, controlSocket);
    }
    
    private static LocalSocket connect(String abstractName) {
    	LocalSocket localSocket = new LocalSocket();
    	localSocket.connect(new LocalSocketAddress(abstractName));
    	return localSocket;
    }
    

    因为PC端通过adb用localabstract:scrcpy开启了端口映射,所以这里通过LocalServerSocketLocalSocket指定Unix Socket Name就可以连接上PC了,这里的Unix Socket Name是"scrcpy",必须和adb指定的保持一致。配合PC侧的逻辑,这里需要连接两次,可以得到videoSocket和controlSocket,同时因为这两个是基于Unix Domain Socket的LocalSocket,所以可以直接拿到其对应的文件描述符FileDescription,后续可以直接通过读写文件描述符进行网络数据传输。对这部分不了解的同学可以回顾下第二篇文章Client端这部分的逻辑描述。

  3. startController - 事件控制相关逻辑,基于controlSocket。

  4. streamScreen - 投屏相关逻辑,基于videoSocket。

看到这里,我们应该知道了,在Sever程序起来后就会去连接PC端,拿到两个Socket。
【投屏】Scrcpy源码分析四(最终章 - Server篇)

下面我们继续看下投屏和控制逻辑。

2. 投屏逻辑

投屏逻辑的入口是streamScreen方法:

// ScreenEncoder.java
public void streamScreen(Device device, FileDescriptor fd) {
	internalStreamScreen(device, fd);
}

private void internalStreamScreen(Device device, FileDescriptor fd) {
	// MediaCodec录屏的模板代码
	MediaFormat format = createFormat(bitRate, maxFps, codecOptions);
	MediaCodec codec = createCodec(encoderName);
	IBinder display = createDisplay();
	surface = codec.createInputSurface();
    setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
    codec.start();
    // 编码
    encode(codec, fd);
}

private boolean encode(MediaCodec codec, FileDescriptor fd) {
	while (!consumeRotationChange() && !eof) {
		int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
		ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
		// 写fd即发送给PC侧
		IO.writeFully(fd, codecBuffer);
	}
}

我们看到,投屏这部分其实就是利用录屏和利用MediaCodec硬编码。这部分偏模板代码,基本就是设置MediaCodec的参数,通过硬编码拿到H264的packet数据,然后通过IO.writeFully对fd进行写操作将数据发出。

大致的流程图下:
【投屏】Scrcpy源码分析四(最终章 - Server篇)

3. 控制逻辑

控制逻辑的入口是startController方法:

private static Thread startController(final Controller controller) {
	 Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                controller.control();
            }
        });
        thread.start();
}

public void control() {
	while (true) {
    	handleEvent();
    }
}

private void handleEvent() {
	// 从controlSocket的inputStream读数据
	ControlMessage msg = connection.receiveControlMessage();
	switch (msg.getType()) {
    	case ControlMessage.TYPE_INJECT_KEYCODE:
    		injectKeycode(msg.getAction(), msg.getKeycode(), msg.getRepeat(), msg.getMetaState());
       	case ControlMessage.TYPE_INJECT_TOUCH_EVENT:
       		injectTouch(msg.getAction(), msg.getPointerId(), msg.getPosition(), msg.getPressure(), msg.getButtons());
      	// ...
    }
}

我们看到控制部分是开启子线程,不断地从controlSocket中PC传来的读控制事件数据,然后根据事件类型的不同做不同的处理。这里我们看到键盘事件或鼠标事件最终都是调用到```injectXXX``方法。其实我们也能猜到,这里肯定是将PC传来的事件转成Android的事件,然后分发事件。那么Scrcpy是怎么实现这个步骤的呢?

3.1 事件注入

我们先来看下injectKeyCode方法:

// Controller.java
private boolean injectKeycode(int action, int keycode, int repeat, int metaState) {
	// 调用Device的injectKeycode方法
	device.injectKeyEvent(action, keycode, repeat, metaState, Device.INJECT_MODE_ASYNC);
}

// Device.java
public static boolean injectKeyEvent(int action, int keyCode, int repeat, int metaState, int displayId, int injectMode) {
	long now = SystemClock.uptimeMillis();
	// 构建一个KeyEvent
	KeyEvent event = new KeyEvent(now, now, action, keyCode, repeat, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
	        InputDevice.SOURCE_KEYBOARD);
	return injectEvent(event, displayId, injectMode);
}

public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) {
    InputManager.setDisplayId(inputEvent, displayId)
    return ServiceManager.getInputManager().injectInputEvent(inputEvent, injectMode);
}

injectKeyCode的调用链中构建了一个KeyEvent,然后调用到了最后这两个方法:

  • InputManager.setDisplayId() - 通过反射调用InputEventsetDisplayMethod方法,为事件指定目标Display:

    // InputManager.java
    public static boolean setDisplayId(InputEvent inputEvent, int displayId) {
    	Method method = getSetDisplayIdMethod();
    	method.invoke(inputEvent, displayId);
    	return true;
    }
    
    private static Method getSetDisplayIdMethod() throws NoSuchMethodException {
        if (setDisplayIdMethod == null) {
            setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class);
        }
        return setDisplayIdMethod;
    }
    
  • ServiceManager.getInputManager().injectInputEvent() - 通过反射的方式获取到系统中InputManager的实例,并用工程里的InputManager类包装一下:

    // ServiceManager.java
    public static InputManager getInputManager() {
        if (inputManager == null) {
            try {
            	// 反射调用系统InputManager的getInstance方法
                Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
                android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
                // 将系统的InputManager实例传入工程自己的InputManager类,包装一下
                inputManager = new InputManager(im);
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                throw new AssertionError(e);
            }
        }
        return inputManager;
    }
    

    然后通过反射调用系统InputManagerinjectInputEvent方法,进行事件注入处理,即通过系统InputManagerService将事件发到了目标Display上:

    private Method getInjectInputEventMethod() throws NoSuchMethodException {
        injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
        return injectInputEventMethod;
    }
    
    public boolean injectInputEvent(InputEvent inputEvent, int mode) {
        Method method = getInjectInputEventMethod();
        return (boolean) method.invoke(manager, inputEvent, mode);
    }
    

injectTouch方法大同小异,注入的是MotionEvent。但Android中MotionEventKeyEvent都是继承于InputEvent,所以最终都是走的injectInputEvent将事件发送到目标Display上。

所以我们的流程图可以填充完整了:
【投屏】Scrcpy源码分析四(最终章 - Server篇)

至此,Server端的连接、投屏、和控制逻辑就已经分析完了。

4. 时序图

照例附上时序图,不同颜色代表不同的线程。
【投屏】Scrcpy源码分析四(最终章 - Server篇)

5. 小结

本篇我们探究了Scrcpy Server端的逻辑,相较Client端而言,Server端的逻辑比较清晰简单。涉及的点有Android录屏、LocalSocket、MediaCode硬编码、事件注入。

到此,关于Scrcpy软件我们就全部分析完了,我们从项目结构开始,研究了其编译系统Meson,然后到Client端(PC端)的建立连接和投屏过程,最后到Server端(Android端)的连接、投屏和控制过程。主线流程还是比较清晰的。

其实最让我个人感到收获的有三个地方:

  1. ADB端口映射,这种方式为PC和手机的相互访问提供了便利,结合Unix Domain Socket,大大拓展了使用场景,应用非常广泛。
  2. SDL,笔者之前对SDL了解不深,只知道他可以用来做多媒体相关的界面。但Scrcpy中广泛地运用了SDL的库函数,比较同步、事件机制等和多媒体不太相关的功能。可以说是一套强大的工具库。所以目前笔者已经果断地将SDL加入了自己的后续学习清单。
  3. Android事件注入,Client端的事件注入机制主要是用了InputEvent的私有API,setDisplayinjectInputEvent。这种方式可以实现自己构建KeyEventMotionEvent后发到指定的屏上。刚巧笔者最近有在做的一个项目是有关多屏的,其中有个需要攻克的技术难点,就是其实要用户在真实物理屏上的触摸事件转发到一个我们自己创建的VirtualDisplay上。于是就借鉴上了Scrcpy中关于事件注入的方法,将Event事件的Display设置成VirtualDisplay的ID,然后通过事件注入的方式实现了转发。

所以,没事多研究成功的开源软件还是有好处的~文章来源地址https://www.toymoban.com/news/detail-459915.html

到了这里,关于【投屏】Scrcpy源码分析四(最终章 - Server篇)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • android开源投屏工具scrcpy简介

    目录 一,初识scrcpy 1.1 scrcpy介绍 1.2 scrcpy特点 二,scrcpy指令说明 2.1 画面设置 2.1.1 缩小分辨率 2.1.2 修改画面比特率 2.1.3 限制画面帧率 2.1.4 画面裁剪 2.1.5 锁定屏幕朝向 2.2 屏幕录制 2.3  连接方式 2.3.1 无线 2.3.2 多设备 2.4 窗口设置 2.4.1  标题 2.4.2 位置和大小 2.4.3 无边框 2.4.

    2024年02月06日
    浏览(45)
  • ADB 连接后,使用scrcpy投屏电脑

    将三个ADB文件复制后,放到C:WindowsSystem32下,同时也复制一份放到C:WindowsSysWOW64下 ADB文件: 他这里有提供百度网盘连接下载这几个文件 【adb安装】简单的一批的adb安装,少走弯路_哔哩哔哩_bilibili 然后,cmd命令, 输入adb,出现版本号,出现一大堆的代码说明,说明安装成功

    2024年02月09日
    浏览(36)
  • Scrcpy视频同步源码分析

    https://github.com/Genymobile/scrcpy         Scrcpy是genymobile开源的一款手机镜像软件,通过对手机音视频的采集和同步,可以实现在PC平台上控制手机的功能。 官方解释:此应用程序镜像通过 USB 或 TCP/IP 连接的 Android 设备(视频和音频),并允许使用计算机的键盘和鼠标控制设备

    2024年02月12日
    浏览(50)
  • scrcpy 投屏电脑能正常显示,但是没法用鼠标操作

    使用介绍: scrcpy投屏教程、scrcpy无线投屏、scrcpy命令大全

    2024年02月12日
    浏览(39)
  • scrcpy之将Android手机投屏到Linux电脑实践

    参考:https://zhuanlan.zhihu.com/p/366378837 电脑端安装投屏程序 手机端设置 手机端无需安装任何软件,只需开启【开发者选项】-【USB调试】及相关选项,比如我开启了【USB调试、USB调试(安全设置)、无线调试、USB安装】 开启【USB调试】相关选项后,用USB数据线连接电脑与手机,

    2024年02月09日
    浏览(55)
  • 分享一个开源的windows安卓投屏工具,scrcpy

    安装adb - ADB是一个Android Debug Bridge,用于与Android设备进行通信。如果您已经安装了Android Studio,则可以从其中运行adb。否则,您可以从ADB官方网站下载并手动安装。 安装SDL库 - Scrcpy使用SDL库来呈现Android设备的屏幕。您可以使用系统包管理器来安装SDL库,例如,在Ubuntu上,您可

    2023年04月18日
    浏览(41)
  • 开源、跨平台安卓摸鱼(投屏)软件 Scrcpy 中文使用指南

    废话不说,先上链接:GitHub上的Scrcpy Scrcpy 可以将手机画面投射到电脑上,让你可以在电脑上对手机进行操控。Scrcpy 通过 USB 或 Wi-Fi 与安卓手机相连,不需要在手机上安装任何 app,也不需要取得 ROOT 权限。 简单地说,就是可以让你在电脑上控制手机!它支持鼠标控制、键盘

    2024年02月12日
    浏览(61)
  • aardio嵌入外部窗口(以scrcpy手机投屏窗口为例)

    scrcpy是一个安卓手机投屏到电脑的开源组件。手机在开发者选项中打开USB调试开关,使用数据线连接到电脑,运行scrcpy.exe,就可以在电脑上查看手机屏幕,可以使用鼠标、键盘进行操作,并且提供了一系列命令行和快捷键,方便二次开发。我下载的是scrcpy-win32-v1.25,你也可以

    2024年02月12日
    浏览(73)
  • 三秒教会你如何使用scrcpy手机无线投屏到电脑

    scrcpy 是一款免费开源的投屏软件,可以将安卓手机屏幕投放在 Windows、macOS、GNU/Linux 上,并可以直接使用鼠标在投屏窗口中进行交互和录制。此应用程序镜像通过 USB 或TCP/IP连接的 Android 设备(视频和音频),并允许使用计算机的键盘和鼠标控制设备。它不需要任何 根访问权

    2024年02月07日
    浏览(66)
  • 安卓投屏神器 Scrcpy 安装与使用(支持 Mac、Windows、Linux)

    Scrcpy 可以在电脑上通过无线投屏操作安卓手机对于测试安卓设备非常方便,省去了电脑到安卓设备端来回奔波。 它支持将 Android 设备屏幕投放到 Windows 、 macOS 或 Linux 上。 安装 Scrcpy ,各平台安装方式 Windows - 64位 - 官方下载地址 Windows - 32位 - 官方下载地址 Linux: 执行 $ ap

    2024年02月08日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包