scrcpy 为什么不能常亮

scrcpy wifi连接无法常亮

我自己开发软件发现无法常亮,这样子用起来就非常不方便,然后我研究了scrcpy,他虽然有一个--stayway on 的命令,但我发现这个只有连接usb才会保持常亮。

过程

  1. 分析Scrcpy 只是设置setting 手机属性而已,电源管理设置而已,但如果用wifi没有效果了,因为我没有充电,但这样子如果我挂机软件或者游戏感觉用起来就不方便了。
  2. 我发现anlink可以做到。

分析anlink

我猜测他用什么adb命令,我懒的逆向分析,直接用火绒剑分析进程行为,我发现他使用了命令

adb shell input keyevent mouse

我找了半天mouse,也没有找到这个按键值,然后我看google input.java的 源代码也没有这个有关的知识,然后我猜测就是未知值,那么我直接adb shell input keyevent 0 不就可以了吗?于是我在代码构建按键,而不是用adb命令模式,每隔10秒发送一个未知按键,未知按键就是0,这个是android 代码里面定义的,这样子就实现屏幕常亮。。。这个操作不管什么类型的手机都能常亮,因为都一直在按键。

额外发现

我突然发现如果间隔50毫秒发送未知按键就导致我的手机无法锁屏,这个逻辑结合屏幕关闭,就可以导致用户无法操作手机。。。。。。 有点无敌啊。。。所以以后普通用户千万不要授权APP 具备ADB命令,现在手机可以直接wifi adb, 不需要连接,这样子就可能出现木马,强制控制手机行为了。。。。。
安全手机不要开启开发者模式,否则带来很大危险。

android 协同逻辑之adb forward

一、背景

开源scrcpy 用adb forward 进行转发,这样子不用知道电脑的IP,直接给PC指定端口发送数据就会传送到手机端

二、问题

自己在开发自己协同工具的时候,发现虽然转发,但流量还是在手机显示,而用alink(基于开源的scrcpy开发),却没有显示流量。

三、解决过程

自己命令:

adb forward tcp:9990 tcp:9990

他们用的:

adb forward tcp:9990 localabstract:local

我发现他们用localServersocket而不是Sersocket,于是百度一下这种locaksocket可以设置别名,类似管道,不走协议栈,常用于IPC进程通信,所以没有显示流量。

四、展示我自己demo

用electron 显示,底层截图API,目前不是采用视频流,因为觉得以后用于远程时候,不需要视频流,截图就已经满足了。目前基本功能能用。

同屏,协作,PC控制手机技术总结

一,背景

最近用一直都有研究这类技术,我总算把大概技术搞清楚了。

二,开源代码

1,minicap

openstf/minicap: Stream real-time screen capture data out of Android devices. (github.com)

用c++ 编译ndk 可执行程序,用的ndk私有API,所以他依赖aosp代码,所以有适配问题,如果系统魔改很厉害化,可能就无法运行,兼容性比其他开源感觉会差一些,毕竟底层so变化挺大,不是接口。

2,scrcpy

Genymobile/scrcpy: Display and control your Android device (github.com)

利用app_process+ java层反射,从而可以进行录屏,而从达到实时传送手机屏幕效果,兼容性理论比minicap要好很多,不管怎么说也是 java层,业务层代码,给上层调用。minicap fps 理论比采用录屏的效果差,采集方式不一样,性能不一样。

3:qtscrcpy

增加许多附加功能,用scrpty开发的

三,技术点

本质技术:通过分析aosp 源代码查看截屏代码的实现和录屏实现。

scrcpy:app_proccess 启动 apk或者 jar(必须dex化),这样子可以直接调用java程序,获取shell 用户权限,通过这个特征可以过系统权限问题。应用层直接反射获取实现截屏或者录制屏幕是没有权限surface权限的,所以失败了,并不是class hide。

app_process 其他博客介绍

android:app_process两种用法_玩出品-CSDN博客

android录制屏幕或者截屏大概流程

深入浅出,Andorid 端屏幕采集技术实践 - 知乎 (zhihu.com)

Android PC投屏简单尝试- 自定义协议章(Socket+Bitmap) - 简书 (jianshu.com)

Android PC投屏简单尝试(录屏直播)2—硬解章(MediaCodec+RMTP) - 简书 (jianshu.com)

使用mediaprojection进行获取屏幕,看这几篇大概就能知道了

Android PC投屏简单尝试—最终章1 - 简书 (jianshu.com)

这个就能明白scrcpy截屏原理,别人写的很好,我也是看他的看明白的。

四,vysor

一款同屏软件,非开源,网上说他反射surface 调用screenshot,我自己反编译发现他也用scrcpy 录屏的功能,可能他的低fps用的 surface 调用screenshot

我自己看了一下android 11 源代码,写了一下反射,截屏成功了。

这里demo 用小米10 android 11测试的,这个screenshot改动比较少。

import android.graphics.Bitmap;

import java.io.File;
import java.io.FileOutputStream;

public class Main {
    public static void main(String args[]) {
        Bitmap bitmap = EncoderFeeder.screenshot(360, 640);
        saveBitmap(bitmap);
    }


    private static boolean saveBitmap(Bitmap bitmap){
        try {
            File file = new File("/sdcard/" + "test11.jpg");
            FileOutputStream out = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
            out.flush();
            out.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }
}


import android.graphics.Bitmap;
import android.os.Build;
import android.util.Log;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import android.graphics.Rect;
import android.view.SurfaceControl;

public class EncoderFeeder {
    public static String TAG = "EncoderFeeder";
    public static Bitmap screenshot(int screenWidth, int screenHeight) {
        String surfaceClassName;
        Log.d(TAG, "screenshot: 开始截图");
        if (Build.VERSION.SDK_INT <= 17) {
            surfaceClassName = "android.view.Surface";

        } else {
            surfaceClassName = "android.view.SurfaceControl";
        }
        Class<?> classname;
        Bitmap bm = null;
        try {
            Log.d(TAG, "screenshot: 截图的类名:" + surfaceClassName);
            classname = Class.forName(surfaceClassName);
            //printMethods(classname);
            if(false){
                return null;
            }

            Method method = classname.getDeclaredMethod("screenshot",
                    new Class[] { Rect.class, int.class, int.class, int.class });
            bm = (Bitmap) method.invoke(
                    null,
                    new Object[] { new Rect(), Integer.valueOf(screenWidth),//分辨率
                            Integer.valueOf(screenHeight), 0 });
        } catch (Exception e) {
            e.printStackTrace();
        }

        Log.d(TAG, "screenshot: 截图的后的数据:");
        //Log.d(TAG, "screenshot: 截图的后的数据:" + bm.toString());
        return bm;
    }


    public static void printMethods(Class c1){
        Method[] methods=c1.getDeclaredMethods();
        for (Method m:methods) {
            Class retType=m.getReturnType();
            String name=m.getName();

            System.out.print("  ");
            String modifiers= Modifier.toString(m.getModifiers());
            if(modifiers.length()>0)
                System.out.print(modifiers+" ");
            System.out.print(retType.getName()+" "+name+"(");
            Class[] paraTypes=m.getParameterTypes();
            for (int i = 0; i < paraTypes.length; i++) {
                if(i>0)
                    System.out.print(", ");
                System.out.print(paraTypes[i].getName());
            }
            System.out.println(");");
        }
    }
}

测试截屏时间:20~30ms的延迟,那么可以20~50fps的样子,那么性能还是够可以的。

android 高效截图研究总结【用于远程控制】

一,背景

自己无意见发现虫洞这个软件,PC可以控制android,发现FPS 非常流程,于是触发我的好奇心,研究几天,大概知道商业用哪种技术。

二,思路

1,网上直接找就是adb shell screencap -p /stcard/xx.png,然后拉取图片,显示图片

这种用我自己的手机测试大概是2秒左右,太迟太大,结合我自己写的c语言的代码,每次启动一个新的adb来截图,所以时间基本更长。

2:上面进化版本 adb exec-out screencap -p > test.png

在手机端不存放普通,通过adb传送数据,写到本地磁盘,这个方案大概1秒左右。但还是像PPT,起码要20FPS稍微流畅一点。不能用adb shell screencap 因为控制台输出,会导致数据不对,貌似换行符号导致。我也是在google好久才找到这个办法。

3:使用ADB协议,不用每次截图启动进程,

我用的node js adbkit,实现ADB协议,貌似各种语言的都有,用脚本或者c#感觉比较方便一些。这个时候我就抛弃了c++,因为对一些图像渲染不熟悉,我把一个老外的sdl1版本改成sdl2,增加模拟点击算法,但用起来nodejs 就简单更多,直接用html 加载 base64 image即可。这个消耗时间大概260ms,所以看起来大概有一秒左右延迟,但只是偶尔棘突,采用这个方案未必不是好事情,因为不需要那么流程。而且类库很少就是使用adb 协议即可。简单远程也没有太多问题。【有时间我整理一下这个代码,直接丢出来,给有用的人,起码不要自己研究了】

4:使用adbkit frambuffer 接口,获取原始图像的数据(rgba)

貌似他这个有bug,代码肯定不对,只能拿到元素rgba的数据,他虽然提供格式png等等,但貌似代码是错误的,他根本就没有传送原始流,貌似接口回调跟他们文档对不上,我自己用jimp转换成png,然后传给网页,结果延迟大概要5秒,因为原始流没有压缩,我自己用小米10,得到元素流有8M,加上转换,传送发送5秒左右,难道adbkit 说screencap 接口比frambuffer快几倍。这个时候我在如果在android获取原始像素,然后压缩png或者其他格式,传送是不是快很多。

5:使用Minicap,达到实时传送【基本0延迟】

github上面有他们代码,我直接用airtest编译好的,用他们工具,他直接会放到local\tmp目录,我主要嫌弃编译麻烦,自己对android ndk编译没有玩过。你可以用airtest直接跑,直接连接设备,就可以点击。然后我使用了一下命令测试一下,基本实时。

三,minicap大概原理

调用aosp 私有API 获取屏幕像是图像,因为用私有API,所以必须编译aosp ,导出SO,给minicap调用,github libs里面含有到29的版本,网易的airtest到30版本了,不同android 必须编译对应so. 这个是我的理解,具体很深细节不太了解。


我思考一个技术实现,通过ADB开启远程调试,然后在android用java实现一个adb协议读取framebuffer数据然后压缩再传送给PC端,这样子延迟不是慢不慢,但不知道用adb 读取framebuffer到底慢不慢。

实现远程点击坐标转换很简单,x1/x2 = y1 /y2 ,只要计算比例即可,显示图片大小与手机大小比就可以了,html 直接监听点击转换手机坐标即可。

上面我心理路程全部写了,如果想实现实时控制手机,基本用minicap就可以了,其他方案都太慢了。这里前提是使用adb 模拟点击,你用辅助服务器 加上android 5.0 截图 api 那是另外一种事情


更新:

minicap 编译是可执行程序,可以直接adb shell 执行指定程序,然后利用编译android 底层so,调用私有api,每个版本android都要单独编译,如果系统魔改估计会有问题,毕竟二进制不一样。adnroid c++底层比较不是给外部用的,这个比java 非SDK api稳定性差多了。

srccpy 和 vysor 用的是java 层的反射 + 加上 app_process【获取高级权限】,这个就不用底层系统so,而且这些软件基本采用录屏,具体逻辑和细节在下一篇文章

我后面分析虫洞他的技术用的就是srccpy开源代码改的【基本差不多】

android getrootinactivewindow return null

自己写自动化时候发现会出现null的情况,并不是切换窗口太快导致问题,自己根据网上资料大概下面几种情况 ,设置如下就可以了:

  • android:canRetrieveWindowContent="true" 没有设置,导致获取不到焦点
  • android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews" 属性不够,导致认为一些为不重要属性节点
  • 窗口没有焦点,一般情况不会出现。

暂时记录一下,方便以后解决同样的问题。