atx-agent minicap、minitouch源码分析
项目描述:
因为公司需要,特别研究了一下openatx系列手机群控源码
源码地址: https://github.com/openatx
该项目主要以go语言来编写服务端、集成 OpenSTF中核心组件 minicap和minitouch来完成
今天主要来分析一下atx-agent服务源码中 minicap和minitouch 相关接口源码
1.minicap
简介:
minicap工具是用NDK开发的,属于Android的底层开发,该工具分为两个部分,一个是动态连接库.so文件,一个是minicap可执行文件。但不是通用的,
因为CPU架构的不同分为不同的版本文件,STF提供的minicap文件根据CPU 的ABI分为如下4种:从上面可以看出,minicap可执行文件分为4种,
分别针对arm64-v8a、armeabi-v7a,x86,x86_64 架构。而minicap.so文件在这个基础上还要分为不同的sdk版本。
minicap采集屏幕的原理很简单:通过ndk的截屏接口不停的截屏并通过socket接口实时发送,这样客户端便可以得到一序列的图片流,图片流合成后就成为视频。
使用原生screencap工具截屏并输出到图像需要4s多,对比minicap则只需要190ms,差距明显。minicap使用了libjpeg-turbo作为编码压缩工具,
压缩后的图片体积更小1080P分辨率的手机截图根据色彩丰富度不同一般只需要100k,sceencap则需要2M。 atx-agent minicap部分
m.HandleFunc("/minicap", singleFightNewerWebsocket(func(w http.ResponseWriter, r *http.Request, ws *websocket.Conn) {
defer ws.Close()
const wsWriteWait = 10 * time.Second
wsWrite := func(messageType int, data []byte) error {
//设置websocket写入最长超时间
ws.SetWriteDeadline(time.Now().Add(wsWriteWait))
return ws.WriteMessage(messageType, data)
}
wsWrite(websocket.TextMessage, []byte("restart @minicap service"))
//重启minicap
if err := service.Restart("minicap"); err != nil && err != cmdctrl.ErrAlreadyRunning {
wsWrite(websocket.TextMessage, []byte("@minicap service start failed: "+err.Error()))
return
}
wsWrite(websocket.TextMessage, []byte("dial unix:@minicap"))
log.Printf("minicap connection: %v", r.RemoteAddr)
dataC := make(chan []byte, 10)
quitC := make(chan bool, 2)
go func() {
defer close(dataC)
retries := 0
for {
if retries > 10 {
log.Println("unix @minicap connect failed")
dataC <- []byte("@minicap listen timeout, possibly minicap not installed")
break
}
conn, err := net.Dial("unix", "@minicap")
if err != nil {
retries++
log.Printf("dial @minicap err: %v, wait 0.5s", err)
select {
case <-quitC:
return
case <-time.After(500 * time.Millisecond):
}
continue
}
dataC <- []byte("rotation " + strconv.Itoa(deviceRotation))
retries = 0 // connected, reset retries
if er := translateMinicap(conn, dataC, quitC); er == nil {
conn.Close()
log.Println("transfer closed")
break
} else {
conn.Close()
log.Println("minicap read error, try to read again")
}
}
}()
go func() {
for {
if _, _, err := ws.ReadMessage(); err != nil {
quitC <- true
break
}
}
}()
var num int = 0
//遍历管道循环发送数据
for data := range dataC {
//丢弃一半的数据包降低帧率
if string(data[:2]) == "\xff\xd8" { // jpeg data
if num %2 == 0{
num ++
continue
}
if err := wsWrite(websocket.BinaryMessage, data); err != nil {
break
}
if err := wsWrite(websocket.TextMessage, []byte("data size: "+strconv.Itoa(len(data)))); err != nil {
break
}
} else {
if err := wsWrite(websocket.TextMessage, data); err != nil {
break
}
}
}
quitC <- true
log.Println("stream finished")
})).Methods("GET")
大致逻辑是当有客户端和agent的minicap接口建立websocket连接后会先开启一个goroutine来和minicap进行通信,并将minicap返回的数据存放到dataC中,然后for循环遍历该管道取出
所有数据,如果是图片格式 直接通过websocket传输到客户端进行展示
流量优化:
1.帧率优化
if num %2 == 0{
num ++
continue
}
考虑到网络流量造成的带宽问题 在这里做了一些小小优化 对minicap返回的图片丢弃一半来达到优化效果
2.图片质量优化
//降低图片画质
service.Add("minicap", cmdctrl.CommandInfo{
Environ: []string{"LD_LIBRARY_PATH=/data/local/tmp"},
Args: []string{"/data/local/tmp/minicap", "-S", "-P",
fmt.Sprintf("%dx%d@%dx%d/0", width, height, displayMaxWidthHeight, displayMaxWidthHeight),
"-Q", "50"},
})

在atx-agent项目源码main.go中 有启动minicap的脚本命令 其中-Q 为图片质量范围在(0-100)之间 详细解释在minicap源码中 我在这里设置为50 大概传输200张图片在4M,从而缓解网络占用问题 参考文章(https://www.jianshu.com/p/5b5fef0241af)
2.minitouch
简介:
跟minicap一样,minitouch也是用NDK开发的,跟minicap使用方法类似,不过它只要上传一个minitouch文件就可以了。对应的文件路径树跟minicap一样就不重复
介绍(不过它只需要对应不同的CPU的ABI,而不需要对应SDK版本)。实际测试这个触摸操作和minicap一样,实时性很高没什么卡顿。
atx-agent minitouch部分
m.HandleFunc("/minitouch", singleFightNewerWebsocket(func(w http.ResponseWriter, r *http.Request, ws *websocket.Conn) {
defer ws.Close()
const wsWriteWait = 10 * time.Second
wsWrite := func(messageType int, data []byte) error {
ws.SetWriteDeadline(time.Now().Add(wsWriteWait))
return ws.WriteMessage(messageType, data)
}
wsWrite(websocket.TextMessage, []byte("start @minitouch service"))
if err := service.Start("minitouch"); err != nil && err != cmdctrl.ErrAlreadyRunning {
wsWrite(websocket.TextMessage, []byte("@minitouch service start failed: "+err.Error()))
return
}
wsWrite(websocket.TextMessage, []byte("dial unix:@minitouch"))
log.Printf("minitouch connection: %v", r.RemoteAddr)
retries := 0
quitC := make(chan bool, 2)
operC := make(chan TouchRequest, 10)
defer func() {
wsWrite(websocket.TextMessage, []byte("unix:@minitouch websocket closed"))
close(operC)
}()
go func() {
for {
if retries > 10 {
log.Println("unix @minitouch connect failed")
wsWrite(websocket.TextMessage, []byte("@minitouch listen timeout, possibly minitouch not installed"))
ws.Close()
break
}
conn, err := net.Dial("unix", "@minitouch")
if err != nil {
retries++
log.Printf("dial @minitouch error: %v, wait 0.5s", err)
select {
case <-quitC:
return
case <-time.After(500 * time.Millisecond):
}
continue
}
log.Println("unix @minitouch connected, accepting requests")
retries = 0 // connected, reset retries
err = drainTouchRequests(conn, operC)
conn.Close()
if err != nil {
log.Println("drain touch requests err:", err)
} else {
log.Println("unix @minitouch disconnected")
break // operC closed
}
}
}()
var touchRequest TouchRequest
//轮询
for {
err := ws.ReadJSON(&touchRequest)
if err != nil {
log.Println("readJson err:", err)
quitC <- true
break
}
select {
case operC <- touchRequest:
//两秒钟
case <-time.After(2 * time.Second):
wsWrite(websocket.TextMessage, []byte("touch request buffer full"))
}
}
})).Methods("GET")
func drainTouchRequests(conn net.Conn, reqC chan TouchRequest) error {
var maxX, maxY int
var flag string
var ver int
var maxContacts, maxPressure int
var pid int
lineRd := lineFormatReader{bufrd: bufio.NewReader(conn)}
lineRd.Scanf("%s %d", &flag, &ver)
lineRd.Scanf("%s %d %d %d %d", &flag, &maxContacts, &maxX, &maxY, &maxPressure)
if err := lineRd.Scanf("%s %d", &flag, &pid); err != nil {
return err
}
log.Debugf("handle touch requests maxX:%d maxY:%d maxPressure:%d maxContacts:%d", maxX, maxY, maxPressure, maxContacts)
go io.Copy(ioutil.Discard, conn) // ignore the rest output
var posX, posY int
for req := range reqC {
var err error
switch req.Operation {
case "r": // reset
_, err = conn.Write([]byte("r\n"))
case "d":
fallthrough
case "m":
//计算点击位置 req.PercentX 前端传过来的值 乘 最大x值
posX = int(req.PercentX * float64(maxX))
posY = int(req.PercentY * float64(maxY))
pressure := int(req.Pressure * float64(maxPressure))
if pressure == 0 {
pressure = maxPressure - 1
}
line := fmt.Sprintf("%s %d %d %d %d\n", req.Operation, req.Index, posX, posY, pressure)
log.Debugf("write to @minitouch %v", line)
_, err = conn.Write([]byte(line))
case "u":
_, err = conn.Write([]byte(fmt.Sprintf("u %d\n", req.Index)))
case "c":
_, err = conn.Write([]byte("c\n"))
case "w":
_, err = conn.Write([]byte(fmt.Sprintf("w %d\n", req.Milliseconds)))
default:
err = errors.New("unsupported operation: " + req.Operation)
}
if err != nil {
return err
}
}
return nil
}
大致逻辑为接收客户端发送过来的json数据并将数据存储到operC管道中, 开启一个goroutine来和minitouch建立连接并根据不同的类型来执行不同的操作
atx-agent minicap、minitouch源码分析的更多相关文章
- openfalcon源码分析之agent
本节内容 agent功能 1.1 agent上报数据 1.2 agent与HBS同步 1.3 agent Http服务 agent源码分析 2.1 初始化config配置 2.2 初始化根目录,本地I ...
- Tomcat源码分析
前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...
- angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)
昨天晚上写完angular源码分析:angular中jqLite的实现--你可以丢掉jQuery了,给今天定了一个题angular源码分析:injector.js文件,以及angular的加载流程,但 ...
- Openrasp源码分析
Openrasp是百度关于rasp技术的开源项目,由于工作需要,之前对rasp的源码进行了简单的分析.文章是之前就写好的,现在放出了,希望对大家有写帮助. OpenRASP中java引擎的源码分析 安 ...
- 开源网站流量统计系统Piwik源码分析——参数统计(一)
Piwik现已改名为Matomo,这是一套国外著名的开源网站统计系统,类似于百度统计.Google Analytics等系统.最大的区别就是可以看到其中的源码,这正合我意.因为我一直对统计的系统很好奇 ...
- JForum 源码分析
怎么才算好的源码分析呢?当然我这个肯定不算.我想大概分为几个层面吧,写写注释那算最基本的了,写写要点思路和难点,算是还不错拉,再难的就是跳出源码举一反三,形成自己的一套思路吧.好好努力吧. 这次针对的 ...
- ceilometer 源码分析(polling)(O版)
一.简单介绍ceilometer 这里长话短说, ceilometer是用来采集openstack下面各种资源的在某一时刻的资源值,比如云硬盘的大小等.下面是官网现在的架构图 这里除了ceilomet ...
- nGrinder对监控机器收集自定义数据及源码分析
转载:https://blog.csdn.net/neven7/article/details/50782451 0.背景 性能测试工具nGrinder支持在无需修改源码的情况下,对目标服务器收集自定 ...
- openfalcon源码分析之transfer
本节内容 transfer功能 transfer接收数据来源 transfer数据去向 transfer的一致性hash transfer的一致性hash key的计算 transfer源码分析 2. ...
随机推荐
- 从基本理解到深入探究 Linux kernel 通知链(notifier chain)【转】
转自:https://blog.csdn.net/u014134180/article/details/86563754 版权声明:本文为博主原创文章,未经博主允许不得转载.——Wu_Being ht ...
- 使用nc传输文件和目录【转】
方法1,传输文件演示(先启动接收命令) 使用nc传输文件还是比较方便的,因为不用scp和rsync那种输入密码的操作了 把A机器上的一个rpm文件发送到B机器上 需注意操作次序,receiver先侦听 ...
- zabbix3.2自动发现批量监控redis端口状态
使用nmap提示被防火墙阻挡,实际没有启用防火墙 [root@eus_chinasoft_haproxy:/usr/local/aegis]# nmap 172.20.103.202 -p 7000 ...
- FTP、FTPS和SFTP
FTP 一.两种传输方式 ASCII传输方式 假定用户正在拷贝的文件包含的简单ASCII码文本,如果在远程机器上运行的不是UNIX,当文件传输时ftp通常会自动地调整文件的内容以便于把文件解释成另外那 ...
- W3CSchool闯关笔记(中级脚本算法)
坚持下去,编程是一门艺术,与君共勉!!! function sumAll(arr) { var result = 0; var sn = Math.min(arr[0] , arr[1]); var ...
- 清晰讲解SQL语句中的外连接,通用于Mysql和Oracle,全是干货哦
直入主题: 我们做一个操作,将员工SCOTT的部门去掉,再次通过内连接查看数据,看看会产生什么现象? 使用内连接,查询数据 问题:找不到SCOTT员工了,只有13条数据,这显然不合理:这就是内连接的缺 ...
- 十五丶IO model
事件驱动模型 上节的问题: 协程:遇到IO操作就切换. 但什么时候切回去呢?怎么确定IO操作完了? 很多程序员可能会考虑使用“线程池”或“连接池”.“线程池”旨在减少创建和销毁线程的频率,其维持一定合 ...
- Django组件-cookie与session
一.会话跟踪技术 1.什么是会话跟踪技术 我们需要先了解一下什么是会话!可以把会话理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应.例如你给10086打个电话,你就是客户端,而 ...
- 用 pdf.js兼容部分安卓显示PDF在线预览 时,a标签直接链接参数文件不能含中文的解决办法
例子: 项目部署在 Tomcat 上的: <a href="../generic/web/viewer.html?file=doc/register/要显示的文件.pdf" ...
- windows下安装Python虚拟环境virtualenvwrapper-win
1 前言 由于Python的版本众多,还有Python2和Python3的争论,因此有些软件包或第三方库就容易出现版本不兼容的问题. 通过 virtualenv 这个工具,就可以构建一系列 虚拟的 ...