一:minicap 简介:
minicap 属于 STF 框架的一个工具,可以高速截图、同步手机屏幕至浏览器等功能,经过试验,截同一个屏幕的一张图,使用 adb shell screencap 命令进行截图速度为 2.9 秒,而 minicap 仅为 0.8 秒,效率高很多,这在基于图像识别的自动化测试中,起到至关重要的作用,假如你要做一个类似于腾讯的 wetest 网测平台,那么或许你可以尝试一下结合 minicap 工具实现屏幕同步功能。

二:minicap 获取途径:
1,通过下载源码进行编译,源码地址:https://github.com/openstf/minicap, 编译环境为 NDK+make,有兴趣的朋友可以参考 https://www.cnblogs.com/qiangayz/p/9580389.html 进行尝试,但本人觉得 windows 的环境设置有点麻烦,就选择了其他现成的 minicap 进行操作。
2,airtest 源码项目进行 minicap 获取,airtest 是网易开发的一款开源项目, 它里面集成了 minicap 和 minitouch 等工具,使用这些工具我们只需要到相应的目录去查找即可,下面介绍通过使用 airtest 上的 minicap 实现录屏功能。

三:将 airtest 上的 minicap 及 minicap.so 文件 push 到手机固定的目录当中去。
1,下载 Airtest IDE,官方网站http://airtest.netease.com/,解压后获得项目源码。
在这里插入图片描述
2,使用 pycharm 打开项目,进入 AirtestIDE_2020-01-21_py3_win64\airtest\core\android\static\stf_libs,如图可以看到,stf_libs 目录下有着各种 minicap 版本的父目录。它是根据手机 CPU 架构(arm64-v8a、armeabi-v7a,x86,x86_64)和 sdk 版本进行分类的。
在这里插入图片描述

3,获取手机 cpu 架构,sdk 版本
cpu 版本:adb shell getprop ro.product.cpu.abi
sdk 版本:adb sehll getprop ro.build.version.sdk
如图,可以看到 cpu 的架构是 arm64-v8a,sdk 版本是 26
在这里插入图片描述
4,在 airtest 上找到相应的 minicap 可执行文件和 minicap.so 文件。minicap 在(AirtestIDE_2020-01-21_py3_win64\airtest\core\android\static\stf_libs\arm64-v8a)目录下,而 minicap.so 在(airtest\core\android\static\stf_libs\minicap-shared\aosp\libs\android-27\arm64-v8a)目录下。
在这里插入图片描述
在这里插入图片描述
5,push minicap,minicap.so 到手机中
adb push minicap /data/local/tmp
adb push minicap.so /data/local/tmp

6,对它们进行赋权
adb shell chmod 777 /data/local/tmp/minicap
adb shell chmod 777 /data/local/tmp/minicap.so

四:获取手机分辨率后测试并且启动 minicap 服务

1,屏幕分辨率:adb shell wm size
在这里插入图片描述
2,测试是否可运行:adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1080x2160@1080x2160/0 -t
在这里插入图片描述
3,启动 minicap 服务:adb shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P 1080x2160@1080x2160/0
-P 后面的参数格式:{RealWidth}x{RealHeight}@{VirtualWidth}x{VirtualHeight}/{Orientation}
Orientation 可以理解为手机的旋转角度,可选参数为 0 | 90 | 180 | 270

五. 端口映射,把 minicap 映射到 1717 端口,也可以是其他端口。**
1,adb forward tcp:1717 localabstract:minicap
在这里插入图片描述

六,进行测试
1,minicap 源码里面有 example/文件夹下就是一个 minicap 提供的测试服务,由 node.js 搭建的服务端
所以启动需要本地有 node.js 环境,具体实现大概为开启 socket 连接,监听 1717 端口,然后再开一个 websocket 把监听到数据发给前端页面启动方式为:node app.js PORD=9002
pord 为指定前端页面端口,
2,打开浏览器访问:http://localhost:9002/ 即可看到手机投屏
在这里插入图片描述
七:通过 python 实现录屏:
1,执行下面代码,可以看到在当前目录下会有手机截屏图片生成。

# coding: utf8
import socket
import sys
import time
import struct
from collections import OrderedDict


class Banner:
    def __init__(self):
        self.__banner = OrderedDict(
            [('version', 0),
             ('length', 0),
             ('pid', 0),
             ('realWidth', 0),
             ('realHeight', 0),
             ('virtualWidth', 0),
             ('virtualHeight', 0),
             ('orientation', 0),
             ('quirks', 0)
             ])

    def __setitem__(self, key, value):
        self.__banner[key] = value

    def __getitem__(self, key):
        return self.__banner[key]

    def keys(self):
        return self.__banner.keys()

    def __str__(self):
        return str(self.__banner)

class Minicap(object):
    def __init__(self, host, port, banner):
        self.buffer_size = 4096
        self.host = host
        self.port = port
        self.banner = banner

    def connect(self):
        try:
            self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        except :
            print('2')
            sys.exit(1)
        self.__socket.connect((self.host, self.port))

    def on_image_transfered(self, data):
        file_name = str(time.time()) + '.jpg'
        with open(file_name, 'wb') as f:
            for b in data:
                f.write(b)

    def consume(self):
        readBannerBytes = 0
        bannerLength = 24
        readFrameBytes = 0
        frameBodyLength = 0
        data = []
        while True:
            try:
                chunk = self.__socket.recv(self.buffer_size)
            except:
                print('11')
                sys.exit(1)
            cursor = 0
            buf_len = len(chunk)
            while cursor < buf_len:
                if readBannerBytes < bannerLength:
                    map(lambda i, val: self.banner.__setitem__(self.banner.keys()[i], val),
                        [i for i in range(len(self.banner.keys()))], struct.unpack("<2b5ibB", chunk))
                    cursor = buf_len
                    readBannerBytes = bannerLength
                    print(self.banner)
                elif readFrameBytes < 4:
                    frameBodyLength += (struct.unpack('B', chunk[cursor])[0] << (readFrameBytes * 8)) >> 0
                    cursor += 1
                    readFrameBytes += 1
                else:
                    print("frame length:{0} buf_len:{1} cursor:{2}".format(frameBodyLength, buf_len, cursor))
                    # pic end
                    if buf_len - cursor >= frameBodyLength:
                        data.extend(chunk[cursor:cursor + frameBodyLength])
                        self.on_image_transfered(data)
                        cursor += frameBodyLength
                        frameBodyLength = readFrameBytes = 0
                        data = []
                    else:
                        data.extend(chunk[cursor:buf_len])
                        frameBodyLength -= buf_len - cursor
                        readFrameBytes += buf_len - cursor
                        cursor = buf_len

if '__main__' == __name__:
    print('开始')
    mc = Minicap('localhost', 1717, Banner())
    mc.connect()
    mc.consume()

2,将图片帧转换为视频,执行下面代码:

import cv2
import glob
import os
from datetime import datetime


def frames_to_video(fps, save_path, frames_path, max_index):
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    videoWriter = cv2.VideoWriter(save_path, fourcc, fps, (960, 544))
    imgs = glob.glob(frames_path + "/*.jpg")
    frames_num = len(imgs)
    for i in range(max_index):
        if os.path.isfile("%s/%d.jpg" % (frames_path, i)):
            frame = cv2.imread("%s/%d.jpg" % (frames_path, i))
            videoWriter.write(frame)
    videoWriter.release()
    return


if __name__ == '__main__':
    t1 = datetime.now()
    frames_to_video(22, "result.mp4", 'face_recog_frames', 1000)
    t2 = datetime.now()
    print("耗时 :", (t2 - t1))
    print("完成了 !!!")

3,结果,可以看到当前项目目录有一个 result.mp4 文件生成,耗时也是相当的短,可以看到通过 minicap 进行截图效率大大提升了。
在这里插入图片描述
在这里插入图片描述

八:总结。
1,在启动 minicap 过程中,出现没有权限的问题,安卓手机在没有 root 的情况下是没有权限对系统文件进行读写操作的,但是可通过 adb 命令进行权限赋予。adb shell chmod 777
在这里插入图片描述
2,一个简单的基于 minicap 的录屏功能就基本完成了,后续将基于 minicap 进行实时录屏操作,并加入 minitouch 进行点击操作,期待能实现更多更多的功能!


↙↙↙阅读原文可查看相关链接,并与作者交流