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 package_name = com.bilibili.azurlane
enable_error_log_and_screenshot_save = yes enable_error_log_and_screenshot_save = yes
enable_perspective_error_image_save = no enable_perspective_error_image_save = no
use_adb_screenshot = yes device_screenshot_method = aScreenCap
use_adb_control = no device_control_method = uiautomator2
combat_screenshot_interval = 1. combat_screenshot_interval = 1.
[Daily] [Daily]

View File

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

View File

@ -280,8 +280,8 @@ def main(ini_name=''):
debug.add_argument('--保存透视识别出错的图像', default=default('--保存透视识别出错的图像'), choices=['', '']) debug.add_argument('--保存透视识别出错的图像', default=default('--保存透视识别出错的图像'), choices=['', ''])
adb = emulator_parser.add_argument_group('ADB设置', '') adb = emulator_parser.add_argument_group('ADB设置', '')
adb.add_argument('--使用ADB截图', default=default('--使用ADB截图'), choices=['', ''], help='建议开启, 能减少CPU占用') adb.add_argument('--设备截图方案', default=default('--设备截图方案'), choices=['aScreenCap', 'uiautomator2', 'ADB'], help='速度: aScreenCap >> uiautomator2 > ADB')
adb.add_argument('--使用ADB点击', default=default('--使用ADB点击'), choices=['', ''], help='建议关闭, 能加快点击速度') adb.add_argument('--设备控制方案', default=default('--设备控制方案'), choices=['uiautomator2', 'ADB'], help='速度: uiautomator2 >> ADB')
adb.add_argument('--战斗中截图间隔', default=default('--战斗中截图间隔'), help='战斗中放慢截图速度, 降低CPU使用') 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']) 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 = 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('--device_screenshot_method', default=default('--device_screenshot_method'), choices=['aScreenCap', 'uiautomator2', 'ADB'], help='Speed: aScreenCap >> uiautomator2 > ADB')
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_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') 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']) 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 = 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('--device_screenshot_method', default=default('--device_screenshot_method'), choices=['aScreenCap', 'uiautomator2', 'ADB'], help='Speed: aScreenCap >> uiautomator2 > ADB')
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_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') 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 = '' SERIAL = ''
PACKAGE_NAME = '' PACKAGE_NAME = ''
COMMAND = '' COMMAND = ''
USE_ADB_SCREENSHOT = True ASCREENCAP_FILEPATH = '/data/local/tmp/ascreencap'
USE_ADB_CONTROL = False # 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_BASE = './screenshot'
SCREEN_SHOT_SAVE_FOLDER = '' SCREEN_SHOT_SAVE_FOLDER = ''
SCREEN_SHOT_SAVE_INTERVAL = 5 # Seconds between two save. Saves in the interval will be dropped. 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.PACKAGE_NAME = option['package_name'].strip()
self.ENABLE_ERROR_LOG_AND_SCREENSHOT_SAVE = to_bool(option['enable_error_log_and_screenshot_save']) 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.ENABLE_PERSPECTIVE_ERROR_IMAGE_SAVE = to_bool(option['enable_perspective_error_image_save'])
self.USE_ADB_SCREENSHOT = to_bool(option['use_adb_screenshot']) self.DEVICE_SCREENSHOT_METHOD = option['device_screenshot_method']
self.USE_ADB_CONTROL = to_bool(option['use_adb_control']) self.DEVICE_CONTROL_METHOD = option['device_control_method']
self.COMBAT_SCREENSHOT_INTERVAL = float(option['combat_screenshot_interval']) self.COMBAT_SCREENSHOT_INTERVAL = float(option['combat_screenshot_interval'])
option = config['Setting'] option = config['Setting']

View File

@ -124,8 +124,8 @@ dic_true_eng_to_eng = {
'package_name': 'package_name', 'package_name': 'package_name',
'enable_error_log_and_screenshot_save': 'enable_error_log_and_screenshot_save', 'enable_error_log_and_screenshot_save': 'enable_error_log_and_screenshot_save',
'enable_perspective_error_image_save': 'enable_perspective_error_image_save', 'enable_perspective_error_image_save': 'enable_perspective_error_image_save',
'use_adb_screenshot': 'use_adb_screenshot', 'device_screenshot_method': 'device_screenshot_method',
'use_adb_control': 'use_adb_control', 'device_control_method': 'device_control_method',
'combat_screenshot_interval': 'combat_screenshot_interval', 'combat_screenshot_interval': 'combat_screenshot_interval',
'enable_daily_mission': 'enable_daily_mission', 'enable_daily_mission': 'enable_daily_mission',
'enable_hard_campaign': 'enable_hard_campaign', 'enable_hard_campaign': 'enable_hard_campaign',
@ -189,6 +189,9 @@ dic_true_eng_to_eng = {
'map_100': 'map_100', 'map_100': 'map_100',
'map_3_star': 'map_3_star', 'map_3_star': 'map_3_star',
'map_green': 'map_green', 'map_green': 'map_green',
'aScreenCap': 'aScreenCap',
'uiautomator2': 'uiautomator2',
'ADB': 'ADB',
'daily_air': 'daily_air', 'daily_air': 'daily_air',
'daily_gun': 'daily_gun', 'daily_gun': 'daily_gun',
'daily_torpedo': 'daily_torpedo', 'daily_torpedo': 'daily_torpedo',
@ -311,8 +314,8 @@ dic_chi_to_eng = {
'包名': 'package_name', '包名': 'package_name',
'出错时保存log和截图': 'enable_error_log_and_screenshot_save', '出错时保存log和截图': 'enable_error_log_and_screenshot_save',
'保存透视识别出错的图像': 'enable_perspective_error_image_save', '保存透视识别出错的图像': 'enable_perspective_error_image_save',
'使用ADB截图': 'use_adb_screenshot', '设备截图方案': 'device_screenshot_method',
'使用ADB点击': 'use_adb_control', '设备控制方案': 'device_control_method',
'战斗中截图间隔': 'combat_screenshot_interval', '战斗中截图间隔': 'combat_screenshot_interval',
'打每日': 'enable_daily_mission', '打每日': 'enable_daily_mission',
'打困难': 'enable_hard_campaign', '打困难': 'enable_hard_campaign',
@ -375,6 +378,9 @@ dic_chi_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',

View File

@ -1,3 +1,4 @@
import os
import subprocess import subprocess
import requests import requests
@ -18,6 +19,8 @@ class Connection:
self.serial = str(self.config.SERIAL) self.serial = str(self.config.SERIAL)
self.device = self.connect(self.serial) self.device = self.connect(self.serial)
self.disable_uiautomator2_auto_quit() self.disable_uiautomator2_auto_quit()
if self.config.DEVICE_SCREENSHOT_METHOD == 'aScreenCap':
self._ascreencap_init()
@staticmethod @staticmethod
def adb_command(cmd, serial=None): def adb_command(cmd, serial=None):
@ -29,13 +32,17 @@ class Connection:
# Use shell=True to disable console window when using GUI. # 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. # 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 # To disable it, edit gooey/gui/util/taskkill.py
result = subprocess.check_output(cmd, timeout=4, stderr=subprocess.STDOUT, shell=True) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
return result return process.communicate(timeout=4)[0]
def adb_shell(self, cmd, serial=None): def adb_shell(self, cmd, serial=None):
cmd.insert(0, 'shell') cmd.insert(0, 'shell')
return self.adb_command(cmd, serial) 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): def _adb_connect(self, serial):
if serial.startswith('127.0.0.1'): if serial.startswith('127.0.0.1'):
msg = self.adb_command(['connect', serial]).decode("utf-8") 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): def disable_uiautomator2_auto_quit(self, port=7912, expire=300000):
self.adb_command(['forward', 'tcp:%s' % port, 'tcp:%s' % port], serial=self.serial) 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)) 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( logger.info(
'Click %s @ %s' % (self._point2str(x, y), button) 'Click %s @ %s' % (self._point2str(x, y), button)
) )
adb = self.config.USE_ADB_CONTROL method = self.config.DEVICE_CONTROL_METHOD
if adb: if method == 'uiautomator2':
self._click_adb(x, y)
else:
self._click_uiautomator2(x, y) self._click_uiautomator2(x, y)
else:
self._click_adb(x, y)
self.sleep(self.config.SLEEP_AFTER_CLICK) self.sleep(self.config.SLEEP_AFTER_CLICK)
@retry() @retry()

View File

@ -3,19 +3,23 @@ import time
from datetime import datetime from datetime import datetime
from io import BytesIO from io import BytesIO
import cv2
import lz4.block
import numpy as np
from PIL import Image from PIL import Image
from retrying import retry from retrying import retry
from module.base.timer import Timer
from module.device.connection import Connection from module.device.connection import Connection
from module.logger import logger from module.logger import logger
from module.base.timer import Timer
class Screenshot(Connection): class Screenshot(Connection):
_screenshot_method = 0 _screenshot_method = 0
_screenshot_method_fixed = False _screenshot_method_fixed = False
_bytepointer = 0
_screenshot_interval_timer = Timer(0.1) _screenshot_interval_timer = Timer(0.1)
_adb = False
_last_save_time = {} _last_save_time = {}
image: Image.Image image: Image.Image
@ -49,6 +53,39 @@ class Screenshot(Connection):
screenshot = self.adb_shell(['screencap', '-p'], serial=self.serial) screenshot = self.adb_shell(['screencap', '-p'], serial=self.serial)
return self._process_screenshot(screenshot) 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() @retry()
# @timer # @timer
def screenshot(self): def screenshot(self):
@ -58,13 +95,14 @@ class Screenshot(Connection):
""" """
self._screenshot_interval_timer.wait() self._screenshot_interval_timer.wait()
self._screenshot_interval_timer.reset() self._screenshot_interval_timer.reset()
adb = self.config.USE_ADB_SCREENSHOT method = self.config.DEVICE_SCREENSHOT_METHOD
self._adb = adb
if adb: if method == 'aScreenCap':
self.image = self._screenshot_adb() self.image = self._screenshot_ascreencap()
else: elif method == 'uiautomator2':
self.image = self._screenshot_uiautomator2() self.image = self._screenshot_uiautomator2()
else:
self.image = self._screenshot_adb()
self.image.load() self.image.load()
if self.config.ENABLE_ERROR_LOG_AND_SCREENSHOT_SAVE: if self.config.ENABLE_ERROR_LOG_AND_SCREENSHOT_SAVE:
@ -83,7 +121,7 @@ class Screenshot(Connection):
""" """
now = time.time() now = time.time()
if now - self._last_save_time.get(genre, 0) > self.config.SCREEN_SHOT_SAVE_INTERVAL: 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) file = '%s.%s' % (int(now * 1000), fmt)
folder = os.path.join(self.config.SCREEN_SHOT_SAVE_FOLDER, genre) folder = os.path.join(self.config.SCREEN_SHOT_SAVE_FOLDER, genre)

View File

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