minicap java_AndroidControl/Minicap.java at master · imharryzhu/AndroidControl · GitHub
/*
*
* MIT License
*
* Copyright (c) 2017 朱辉 https://blog.yeetor.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
package com.yeetor.minicap;
import com.android.ddmlib.*;
import com.yeetor.adb.AdbDevice;
import com.yeetor.adb.AdbForward;
import com.yeetor.adb.AdbServer;
import com.yeetor.util.Constant;
import com.yeetor.util.Util;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import sun.misc.BASE64Decoder;
import java.io.*;
import java.net.Socket;
import java.util.*;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import static com.yeetor.adb.AdbDevice.SCREEN_SIZE;
/**
* Created by harry on 2017/4/17.
*/
public class Minicap extends ScreencapBase {
private static Logger logger = Logger.getLogger(Minicap.class);
private static final String MINICAP_BIN = "minicap";
private static final String MINICAP_SO = "minicap.so";
private static final String REMOTE_PATH = "/data/local/tmp";
private AdbDevice device;
// 物理屏幕宽高
private Size deviceSize;
private boolean running = false;
// 启动minicap的线程
private Thread minicapThread, minicapInitialThread, dataReaderThread, imageParserThread;
private AdbForward forward;
// listener
private List listenerList = new ArrayList();
private BlockingQueue dataQueue = new LinkedBlockingQueue();
private Banner banner;
private Socket minicapSocket;
public static void installMinicap(AdbDevice device) throws MinicapInstallException {
if (device == null) {
throw new MinicapInstallException("device can't be null");
}
if (isMinicapInstalled(device)) {
return;
}
String sdk = device.getProperty(Constant.PROP_SDK);
String abi = device.getProperty(Constant.PROP_ABI);
if (StringUtils.isEmpty(sdk) || StringUtils.isEmpty(abi)) {
throw new MinicapInstallException("cant not get device info. please check device is connected");
}
sdk = sdk.trim();
abi = abi.trim();
// minicap
File minicap_bin = Constant.getMinicap(abi);
if (minicap_bin == null || !minicap_bin.exists()) {
throw new MinicapInstallException("File: " + minicap_bin.getAbsolutePath() + " not exists!");
}
try {
AdbServer.server().executePushFile(device.getIDevice(), minicap_bin.getAbsolutePath(), REMOTE_PATH + "/" + MINICAP_BIN);
} catch (Exception e) {
throw new MinicapInstallException(e.getMessage());
}
AdbServer.executeShellCommand(device.getIDevice(), "chmod 777 " + REMOTE_PATH + "/" + MINICAP_BIN);
// minicap.so
File minicap_so = Constant.getMinicapSo(abi, sdk);
if (minicap_so == null || !minicap_so.exists()) {
throw new MinicapInstallException("File: " + minicap_so.getAbsolutePath() + " not exists!");
}
try {
AdbServer.server().executePushFile(device.getIDevice(), minicap_so.getAbsolutePath(), REMOTE_PATH + "/" + MINICAP_SO);
} catch (Exception e) {
throw new MinicapInstallException(e.getMessage());
}
}
public Minicap(AdbDevice device) {
this.device = device;
try {
installMinicap(device);
} catch (MinicapInstallException e) {
e.printStackTrace();
}
// 获取屏幕大小
String[] ss = device.findPropertyCahe(SCREEN_SIZE).split("x");
int w = Integer.parseInt(ss[0]);
int h = Integer.parseInt(ss[1]);
deviceSize = new Size(w, h);
}
public Minicap(String serialNumber) {
this(AdbServer.server().getDevice(serialNumber));
}
public Minicap() {
this(AdbServer.server().getFirstDevice());
}
public void addEventListener(MinicapListener listener) {
if (listener != null) {
this.listenerList.add(listener);
}
}
public List getEventListener() {
return listenerList;
}
/*
Usage: /data/local/tmp/minicap [-h] [-n ]
-d : Display ID. (0)
-n : Change the name of the abtract unix domain socket. (minicap)
-P : Display projection (x@x/{0|90|180|270}).
-Q : JPEG quality (0-100).
-s: Take a screenshot and output it to stdout. Needs -P.
-S: Skip frames when they cannot be consumed quickly enough.
-t: Attempt to get the capture method running, then exit.
-i: Get display information in JSON format. May segfault.
*/
public String getMinicapCommand(int ow, int oh, int dw, int dh, int rotate, boolean shipFrame, String name, String[] args) {
ArrayList commands = new ArrayList();
commands.add(String.format("LD_LIBRARY_PATH=%s", REMOTE_PATH));
commands.add(REMOTE_PATH + "/" + MINICAP_BIN);
commands.add("-P");
commands.add(String.format("%dx%d@%dx%d/%d", ow, oh, dw, dh, rotate));
commands.add("-n");
commands.add(name);
if (shipFrame)
commands.add("-S");
if (args != null) {
for (String s : args) {
commands.add(s);
}
}
String command = StringUtils.join(commands, " ");
return command;
}
public AdbForward createForward() {
forward = generateForwardInfo();
try {
device.getIDevice().createForward(forward.getPort(), forward.getLocalabstract(), IDevice.DeviceUnixSocketNamespace.ABSTRACT);
} catch (Exception e) {
e.printStackTrace();
System.out.println("create forward failed");
}
return forward;
}
private void removeForward() {
if (forward == null || !forward.isForward()) {
return;
}
try {
device.getIDevice().removeForward(forward.getPort(), forward.getLocalabstract(), IDevice.DeviceUnixSocketNamespace.ABSTRACT);
forward = null;
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 屏幕截图
*
* 由于个平台的换行符导致二进制流输出有问题,二进制数据将先base64编码后传输
*
* @return
*/
public byte[] takeScreenShot() {
String command = getMinicapCommand(deviceSize.w, deviceSize.h, deviceSize.w, deviceSize.h, 0, false, "minicap", new String[] {"-s -b"});
logger.info(String.format("device(%s) takeScreenShot", device.getSerialNumber()));
BinaryOutputReceiver receiver = new BinaryOutputReceiver();
try {
device.getIDevice().executeShellCommand(command, receiver, 0);
} catch (Exception e) {
logger.warn("device execute command error!");
e.printStackTrace();
}
// remove text output
byte[] bytes = receiver.getOutput();
do {
String dataStr = new String(bytes);
int jpgStart = dataStr.indexOf("/9j/");
if (jpgStart >= 0) {
dataStr = dataStr.substring(jpgStart) ; // jpg特征码base64
} else {
break;
}
try {
bytes = new BASE64Decoder().decodeBuffer(dataStr);
} catch (IOException e) {
logger.warn("base64 decode error!");
e.printStackTrace();
break;
}
if (bytes[0] != -1 && bytes[1] != -40) {
logger.warn("not a jpg file!");
break;
}
return bytes;
} while(false);
return new byte[0];
}
public void start(int ow, int oh, int dw, int dh, int rotate, boolean shipFrame, String[] args) {
AdbForward forward = createForward();
String command = getMinicapCommand(ow, oh, dw, dh ,rotate, shipFrame, forward.getLocalabstract(), args);
logger.info("start minicap:" + command);
minicapThread = startMinicapThread(command);
minicapInitialThread = startInitialThread("127.0.0.1", forward.getPort());
}
public void start(final float scale, final int rotate) {
start(deviceSize.w, deviceSize.h, (int)(deviceSize.w * scale), (int)(deviceSize.h * scale), rotate,true, null);
}
public void reStart(final float scale, final int rotate) {
running = false;
if (minicapThread != null) {
minicapThread.stop();
}
if (dataReaderThread != null) {
try {
dataReaderThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (imageParserThread != null) {
try {
imageParserThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
start(scale, rotate);
}
public void kill() {
onClose();
running = false;
if (minicapThread != null) {
minicapThread.stop();
}
// 关闭socket
if (minicapSocket != null && minicapSocket.isConnected()) {
try {
minicapSocket.close();
} catch (IOException e) {
}
minicapSocket = null;
}
if (dataReaderThread != null) {
try {
dataReaderThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (imageParserThread != null) {
try {
imageParserThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 启动线程开启 minicap
* @param shellCommand
* @return 线程
*/
private Thread startMinicapThread(final String shellCommand) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
device.getIDevice().executeShellCommand(shellCommand, new IShellOutputReceiver() {
@Override
public void addOutput(byte[] bytes, int offset, int len) {
System.out.println("startMinicapThread" + new String(bytes, offset, len));
}
@Override
public void flush() {}
@Override
public boolean isCancelled() {
return false;
}
}, 0);
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
return thread;
}
private Thread startInitialThread(final String host, final int port) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
byte[] bytes = null;
int tryTime = 50;
while (true) {
// 连接minicap启动的服务
minicapSocket = new Socket(host, port);
InputStream inputStream = minicapSocket.getInputStream();
bytes = new byte[4096];
int n = inputStream.read(bytes);
if (n == -1) {
Thread.sleep(10);
minicapSocket.close();
} else {
// bytes内包含有信息,需要给Dataparser处理
dataQueue.add(Arrays.copyOfRange(bytes, 0, n));
running = true;
onStartup(true);
// 启动 DataReader ImageParser
dataReaderThread = startDataReaderThread(minicapSocket);
imageParserThread = startImageParserThread();
break;
}
tryTime--;
if (tryTime == 0) {
onStartup(false);
break;
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
thread.start();
return thread;
}
/**
* 生成forward信息
*/
private AdbForward generateForwardInfo() {
AdbForward[] forwards = AdbServer.server().getForwardList();
// serial_cap_number
int maxNumber = 0;
if (forwards.length > 0) {
for (AdbForward forward : forwards) {
if (forward.getSerialNumber().equals(device.getIDevice().getSerialNumber())) {
String l = forward.getLocalabstract();
String[] s = l.split("_");
if (s.length == 3) {
int n = Integer.parseInt(s[2]);
if (n > maxNumber) maxNumber = n;
}
}
}
}
maxNumber += 1;
String forwardStr = String.format("%s_cap_%d", device.getIDevice().getSerialNumber(), maxNumber);
int freePort = Util.getFreePort();
AdbForward forward = new AdbForward(device.getIDevice().getSerialNumber(), freePort, forwardStr);
return forward;
}
private Thread startDataReaderThread(Socket minicapSocket) {
Thread thread = new Thread(new DataReader(minicapSocket));
thread.start();
return thread;
}
private Thread startImageParserThread() {
Thread thread = new Thread(new ImageParser());
thread.start();
return thread;
}
private void onStartup(boolean success) {
for (MinicapListener listener : listenerList) {
listener.onStartup(this, success);
}
}
private void onClose() {
for (MinicapListener listener : listenerList) {
listener.onClose(this);
}
removeForward();
}
private void onBanner(Banner banner) {
for (MinicapListener listener : listenerList) {
listener.onBanner(this, banner);
}
}
private void onJPG(byte[] data) {
for (MinicapListener listener : listenerList) {
listener.onJPG(this, data);
}
}
/**
* 判断该手机是否已经安装minicap
* @return
*/
public static boolean isMinicapInstalled(AdbDevice device) {
if (device == null || device.getIDevice() == null) {
return false;
}
String s = AdbServer.executeShellCommand(device.getIDevice(), String.format("LD_LIBRARY_PATH=%s %s/%s -i", REMOTE_PATH, REMOTE_PATH, MINICAP_BIN));
// TODO: 这里简单处理了一下
return s.startsWith("{");
}
private class DataReader implements Runnable {
static final int BUFF_SIZ = 4096;
Socket socket = null;
InputStream inputStream = null;
long ts = 0;
DataReader(Socket minicapSocket) {
this.socket = minicapSocket;
try {
this.inputStream = minicapSocket.getInputStream();
} catch (IOException e) {
e.printStackTrace();
onClose();
}
}
@Override
public void run() {
try {
readData();
} catch (IOException e) {
logger.warn("minicap lost connection: " + e.getMessage());
onClose();
}
}
public void readData() throws IOException {
DataInputStream stream = new DataInputStream(inputStream);
while (running) {
byte[] buffer = new byte[BUFF_SIZ];
ts = System.currentTimeMillis();
int len = stream.read(buffer);
if (len == -1) {
return;
}
if (len == BUFF_SIZ) {
dataQueue.add(buffer);
} else {
dataQueue.add(Util.subArray(buffer, 0, len));
}
}
}
}
private class ImageParser implements Runnable {
int readn = 0; // 已读大小
int bannerLen = 2; // banner信息大小
int readFrameBytes = 0;
int frameBodyLength = 0;
byte[] frameBody = new byte[0];
long t = 0;
@Override
public void run() {
while (running) {
try {
banner = new Banner();
readData();
} catch (InterruptedException e) {
System.out.println(e.getMessage());
onClose();
}
}
}
void readData() throws InterruptedException {
byte[] buffer = dataQueue.poll(5000, TimeUnit.MILLISECONDS);
if (buffer == null) { // TODO 使用阻塞队列就不用判断了
return;
}
int length = buffer.length;
for (int cursor = 0; cursor < length;) {
int ch = buffer[cursor] & 0xff;
if (readn < bannerLen) {
cursor = parserBanner(cursor, ch);
} else if(readFrameBytes < 4) { // frame length
frameBodyLength += (ch << (readFrameBytes * 8));
cursor += 1;
readFrameBytes += 1;
if (readFrameBytes == 4) {
t = System.currentTimeMillis();
}
} else {
if (length - cursor >= frameBodyLength) {
byte[] subByte = Arrays.copyOfRange(buffer, cursor,
cursor + frameBodyLength);
frameBody = Util.mergeArray(frameBody, subByte);
if ((frameBody[0] != -1) || frameBody[1] != -40) {
System.out.println("Frame body does not start with JPG header");
return;
}
byte[] finalBytes = Arrays.copyOfRange(frameBody, 0, frameBody.length);
onJPG(finalBytes);
cursor += frameBodyLength;
frameBodyLength = 0;
readFrameBytes = 0;
frameBody = new byte[0];
long timeused = (System.currentTimeMillis() - t);
timeused = timeused == 0 ? 1 : timeused;
String log = String.format("jpg: %d timeused: %dms fps: %d", finalBytes.length, (int)timeused, 1000 / timeused);
} else {
byte[] subByte = Arrays.copyOfRange(buffer, cursor, length);
frameBody = Util.mergeArray(frameBody, subByte);
frameBodyLength -= (length - cursor);
readFrameBytes += (length - cursor);
cursor = length;
}
}
}
}
// banner
int pid = 0;
int realWidth = 0;
int realHeight = 0;
int virtualWidth = 0;
int virtualHeight = 0;
int orientation = 0;
int quirks = 0;
int parserBanner(int cursor, int ch) {
switch (cursor) {
case 0:
banner.setVersion(ch);
break;
case 1:
bannerLen = ch;
banner.setLength(bannerLen);
break;
case 2:
case 3:
case 4:
case 5: {
pid += (ch << ((readn - 2) * 8));
if (cursor == 5)
banner.setPid(pid);
break;
}
case 6:
case 7:
case 8:
case 9:
{
realWidth += (ch << ((readn - 6) * 8));
if (cursor == 9)
banner.setReadWidth(realWidth);
break;
}
case 10:
case 11:
case 12:
case 13:
realHeight += (ch << ((readn - 10) * 8));
if (cursor == 13)
banner.setReadHeight(realHeight);
break;
case 14:
case 15:
case 16:
case 17:
virtualWidth += (ch << ((readn - 14) * 8));
if (cursor == 17)
banner.setVirtualWidth(virtualWidth);
break;
case 18:
case 19:
case 20:
case 21:
virtualHeight += (ch << ((readn - 18) * 8));
if (cursor == 21)
banner.setVirtualHeight(virtualHeight);
break;
case 22:
orientation = ch * 90;
banner.setOrientation(orientation);
break;
case 23:
quirks = ch;
banner.setQuirks(quirks);
onBanner(banner);
break;
}
++readn;
++cursor;
return cursor;
}
}
}
minicap java_AndroidControl/Minicap.java at master · imharryzhu/AndroidControl · GitHub相关推荐
- java并发框架支持锁包括,tip/面试题_并发与多线程.md at master · 171437912/tip · GitHub...
01. java用()机制实现了进程之间的同步执行 A. 监视器 B. 虚拟机 C. 多个CPU D. 异步调用 正解: A 解析: 监视器机制即锁机制 02. 线程安全的map在JDK 1.5及其更 ...
- java j集合_JNotes/Java-集合篇(2)集合之List.md at master · harryjudy2240/JNotes · GitHub...
ArrayList 存储机制 基于动态数组实现,默认长度 10 :因为数组特性,其读取速度快,增删效率慢 因为写入或读取都是通过数组索引直接操作,所以,允许为 null 值, 且可以重复 当数组大小不 ...
- linux at java,Linux-Tutorial/Java-bin.md at master · linsanityHuang/Linux-Tutorial · GitHub
Java bin 目录下的工具 JVM 内存结构 运行时数据区(JVM 规范) VM 栈(JVM 虚拟机栈) 是线程私有的,它的生命周期和线程相同.它描述的是 Java 方法执行的内存模式. Java ...
- c await和java_blog/java/test/awaitility.zh.md at master · c-rainstorm/blog · GitHub
```java AtomicInteger atomic = new AtomicInteger(0); // Do some async stuff that eventually updates ...
- java cookbook 3_CookBook/Java核心/3-Java反射.md at master · zhgdbut/CookBook · GitHub
#Java核心(三)反射 Java反射给我们提供了在运行时检查甚至修改应用行为的机制. 反射是java高级的核心技术,所有有经验的程序员都应该理解. 通过反射机制,我们可以在运行时检视 类.接口.枚举 ...
- java编程规范每行代码窄字符,wiki/0xFE_编程规范.md at master · islibra/wiki · GitHub
0xFE_编程规范 使用UTF-8编码 使用空格缩进 命名 清晰表达意图, 少用缩写(行业通用除外, 如: request=req, response=resp, message=msg), 不应使用 ...
- java p=x,Java-Tutorial/20、javac和javap.md at master · allenchenx/Java-Tutorial · GitHub
目录 title: 夯实Java基础系列20:从IDE的实现原理聊起,谈谈那些年我们用过的Java命令 date: 2019-9-20 15:56:26 # 文章生成时间,一般不改 categorie ...
- java 正则表达式 table_JavaEdge/Java/Java中正则表达式.md at master · VegTableBird/JavaEdge · GitHub...
主要用到的是这两个类 - java.util.regex.Pattern - java.util.regex.Matcher. Pattern对应正则表达式,一个Pattern与一个String对象关 ...
- think in java第6_think-in-java/6.5 protected.md at master · quanke/think-in-java · GitHub
6.5 protected 现在我们已理解了继承的概念,protected这个关键字最后终于有了意义.在理想情况下,private成员随时都是"私有"的,任何人不得访问.但在实际应 ...
最新文章
- TensorFlow优化器及用法
- php校友录毕业论文,基于WEB的同学校友录的设计(PHP,MySQL)(附答辩记录)
- Nature 子刊:加州大学Banfield组揭示CPR细菌和DPANN古菌多样性及与低温TEM下宿主互作关系...
- java socket datagramsocket_用DatagramSocket写的个渣渣聊天后台,求拍砖
- Android中Application类用法
- Zoom Host可以真正看到您的所有私人消息吗?
- Python源码深度解析—对象的行为和多态性
- [设计素材]你也在找CTBiaoSongSJ吗?我这里有耶!
- ubuntu20.04 NVIDIA显卡驱动安装教程(Y9000p)
- 第3章 词性标注(pos、N-gram、NER等标注器)
- java 去掉图片水印文字_Java实现图片水印工具类
- tomcat 设置缓存大小
- 天马G6二期、腾龙光谷数据中心 落户武汉东湖高新区
- ATECC508A芯片开发笔记(十一):NXP 平台移植ATECCx08 CryptoAuthLib库(I2C)
- 使用Dubbo实现简单的RPC调用(Spring配置文件版)
- python项目对接钉钉SDK
- 东风破 苏轼 喜欢的词,方文山的歌词好象就是这样借过来的。
- 开放手机联盟(OHA)
- android平板投屏,Mac、ipad投屏安卓平板
- 序列向量和时间向量matlab,MATLAB日期和时间处理
热门文章
- android 华为pad 自动对焦,有料更有“生产力”,华为平板M6体验,补足安卓生态短板...
- OpenOcean 是世界上第一个领先的完整聚合器
- 百度搜索“一起自学吧”做大数据专家,不做第一,只做唯一
- 在网页上播放多种后缀的视频文件的临时解决方案
- php中strchr的语法,如何使用php strchr函数
- 职称计算机execl试题,职称计算机考试2017年Excel全真模拟试题
- android如何确保应用进程不被杀死,使得一个android应用不会被进程杀死
- 关于辛普森积分法的研究
- Windows10 ls命令
- 【笔记】华莱士(Wallis)公式