# minicapGo **Repository Path**: phery/minicapGo ## Basic Information - **Project Name**: minicapGo - **Description**: 基于开源项目STF minicap的PC同步录屏软件,通过java实现视频源协议的解析 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 8 - **Forks**: 6 - **Created**: 2018-01-05 - **Last Updated**: 2024-01-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 先看看效果: ![](https://testerhome.com/uploads/photo/2018/d821ce63-33f0-4084-a541-d7ab6f42db63.gif!large) #### 1.项目地址:https://gitee.com/phery/minicapGo #### 2.实现原理: 手机画面同步:部署minicap程序到手机>执行minicap程序>绑定Socket端口到本地PC>监听并解析协议内容 调用adb模拟点击:获取鼠标按下x坐标>获取鼠标松开x坐标>计算两个x坐标的差值>换算成adb长按的时长 #### 3.关键代码 初始化minicap: ```java package com.phery.minicap; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; public class InitDevices { public static int SDK_VERSION; public static String ABI; public static int PRE; public static String REL; public static String DIR; public static String BIN; public static void init() { SDK_VERSION = Integer.valueOf(excuteCommand("adb shell getprop ro.build.version.sdk")).intValue(); ABI = excuteCommand("adb shell getprop ro.product.cpu.abi"); String preSDK = excuteCommand("adb shell getprop ro.build.version.preview_sdk"); if (!"".equals(preSDK)) PRE = Integer.valueOf(preSDK).intValue(); REL = excuteCommand("adb shell getprop ro.build.version.release"); DIR = "/data/local/tmp/minicap-devel"; if (PRE > 0) SDK_VERSION++; if (SDK_VERSION >= 16) BIN = "minicap"; else BIN = "minicap-nopie"; String result; result = excuteCommand("adb shell \"mkdir " + DIR + " 2>/dev/null || true\""); System.out.println(result); result = excuteCommand("adb push resource/libs/" + ABI + "/" + BIN + " " + DIR); System.out.println(result); result = excuteCommand("adb push resource/aosp/libs/android-" + SDK_VERSION + "/" + ABI + "/minicap.so " + DIR); System.out.println(result); result = excuteCommand("adb shell \"chmod 777 " + DIR + "/*\""); System.out.println(result); result = excuteCommand("adb forward tcp:1717 localabstract:minicap"); System.out.println(result); String wmsize = excuteCommand("adb shell wm size"); String[] value = wmsize.split(" "); wmsize = value[2]; try { Runtime.getRuntime().exec( "adb shell LD_LIBRARY_PATH=" + DIR + " " + DIR + "/" + BIN + " -Q 100 -P " + wmsize + "@480x800/0"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } // System.out.println(result); } // public static void main(String[] args) // { // init(); // } public static String excuteCommand(String cmd) { /* 获取cmd命令 */ try { System.out.println("正在执行命令:" + cmd); Process pro = Runtime.getRuntime().exec(cmd); // 添加要进行的命令,"cmd // /c // calc"中calc代表要执行打开计算器,如何设置关机请自己查找cmd命令 BufferedReader br = new BufferedReader(new InputStreamReader(pro.getInputStream())); // 虽然cmd命令可以直接输出,但是通过IO流技术可以保证对数据进行一个缓冲。 String msg = null; String result = null; System.out.println("执行完成!!!"); while ((msg = br.readLine()) != null) { System.out.println(msg); if (result == null) result = msg; } System.out.println(result); return result; } catch (IOException exception) { exception.printStackTrace(); return ""; } /* * cmd /c dir 是执行完dir命令后关闭命令窗口 cmd /k dir 是执行完dir命令后不关闭命令窗口 cmd /c start * dir 会打开一个新窗口后执行dir命令,原窗口会关闭 cmd /k start dir 会打开一个新窗口后执行dir命令,原窗口不会关闭 * cmd /? 查看帮助信息 */ } } ``` 解析minicap协议: ```java package com.phery.minicap; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.net.UnknownHostException; import java.nio.Buffer; public class MiniCap { private RefreshListener listener; public MiniCap(RefreshListener listener) { this.listener = listener; } int readBannerBytes = 0; int bannerLength = 24; int readFrameBytes = 0; int frameBodyLength = 0; byte[] frameBody = new byte[0]; String[] banner = new String[24]; public void tryRead() { Socket socket; InputStream stream = null; try { socket = new Socket("127.0.0.1", 1717); stream = socket.getInputStream(); } catch (UnknownHostException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } while (true) { try { DataInputStream input = new DataInputStream(stream); int len = input.available();// 获取读到的字节长度 byte[] chunk = new byte[len]; input.read(chunk);// 将读取的字节保存到chunk // System.out.println("拉取到长度:" + len); for (int cursor = 0; cursor < len;) { if (readBannerBytes < bannerLength) { switch (readBannerBytes) { case 0: // version banner[0] = chunk[cursor] + ""; System.out.println("Banner Version:" + banner[0]); break; case 1: // length bannerLength = chunk[cursor]; banner[1] = String.valueOf(bannerLength); System.out.println("Banner bannerLength:" + banner[1]); break; // case 2: // case 3: // case 4: case 5: // byte[] temp = new byte[4]; // System.arraycopy(chunk, 2, temp, 0, 4); banner[5] = bytesToLong(chunk, 2) + ""; // pid // banner[5] += (chunk[cursor] << ((readBannerBytes // - 2) * 8)) >>> 0; System.out.println("Banner pid:" + banner[5]); break; // case 6: // case 7: // case 8: case 9: // real width banner[9] = bytesToLong(chunk, 6) + ""; // banner[9] += (chunk[cursor] << ((readBannerBytes // - 6) * 8)) >>> 0; System.out.println("Banner real width:" + banner[9]); break; // case 10: // case 11: // case 12: case 13: // real height banner[13] = bytesToLong(chunk, 10) + ""; // banner[13] += (chunk[cursor] << ((readBannerBytes // - 10) * 8)) >>> 0; System.out.println("Banner real height:" + banner[13]); break; // case 14: // case 15: // case 16: case 17: // virtual width banner[17] = bytesToLong(chunk, 14) + ""; // banner[17] += (chunk[cursor] << ((readBannerBytes // - 14) * 8)) >>> 0; System.out.println("Banner virtual width:" + banner[17]); break; // case 18: // case 19: // case 20: case 21: // virtual height banner[21] = bytesToLong(chunk, 18) + ""; // banner[21] += (chunk[cursor] << ((readBannerBytes // - 18) * 8)) >>> 0; System.out.println("Banner virtual height:" + banner[21]); break; case 22: // orientation banner[22] += chunk[cursor] * 90; System.out.println("Banner orientation:" + banner[22]); break; case 23: // quirks banner[23] = chunk[cursor] + ""; System.out.println("Banner quirks:" + banner[23]); break; } cursor += 1; readBannerBytes += 1; if (readBannerBytes == bannerLength) { System.out.println("banner读取完毕!"); // console.log('banner', banner) } } else if (readFrameBytes < 4) { frameBodyLength = (int) bytesToLong(chunk, 0); // System.out.println("获取字节长度:" + frameBodyLength); // frameBodyLength += (chunk[cursor] << (readFrameBytes // * 8)) >>> 0; if (frameBodyLength < 0 || frameBodyLength > 999999) { cursor += frameBodyLength; frameBodyLength = readFrameBytes = 0; frameBody = new byte[0]; continue; } cursor += 4; readFrameBytes += 4; } else { if (len - cursor >= frameBodyLength) { // console.info('bodyfin(len=%d,cursor=%d)', // frameBodyLength, cursor) byte[] temp = new byte[cursor + frameBodyLength]; System.arraycopy(chunk, cursor, temp, 0, cursor + frameBodyLength); frameBody = addBytes(frameBody, temp); // Sanity check for JPG header, only here for // debugging purposes. // System.out.println("frameBody[0]:" + frameBody[0] // + " frameBody[1]" + frameBody[1] // + " 0xFF:" + 0xFF + " 0xD8:" + 0xD8); // if (frameBody[0] != -1 || frameBody[1] != -40) // { // cursor += frameBodyLength; // frameBodyLength = readFrameBytes = 0; // frameBody = new byte[0]; // console.error( // 'Frame body does not start with JPG header', // frameBody) // continue; // } // System.out.println("读取完毕:" + frameBody.length); listener.refresh(frameBody); cursor += frameBodyLength; frameBodyLength = readFrameBytes = 0; frameBody = new byte[0]; } else { // console.info('body(len=%d)', len - cursor) byte[] temp = new byte[len - cursor]; System.arraycopy(chunk, cursor, temp, 0, len - cursor); frameBody = addBytes(frameBody, temp); // System.out.println("body:" + (len - cursor)); // System.out.println("frameBodyLength:" + // frameBodyLength); frameBodyLength -= len - cursor; readFrameBytes += len - cursor; cursor = len; } } } } catch (Exception e) { e.printStackTrace(); System.out.println("报错了"); } } } /** * byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和和intToBytes()配套使用 * * @param src * byte数组 * @param offset * 从数组的第offset位开始 * @return int数值 */ public static long bytesToLong(byte[] src, int offset) { long value; value = (long) ((src[offset] & 0xFF) | ((src[offset + 1] & 0xFF) << 8) | ((src[offset + 2] & 0xFF) << 16) | ((src[offset + 3] & 0xFF) << 24)); return value; } /** * * @param data1 * @param data2 * @return data1 与 data2拼接的结果 */ public static byte[] addBytes(byte[] data1, byte[] data2) { byte[] data3 = new byte[data1.length + data2.length]; System.arraycopy(data1, 0, data3, 0, data1.length); System.arraycopy(data2, 0, data3, data1.length, data2.length); return data3; } } ``` * minicap项目地址:https://github.com/openstf/minicap