Add: aScreenCap support

- Optimize adb command call
This commit is contained in:
LmeSzinc 2020-06-16 07:33:28 +08:00
parent 1e55c25f49
commit 97b85160cd
15 changed files with 115 additions and 33 deletions

Binary file not shown.

Binary file not shown.

BIN
ascreencap/x86/ascreencap Normal file

Binary file not shown.

Binary file not shown.

View File

@ -96,8 +96,8 @@ serial = 127.0.0.1:62001
package_name = com.bilibili.azurlane
enable_error_log_and_screenshot_save = yes
enable_perspective_error_image_save = no
use_adb_screenshot = yes
use_adb_control = no
device_screenshot_method = aScreenCap
device_control_method = uiautomator2
combat_screenshot_interval = 1.
[Daily]

View File

@ -20,6 +20,7 @@ class EmulatorChecker(Device):
self._screenshot_uiautomator2()
# self._click_adb(1270, 360)
# self._click_uiautomator2(1270, 360)
# self._screenshot_ascreencap()
cost = time.time() - t0
record.append(cost)
@ -27,13 +28,18 @@ class EmulatorChecker(Device):
print(count, np.round(np.mean(record), 3), np.round(np.std(record), 3))
class Config:
SERIAL = '127.0.0.1:62001'
class Config(AzurLaneConfig):
SERIAL = '127.0.0.1:5555'
# SERIAL = '127.0.0.1:62001'
# SERIAL = '127.0.0.1:7555'
# SERIAL = 'emulator-5554'
# SERIAL = '127.0.0.1:21503'
USE_ADB_SCREENSHOT = False
# Speed: aScreenCap >> uiautomator2 > ADB
DEVICE_SCREENSHOT_METHOD = 'aScreenCap' # ADB, uiautomator2, aScreenCap
# Speed: uiautomator2 >> ADB
DEVICE_CONTROL_METHOD = 'uiautomator2' # ADB, uiautomator2
az = EmulatorChecker(AzurLaneConfig('template').merge(Config()))

View File

@ -280,8 +280,8 @@ def main(ini_name=''):
debug.add_argument('--保存透视识别出错的图像', default=default('--保存透视识别出错的图像'), choices=['', ''])
adb = emulator_parser.add_argument_group('ADB设置', '')
adb.add_argument('--使用ADB截图', default=default('--使用ADB截图'), choices=['', ''], help='建议开启, 能减少CPU占用')
adb.add_argument('--使用ADB点击', default=default('--使用ADB点击'), choices=['', ''], help='建议关闭, 能加快点击速度')
adb.add_argument('--设备截图方案', default=default('--设备截图方案'), choices=['aScreenCap', 'uiautomator2', 'ADB'], help='速度: aScreenCap >> uiautomator2 > ADB')
adb.add_argument('--设备控制方案', default=default('--设备控制方案'), choices=['uiautomator2', 'ADB'], help='速度: uiautomator2 >> ADB')
adb.add_argument('--战斗中截图间隔', default=default('--战斗中截图间隔'), help='战斗中放慢截图速度, 降低CPU使用')
# ==========每日任务==========

View File

@ -278,8 +278,8 @@ def main(ini_name=''):
debug.add_argument('--enable_perspective_error_image_save', default=default('--enable_perspective_error_image_save'), choices=['yes', 'no'])
adb = emulator_parser.add_argument_group('ADB settings', '')
adb.add_argument('--use_adb_screenshot', default=default('--use_adb_screenshot'), choices=['yes', 'no'], help='It is recommended to enable it to reduce CPU usage')
adb.add_argument('--use_adb_control', default=default('--use_adb_control'), choices=['yes', 'no'], help='Recommended off, can speed up the click speed')
adb.add_argument('--device_screenshot_method', default=default('--device_screenshot_method'), choices=['aScreenCap', 'uiautomator2', 'ADB'], help='Speed: aScreenCap >> uiautomator2 > ADB')
adb.add_argument('--device_control_method', default=default('--device_control_method'), choices=['uiautomator2', 'ADB'], help='Speed: uiautomator2 >> ADB')
adb.add_argument('--combat_screenshot_interval', default=default('--combat_screenshot_interval'), help='Slow down the screenshot speed during battle and reduce CPU')
# ==========每日任务==========

View File

@ -273,8 +273,8 @@ def main(ini_name=''):
debug.add_argument('--enable_perspective_error_image_save', default=default('--enable_perspective_error_image_save'), choices=['yes', 'no'])
adb = emulator_parser.add_argument_group('ADB settings', '')
adb.add_argument('--use_adb_screenshot', default=default('--use_adb_screenshot'), choices=['yes', 'no'], help='It is recommended to enable it to reduce CPU usage')
adb.add_argument('--use_adb_control', default=default('--use_adb_control'), choices=['yes', 'no'], help='Suggest to close, can speed up the click speed')
adb.add_argument('--device_screenshot_method', default=default('--device_screenshot_method'), choices=['aScreenCap', 'uiautomator2', 'ADB'], help='Speed: aScreenCap >> uiautomator2 > ADB')
adb.add_argument('--device_control_method', default=default('--device_control_method'), choices=['uiautomator2', 'ADB'], help='Speed: uiautomator2 >> ADB')
adb.add_argument('--combat_screenshot_interval', default=default('--combat_screenshot_interval'), help='Slow down the screenshot speed during battle and reduce CPU')
# ==========每日任务==========

View File

@ -152,8 +152,13 @@ class AzurLaneConfig:
SERIAL = ''
PACKAGE_NAME = ''
COMMAND = ''
USE_ADB_SCREENSHOT = True
USE_ADB_CONTROL = False
ASCREENCAP_FILEPATH = '/data/local/tmp/ascreencap'
# Speed: aScreenCap >> uiautomator2 > ADB
DEVICE_SCREENSHOT_METHOD = 'aScreenCap' # ADB, uiautomator2, aScreenCap
# Speed: uiautomator2 >> ADB
DEVICE_CONTROL_METHOD = 'uiautomator2' # ADB, uiautomator2
# USE_ADB_SCREENSHOT = True
# USE_ADB_CONTROL = False
SCREEN_SHOT_SAVE_FOLDER_BASE = './screenshot'
SCREEN_SHOT_SAVE_FOLDER = ''
SCREEN_SHOT_SAVE_INTERVAL = 5 # Seconds between two save. Saves in the interval will be dropped.
@ -405,8 +410,8 @@ class AzurLaneConfig:
self.PACKAGE_NAME = option['package_name'].strip()
self.ENABLE_ERROR_LOG_AND_SCREENSHOT_SAVE = to_bool(option['enable_error_log_and_screenshot_save'])
self.ENABLE_PERSPECTIVE_ERROR_IMAGE_SAVE = to_bool(option['enable_perspective_error_image_save'])
self.USE_ADB_SCREENSHOT = to_bool(option['use_adb_screenshot'])
self.USE_ADB_CONTROL = to_bool(option['use_adb_control'])
self.DEVICE_SCREENSHOT_METHOD = option['device_screenshot_method']
self.DEVICE_CONTROL_METHOD = option['device_control_method']
self.COMBAT_SCREENSHOT_INTERVAL = float(option['combat_screenshot_interval'])
option = config['Setting']

View File

@ -124,8 +124,8 @@ dic_true_eng_to_eng = {
'package_name': 'package_name',
'enable_error_log_and_screenshot_save': 'enable_error_log_and_screenshot_save',
'enable_perspective_error_image_save': 'enable_perspective_error_image_save',
'use_adb_screenshot': 'use_adb_screenshot',
'use_adb_control': 'use_adb_control',
'device_screenshot_method': 'device_screenshot_method',
'device_control_method': 'device_control_method',
'combat_screenshot_interval': 'combat_screenshot_interval',
'enable_daily_mission': 'enable_daily_mission',
'enable_hard_campaign': 'enable_hard_campaign',
@ -189,6 +189,9 @@ dic_true_eng_to_eng = {
'map_100': 'map_100',
'map_3_star': 'map_3_star',
'map_green': 'map_green',
'aScreenCap': 'aScreenCap',
'uiautomator2': 'uiautomator2',
'ADB': 'ADB',
'daily_air': 'daily_air',
'daily_gun': 'daily_gun',
'daily_torpedo': 'daily_torpedo',
@ -311,8 +314,8 @@ dic_chi_to_eng = {
'包名': 'package_name',
'出错时保存log和截图': 'enable_error_log_and_screenshot_save',
'保存透视识别出错的图像': 'enable_perspective_error_image_save',
'使用ADB截图': 'use_adb_screenshot',
'使用ADB点击': 'use_adb_control',
'设备截图方案': 'device_screenshot_method',
'设备控制方案': 'device_control_method',
'战斗中截图间隔': 'combat_screenshot_interval',
'打每日': 'enable_daily_mission',
'打困难': 'enable_hard_campaign',
@ -375,6 +378,9 @@ dic_chi_to_eng = {
'地图通关': 'map_100',
'地图三星': 'map_3_star',
'地图绿海': 'map_green',
'aScreenCap': 'aScreenCap',
'uiautomator2': 'uiautomator2',
'ADB': 'ADB',
'航空': 'daily_air',
'炮击': 'daily_gun',
'雷击': 'daily_torpedo',

View File

@ -1,3 +1,4 @@
import os
import subprocess
import requests
@ -18,6 +19,8 @@ class Connection:
self.serial = str(self.config.SERIAL)
self.device = self.connect(self.serial)
self.disable_uiautomator2_auto_quit()
if self.config.DEVICE_SCREENSHOT_METHOD == 'aScreenCap':
self._ascreencap_init()
@staticmethod
def adb_command(cmd, serial=None):
@ -29,13 +32,17 @@ class Connection:
# Use shell=True to disable console window when using GUI.
# Although, there's still a window when you stop running in GUI, which cause by gooey.
# To disable it, edit gooey/gui/util/taskkill.py
result = subprocess.check_output(cmd, timeout=4, stderr=subprocess.STDOUT, shell=True)
return result
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
return process.communicate(timeout=4)[0]
def adb_shell(self, cmd, serial=None):
cmd.insert(0, 'shell')
return self.adb_command(cmd, serial)
def adb_exec_out(self, cmd, serial=None):
cmd.insert(0, 'exec-out')
return self.adb_command(cmd, serial)
def _adb_connect(self, serial):
if serial.startswith('127.0.0.1'):
msg = self.adb_command(['connect', serial]).decode("utf-8")
@ -61,3 +68,22 @@ class Connection:
def disable_uiautomator2_auto_quit(self, port=7912, expire=300000):
self.adb_command(['forward', 'tcp:%s' % port, 'tcp:%s' % port], serial=self.serial)
requests.post('http://127.0.0.1:%s/newCommandTimeout' % port, data=str(expire))
def _ascreencap_init(self):
logger.hr('aScreenCap init')
arc = self.adb_exec_out(['getprop', 'ro.product.cpu.abi'], serial=self.serial).decode('utf-8').strip()
sdk = self.adb_exec_out(['getprop', 'ro.build.version.sdk'], serial=self.serial).decode('utf-8').strip()
logger.attr('CPU_Architecture', arc)
logger.attr('Android_SDK_version', sdk)
if int(sdk) not in range(21, 26) or not os.path.exists(f'./ascreencap/{arc}'):
logger.warning('No suitable version of aScreenCap lib is available locally')
exit(1)
filepath = f'./ascreencap/{arc}/ascreencap'
logger.info(f'Pushing {filepath} to {self.config.ASCREENCAP_FILEPATH}')
self.adb_command(['push', filepath, self.config.ASCREENCAP_FILEPATH], serial=self.serial)
logger.info(f'chmod 0777 {self.config.ASCREENCAP_FILEPATH}')
self.adb_shell(['chmod', '0777', self.config.ASCREENCAP_FILEPATH], serial=self.serial)

View File

@ -57,11 +57,11 @@ class Control(Connection):
logger.info(
'Click %s @ %s' % (self._point2str(x, y), button)
)
adb = self.config.USE_ADB_CONTROL
if adb:
self._click_adb(x, y)
else:
method = self.config.DEVICE_CONTROL_METHOD
if method == 'uiautomator2':
self._click_uiautomator2(x, y)
else:
self._click_adb(x, y)
self.sleep(self.config.SLEEP_AFTER_CLICK)
@retry()

View File

@ -3,19 +3,23 @@ import time
from datetime import datetime
from io import BytesIO
import cv2
import lz4.block
import numpy as np
from PIL import Image
from retrying import retry
from module.base.timer import Timer
from module.device.connection import Connection
from module.logger import logger
from module.base.timer import Timer
class Screenshot(Connection):
_screenshot_method = 0
_screenshot_method_fixed = False
_bytepointer = 0
_screenshot_interval_timer = Timer(0.1)
_adb = False
_last_save_time = {}
image: Image.Image
@ -49,6 +53,39 @@ class Screenshot(Connection):
screenshot = self.adb_shell(['screencap', '-p'], serial=self.serial)
return self._process_screenshot(screenshot)
def _reposition_byte_pointer(self, byte_array):
"""Method to return the sanitized version of ascreencap stdout for devices
that suffers from linker warnings. The correct pointer location will be saved
for subsequent screen refreshes
"""
while byte_array[self._bytepointer:self._bytepointer + 4] != b'BMZ1':
self._bytepointer += 1
if self._bytepointer >= len(byte_array):
text = 'Repositioning byte pointer failed, corrupted aScreenCap data received'
logger.warning(text)
exit(1)
return byte_array[self._bytepointer:]
def _screenshot_ascreencap(self):
raw_compressed_data = self._reposition_byte_pointer(
self.adb_exec_out([self.config.ASCREENCAP_FILEPATH, '--pack', '2', '--stdout'], serial=self.serial))
compressed_data_header = np.frombuffer(raw_compressed_data[0:20], dtype=np.uint32)
if compressed_data_header[0] != 828001602:
compressed_data_header = compressed_data_header.byteswap()
if compressed_data_header[0] != 828001602:
text = f'aScreenCap header verification failure, corrupted image received. ' \
f'HEADER IN HEX = {compressed_data_header.tobytes().hex()}'
logger.warning(text)
exit(1)
uncompressed_data_size = compressed_data_header[1].item()
data = lz4.block.decompress(raw_compressed_data[20:], uncompressed_size=uncompressed_data_size)
image = cv2.imdecode(np.frombuffer(data, dtype=np.uint8), cv2.IMREAD_COLOR)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image)
return image
@retry()
# @timer
def screenshot(self):
@ -58,13 +95,14 @@ class Screenshot(Connection):
"""
self._screenshot_interval_timer.wait()
self._screenshot_interval_timer.reset()
adb = self.config.USE_ADB_SCREENSHOT
self._adb = adb
method = self.config.DEVICE_SCREENSHOT_METHOD
if adb:
self.image = self._screenshot_adb()
else:
if method == 'aScreenCap':
self.image = self._screenshot_ascreencap()
elif method == 'uiautomator2':
self.image = self._screenshot_uiautomator2()
else:
self.image = self._screenshot_adb()
self.image.load()
if self.config.ENABLE_ERROR_LOG_AND_SCREENSHOT_SAVE:
@ -83,7 +121,7 @@ class Screenshot(Connection):
"""
now = time.time()
if now - self._last_save_time.get(genre, 0) > self.config.SCREEN_SHOT_SAVE_INTERVAL:
fmt = 'png' if self._adb else 'png'
fmt = 'png'
file = '%s.%s' % (int(now * 1000), fmt)
folder = os.path.join(self.config.SCREEN_SHOT_SAVE_FOLDER, genre)

View File

@ -3,6 +3,7 @@ scipy==1.4.1
pillow
opencv-python
scikit-image==0.16.2
lz4
requests<2.19.0,>=2.18.4
uiautomator2
retrying