一,背景
最近用一直都有研究这类技术,我总算把大概技术搞清楚了。
二,开源代码
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的样子,那么性能还是够可以的。