This commit is contained in:
EightyDollars 2024-12-05 19:00:49 +08:00
commit 20e8a59b62
9 changed files with 146 additions and 67 deletions

View file

@ -346,6 +346,12 @@ class EmulatorPart(ConfModel):
rotate: bool = False
"将截图旋转180度"
class DroidCastRawConf(ConfModel):
orientation: Literal["portrait", "landscape"] = "landscape"
"屏幕方向"
rotate: Literal[0, 90, 180, 270] = 0
"截图旋转角度"
adb: str = "127.0.0.1:16384"
"ADB连接地址"
emulator: EmulatorConf
@ -356,6 +362,8 @@ class EmulatorPart(ConfModel):
"游戏服务器"
droidcast: DroidCastConf
"DroidCast截图设置"
droidcast_raw: DroidCastRawConf
"DroidCast截图设置"
custom_screenshot: CustomScreenshotConf
"自定义截图"
tap_to_launch_game: TapToLaunchGameConf
@ -369,10 +377,11 @@ class EmulatorPart(ConfModel):
screencap_strategy: Literal[
"adb",
"droidcast",
"droidcast_raw",
"mumuipc",
"diy",
"scrcpy",
] = "droidcast"
] = "droidcast_raw"
"截图方案"
control_strategy: Literal["scrcpy", "mumuipc"] = "scrcpy"
"触控方案"

View file

@ -12,6 +12,7 @@ from mower.utils.log import logger, save_screenshot
method_map = {
"adb": "mower.utils.device.method.adb.ADB",
"droidcast": "mower.utils.device.method.droidcast.DroidCast",
"droidcast_raw": "mower.utils.device.method.droidcast.DroidCast_raw",
"scrcpy": "mower.utils.device.method.scrcpy.Scrcpy",
"mumuipc": "mower.utils.device.method.mumu_ipc.MuMu12IPC",
"diy": "mower.utils.device.method.adb.DIY",

View file

@ -285,7 +285,7 @@ class ADB:
dpi = (override_dpi or physical_dpi).partition("density:")[2].strip()
if (
config.conf.emulator.name == "MuMu12"
and config.conf.screencap_strategy == "droidcast"
and config.conf.screencap_strategy.startswith("droidcast")
):
from mower.utils.device.method.mumumanager import MuMuManager

View file

@ -2,15 +2,16 @@ import time
from functools import cached_property, wraps
import cv2
import numpy as np
import requests
from mower import __rootdir__
from mower.utils import config
from mower.utils.csleep import MowerExit
from mower.utils.device.method.adb import ADB
from mower.utils.image import bytes2img
from mower.utils.log import logger
from mower.utils.network import get_new_port, is_port_in_use
from mower.utils.path import get_path
def retry_droidcast(func):
@ -29,6 +30,10 @@ def retry_droidcast(func):
class DroidCast:
display_name = "DroidCast"
package_name = "com.rayworks.droidcast"
apk_file = "DroidCast-debug-1.2.1.apk"
def __init__(self):
self.session = requests.Session()
self.port = 0
@ -40,7 +45,7 @@ class DroidCast:
def get_droidcast_classpath(self) -> str | None:
# TODO: 退出时(并非结束mower线程时)关闭DroidCast进程、取消ADB转发
out = self.adb.adb_shell("pm path com.rayworks.droidcast")
out = self.adb.adb_shell(f"pm path {self.package_name}")
if out is None:
logger.exception("无法获取CLASSPATH")
return None
@ -54,15 +59,18 @@ class DroidCast:
return class_path
def start_droidcast(self) -> bool:
class_path = self.get_droidcast_classpath()
try:
class_path = self.get_droidcast_classpath()
except Exception:
class_path = None
if not class_path:
logger.info("安装DroidCast")
apk_path = f"{__rootdir__}/vendor/DroidCast-debug-1.2.1.apk"
logger.info(f"安装{self.display_name}")
try:
self.adb.adb.install(apk_path)
logger.info("DroidCast安装完成,获取CLASSPATH")
apk_path = get_path(f"@install/mower/vendor/{self.apk_file}")
self.adb.adb.install(str(apk_path))
logger.info(f"{self.display_name}安装完成,获取CLASSPATH")
except Exception as e:
logger.error(f"DroidCast安装失败: {e}")
logger.error(f"{self.display_name}安装失败: {e}")
return False
class_path = self.get_droidcast_classpath()
if not class_path:
@ -83,24 +91,68 @@ class DroidCast:
if port == 0:
port = get_new_port()
self.port = port
logger.info(f"更新DroidCast端口为{port}")
logger.info(f"更新{self.display_name}端口为{port}")
else:
logger.info(f"保持DroidCast端口为{port}")
logger.info(f"保持{self.display_name}端口为{port}")
self.adb.adb.forward(f"tcp:{port}", f"tcp:{port}")
logger.info("ADB端口转发成功,启动DroidCast")
cmd = f"{class_path} app_process / com.rayworks.droidcast.Main --port={port}"
logger.info("ADB端口转发成功,启动{self.display_name}")
cmd = f"{class_path} app_process / {self.package_name}.Main --port={port}"
self.socket = self.adb.adb_shell(cmd, True)
time.sleep(1) # TODO: 更好地等待DroidCast启动
return True
@retry_droidcast
def capture_display(self):
if self.port == 0:
raise Exception("DroidCast未启动")
url = f"http://127.0.0.1:{self.port}/screenshot"
logger.debug(f"GET {url}")
r = self.session.get(url)
img = bytes2img(r.content)
def decode(self, data):
img = bytes2img(data)
if config.conf.droidcast.rotate:
img = cv2.rotate(img, cv2.ROTATE_180)
return img
@retry_droidcast
def capture_display(self):
if self.port == 0:
raise Exception(f"{self.display_name}未启动")
url = f"http://127.0.0.1:{self.port}/screenshot"
logger.debug(f"GET {url}")
r = self.session.get(url)
return self.decode(r.content)
class DroidCast_raw(DroidCast):
display_name = "DroidCast_raw"
package_name = "ink.mol.droidcast_raw"
apk_file = "DroidCast_raw-release-1.1.apk"
def decode(self, data):
# https://github.com/LmeSzinc/StarRailCopilot/blob/bf62eccaaa6f46ea9796a83175df0ab7882dd137/module/device/method/droidcast.py#L226
arr = np.frombuffer(data, dtype=np.uint16)
if config.conf.droidcast_raw.orientation == "portrait":
shape = (1080, 1920)
else:
shape = (1920, 1080)
arr = arr.reshape(shape)
r = cv2.bitwise_and(arr, 0b1111100000000000)
r = cv2.convertScaleAbs(r, alpha=0.00390625)
m = cv2.convertScaleAbs(r, alpha=0.03125)
cv2.add(r, m, dst=r)
g = cv2.bitwise_and(arr, 0b0000011111100000)
g = cv2.convertScaleAbs(g, alpha=0.125)
m = cv2.convertScaleAbs(g, alpha=0.015625, dst=m)
cv2.add(g, m, dst=g)
b = cv2.bitwise_and(arr, 0b0000000000011111)
b = cv2.convertScaleAbs(b, alpha=8)
m = cv2.convertScaleAbs(b, alpha=0.03125, dst=m)
cv2.add(b, m, dst=b)
img = cv2.merge([r, g, b])
if config.conf.droidcast_raw.rotate == 90:
img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
elif config.conf.droidcast_raw.rotate == 180:
img = cv2.rotate(img, cv2.ROTATE_180)
elif config.conf.droidcast_raw.rotate == 270:
img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)
return img

View file

@ -145,7 +145,10 @@ class MuMuManager:
width = int(float(self.get_field_value(data, "resolution_width.custom")))
height = int(float(self.get_field_value(data, "resolution_height.custom")))
dpi = int(float(self.get_field_value(data, "resolution_dpi.custom")))
if config.conf.screencap_strategy == "droidcast" and self.app_kept_alive():
if (
config.conf.screencap_strategy.startswith("droidcast")
and self.app_kept_alive()
):
logger.error("MuMu12的droidcast截图策略下,需要关闭应用后台保活")
try:
self.set_app_kept_alive_false()