diff --git a/resource/config.json b/resource/config.json index 02077e29c0..549d7cc014 100644 --- a/resource/config.json +++ b/resource/config.json @@ -76,7 +76,8 @@ "stop": "[Adb] -s [AdbSerial] shell am force-stop [PackageName]", "back_to_home": "[Adb] -s [AdbSerial] shell input keyevent HOME", "release": "[Adb] kill-server", - "pressEsc": "[Adb] -s [AdbSerial] shell input keyevent 111" + "pressEsc": "[Adb] -s [AdbSerial] shell input keyevent 111", + "fps": "[Adb] -s [AdbSerial] shell \"dumpsys SurfaceFlinger --latency | head -n 1\"" }, { "configName": "CapWithShell", diff --git a/src/MaaCore/Config/GeneralConfig.cpp b/src/MaaCore/Config/GeneralConfig.cpp index 4c5f3f9b02..82e4416047 100644 --- a/src/MaaCore/Config/GeneralConfig.cpp +++ b/src/MaaCore/Config/GeneralConfig.cpp @@ -87,6 +87,7 @@ bool asst::GeneralConfig::parse(const json::value& json) adb.call_maatouch = cfg_json.get("callMaatouch", base_cfg.call_maatouch); adb.event_id = cfg_json.get("eventId", base_cfg.event_id); adb.back_to_home = cfg_json.get("back_to_home", base_cfg.back_to_home); + adb.fps = cfg_json.get("fps", base_cfg.fps); } return true; diff --git a/src/MaaCore/Config/GeneralConfig.h b/src/MaaCore/Config/GeneralConfig.h index 4a17c18ff5..61297188ba 100644 --- a/src/MaaCore/Config/GeneralConfig.h +++ b/src/MaaCore/Config/GeneralConfig.h @@ -87,6 +87,7 @@ struct AdbCfg std::string call_maatouch; std::string event_id; std::string back_to_home; + std::string fps; // 获取模拟器刷新率(SurfaceFlinger 帧间隔,单位纳秒) json::object extras; }; diff --git a/src/MaaCore/Controller/AdbController.cpp b/src/MaaCore/Controller/AdbController.cpp index a6c1ed5b7c..ab5be92bdf 100644 --- a/src/MaaCore/Controller/AdbController.cpp +++ b/src/MaaCore/Controller/AdbController.cpp @@ -3,6 +3,7 @@ #include "Assistant.h" #include "Controller.h" #include "MaaUtils/NoWarningCV.hpp" +#include #include #include @@ -352,6 +353,7 @@ void asst::AdbController::clear_info() noexcept m_width = 0; m_height = 0; m_screen_size = { 0, 0 }; + m_last_fps_check_time = {}; // 重置帧率检测计时,重连后立即检测一次 } bool asst::AdbController::inited() const noexcept @@ -787,6 +789,10 @@ bool asst::AdbController::screencap(cv::Mat& image_payload, bool allow_reconnect } callback(AsstMsg::ConnectionInfo, info); } + + // 每 1 分钟检测一次模拟器帧率 + check_fps(); + return screencap_ret; } } @@ -1156,6 +1162,7 @@ bool asst::AdbController::connect(const std::string& adb_path, const std::string m_adb.start = m_conn_ctx.replace_cmd(adb_cfg.start); m_adb.stop = m_conn_ctx.replace_cmd(adb_cfg.stop); m_adb.back_to_home = m_conn_ctx.replace_cmd(adb_cfg.back_to_home); + m_adb.fps = m_conn_ctx.replace_cmd(adb_cfg.fps); if (m_support_socket && !m_server_started) { std::string bind_address; @@ -1214,6 +1221,74 @@ void asst::AdbController::clear_lf_info() m_adb.screencap_end_of_line = AdbProperty::ScreencapEndOfLine::UnknownYet; } +void asst::AdbController::check_fps() +{ + // 命令未配置或尚未连接,跳过 + if (m_adb.fps.empty()) { + return; + } + + // 每 1 分钟检测一次 + constexpr auto FpsCheckInterval = std::chrono::minutes(1); + auto now = std::chrono::steady_clock::now(); + if (m_last_fps_check_time.time_since_epoch().count() != 0 && + now - m_last_fps_check_time < FpsCheckInterval) { + return; + } + m_last_fps_check_time = now; + + auto ret = call_command(m_adb.fps, 100, false /* 帧率检测不触发重连,避免拖慢截图流程 */); + if (!ret || ret.value().empty()) { + Log.warn("fps command failed or empty"); + return; + } + + // SurfaceFlinger --latency 第一行是每帧刷新周期(纳秒),例如 16666666 表示 60 FPS + // 注意:这里检测的是模拟器/系统的设置刷新率,而非游戏实际运行帧率。 + // 游戏原生帧率上限为 60 FPS,高于 60 通常为模拟器插帧,不作为异常处理。 + auto& output = ret.value(); + convert_lf(output); + auto newline_pos = output.find('\n'); + std::string first_line = newline_pos == std::string::npos ? output : output.substr(0, newline_pos); + + // 去掉空白和非数字字符 + std::erase_if(first_line, [](char c) { return !std::isdigit(static_cast(c)); }); + + if (first_line.empty()) { + Log.warn("fps output is empty after sanitize"); + return; + } + + long long refresh_period_ns = 0; + try { + refresh_period_ns = std::stoll(first_line); + } + catch (...) { + Log.warn("fps output parse failed:", first_line); + return; + } + + if (refresh_period_ns <= 0) { + Log.warn("invalid refresh period:", refresh_period_ns); + return; + } + + // ns -> FPS + double fps = 1000000000.0 / static_cast(refresh_period_ns); + int fps_int = static_cast(std::round(fps)); + + json::value info = json::object { + { "uuid", m_uuid }, + { "what", "EmulatorFPS" }, + { "details", + json::object { + { "fps", fps_int }, + { "refresh_period_ns", refresh_period_ns }, + } }, + }; + callback(AsstMsg::ConnectionInfo, info); +} + void asst::AdbController::back_to_home() noexcept { call_command(m_adb.back_to_home); diff --git a/src/MaaCore/Controller/AdbController.h b/src/MaaCore/Controller/AdbController.h index f6f515f0db..c02f287a9c 100644 --- a/src/MaaCore/Controller/AdbController.h +++ b/src/MaaCore/Controller/AdbController.h @@ -2,6 +2,7 @@ #include "ControllerAPI.h" +#include #include #include @@ -147,6 +148,9 @@ protected: // 导致解码错误,所以这里转一下回来(点名批评 mumu 和雷电) static bool convert_lf(std::string& data); + // 每 1 分钟检测一次模拟器帧率,回调给 UI 用于低帧率提示 + void check_fps(); + AdbConnectionContext m_conn_ctx; AsstCallback m_callback; @@ -180,6 +184,8 @@ protected: std::string back_to_home; + std::string fps; // 获取模拟器刷新率的命令 + /* properties */ enum class ScreencapEndOfLine { @@ -216,6 +222,7 @@ protected: long long m_last_command_duration = 0; // 上次命令执行用时 std::deque m_screencap_cost; // 截图用时 int m_screencap_times = 0; // 截图次数 + std::chrono::steady_clock::time_point m_last_fps_check_time; // 上次帧率检测时间 #if ASST_WITH_EMULATOR_EXTRAS MumuExtras m_mumu_extras; diff --git a/src/MaaWpfGui/Helper/Instances.cs b/src/MaaWpfGui/Helper/Instances.cs index 53f3d6cd42..0b2a8a5da7 100644 --- a/src/MaaWpfGui/Helper/Instances.cs +++ b/src/MaaWpfGui/Helper/Instances.cs @@ -44,6 +44,8 @@ public static class Instances public static bool HasPrintedScreencapWarning { get; set; } = false; + public static bool HasPrintedFpsHighTip { get; set; } = false; + public static int RecruitConfirmTime { get; set; } = 0; public static void ClearCache() @@ -54,6 +56,7 @@ public static class Instances StoneUsedTimes = 0; RecruitConfirmTime = 0; HasPrintedScreencapWarning = false; + HasPrintedFpsHighTip = false; } } diff --git a/src/MaaWpfGui/Main/AsstProxy.cs b/src/MaaWpfGui/Main/AsstProxy.cs index 0c26067ee3..766b306737 100644 --- a/src/MaaWpfGui/Main/AsstProxy.cs +++ b/src/MaaWpfGui/Main/AsstProxy.cs @@ -1003,6 +1003,37 @@ public class AsstProxy } } + break; + + case "EmulatorFPS": + var fpsValue = details["details"]?["fps"]?.ToString() ?? "???"; + if (!int.TryParse(fpsValue, out var fpsInt)) + { + break; + } + + if (fpsInt <= 0) + { + break; + } + + if (fpsInt < 30) + { + Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetStringFormat("EmulatorFpsErrorTip", fpsInt), UiLogColor.Error); + Instances.CopilotViewModel.AddLog(LocalizationHelper.GetStringFormat("EmulatorFpsErrorTip", fpsInt), UiLogColor.Error, showTime: false); + } + else if (fpsInt < 60) + { + Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetStringFormat("EmulatorFpsWarningTip", fpsInt), UiLogColor.Warning); + Instances.CopilotViewModel.AddLog(LocalizationHelper.GetStringFormat("EmulatorFpsWarningTip", fpsInt), UiLogColor.Warning, showTime: false); + } + else if (fpsInt > 60 && !HasPrintedFpsHighTip) + { + Instances.TaskQueueViewModel.AddLog(LocalizationHelper.GetStringFormat("EmulatorFpsHighTip", fpsInt), UiLogColor.Warning); + Instances.CopilotViewModel.AddLog(LocalizationHelper.GetStringFormat("EmulatorFpsHighTip", fpsInt), UiLogColor.Warning, showTime: false); + HasPrintedFpsHighTip = true; + } + break; } } diff --git a/src/MaaWpfGui/Res/Localizations/en-us.xaml b/src/MaaWpfGui/Res/Localizations/en-us.xaml index 14e1d3cba5..d530dc9da2 100644 --- a/src/MaaWpfGui/Res/Localizations/en-us.xaml +++ b/src/MaaWpfGui/Res/Localizations/en-us.xaml @@ -1214,6 +1214,9 @@ Right-click to clear inactive jobs Screenshot test tasks: {0}ms ({1}) Screencap takes a long time (avg: {0}ms) which may cause some abnormal behaviors using automatic combat functions (e.g. Auto I. S.). If using MuMu or LDPlayer, please try enabling screenshot enhancement Screencap takes too long (avg: {0}ms), so automatic combat functions (e.g. Auto I. S.) may not run properly. It is recommended to try restarting or changing the emulator! If using MuMu or LDPlayer, please try enabling screenshot enhancement + Emulator refresh rate is set to {0} FPS, below the game's native frame rate (60 FPS). Background frame rate reduction may be enabled or the rate is set too low. It is recommended to set the emulator frame rate to 60 FPS + Emulator refresh rate is set too low ({0} FPS), which may cause tasks to malfunction. Please turn off background frame rate reduction and set the frame rate to 60 FPS + Emulator refresh rate is set to {0} FPS, above the game's native frame rate cap (60 FPS). This may be the emulator's frame interpolation feature, which can cause visual artifacts or unstable recognition. It is recommended to disable frame interpolation and set the frame rate to 60 FPS Recognition error Task error:  Combat error diff --git a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml index 8c3c02d669..bc945c44fd 100644 --- a/src/MaaWpfGui/Res/Localizations/ja-jp.xaml +++ b/src/MaaWpfGui/Res/Localizations/ja-jp.xaml @@ -1215,6 +1215,9 @@ C:\\leidian\\LDPlayer9 スクリーンショット テストには: {0}ms ({1}) スクリーンキャップには時間がかかるため(平均: {0}ms)、自動戦闘機能 (Auto I.S. など) を使用すると異常な動作が発生する可能性があります。MuMuやLDPlayerを使用している場合は、スクリーンショット拡張を試してください。 スクリーンキャップに時間がかかりすぎるため(平均: {0}ms)、自動戦闘機能 (Auto I.S. など) が適切に動作しない可能性があります、シミュレータの再起動や交換をお勧めします!MuMuやLDPlayerを使用している場合は、スクリーンショット拡張を試してください。 + エミュレータの設定フレームレートが {0} FPS で、ゲームのネイティブフレームレート(60 FPS)を下回っています。バックグラウンドのフレームレート低下が有効になっているか、フレームレートの設定が低すぎる可能性があります。エミュレータのフレームレートを 60 FPS に設定することを推奨します + エミュレータの設定フレームレートが低すぎます({0} FPS)。タスクが異常になる可能性があります。エミュレータの設定でバックグラウンドのフレームレート低下をオフにし、フレームレートを 60 FPS に設定してください + エミュレータの設定フレームレートが {0} FPS で、ゲームのネイティブフレームレート上限(60 FPS)を上回っています。これはエミュレータのフレーム補間機能の可能性があり、画面の異常や認識の不安定化を招くことがあります。フレーム補間を無効にし、フレームレートを 60 FPS に設定することを推奨します 認識エラー タスクエラー: 戦闘エラー diff --git a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml index 5ef3ea848d..a01e67a367 100644 --- a/src/MaaWpfGui/Res/Localizations/ko-kr.xaml +++ b/src/MaaWpfGui/Res/Localizations/ko-kr.xaml @@ -1216,6 +1216,9 @@ C:\\leidian\\LDPlayer9 스크린샷 캡처 속도: {0}ms ({1}) 스크린샷 캡처 속도가 느려서 (평균: {0}ms) Copilot 기능에서 일부 비정상적인 동작이 발생할 수 있습니다. MuMu 또는 LDPlayer를 사용하는 경우 스크린샷 향상 기능을 활성화해 보세요. 스크린샷 캡처 속도가 너무 느려서 (평균: {0}ms) Copliot 기능이 제대로 실행되지 않을 수 있습니다. 에뮬레이터를 재시작하거나 변경하는 것을 권장합니다! MuMu 또는 LDPlayer를 사용하는 경우 스크린샷 향상 기능을 활성화해 보세요. + 에뮬레이터 설정 주사율이 {0} FPS로 게임의 기본 프레임 속도(60 FPS)보다 낮습니다. 백그라운드 프레임 저하가 활성화되어 있거나 주사율이 너무 낮게 설정되어 있을 수 있습니다. 에뮬레이터 프레임 속도를 60 FPS로 설정하는 것을 권장합니다 + 에뮬레이터 설정 주사율이 너무 낮습니다 ({0} FPS). 작업에 문제가 발생할 수 있습니다. 에뮬레이터 설정에서 백그라운드 프레임 저하를 끄고 프레임 속도를 60 FPS로 설정해 주세요 + 에뮬레이터 설정 주사율이 {0} FPS로 게임의 기본 프레임 속도 상한(60 FPS)을 초과합니다. 이는 에뮬레이터의 프레임 보간 기능일 수 있으며, 화면 왜곡이나 인식 불안정을 유발할 수 있습니다. 프레임 보간을 비활성화하고 프레임 속도를 60 FPS로 설정하는 것을 권장합니다 인식 오류 작업 오류: 작전 오류 diff --git a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml index 53daa7c6ed..7679aa3d6e 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-cn.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-cn.xaml @@ -1215,6 +1215,9 @@ C:\\leidian\\LDPlayer9。\n 最快截图耗时: {0}ms ({1}) 截图用时较长(平均: {0}ms),自动战斗类功能(如自动肉鸽)可能表现异常。如使用 MuMu、雷电模拟器请尝试开启截图增强 截图用时过长(平均: {0}ms),自动战斗类功能(如自动肉鸽)很可能无法正常运行,建议尝试重启或更换模拟器!如使用 MuMu、雷电模拟器请尝试开启截图增强 + 检测到模拟器设置帧率为 {0} FPS,低于游戏原生帧率(60 FPS),可能开启了后台降帧或帧率设置过低,建议将模拟器帧率设置为 60 FPS + 检测到模拟器设置帧率过低({0} FPS),可能导致任务异常,请关闭模拟器后台降帧并将帧率设置为 60 FPS + 检测到模拟器设置帧率为 {0} FPS,高于游戏原生帧率上限(60 FPS),可能是模拟器插帧功能,可能导致画面异常或识别不稳定,建议关闭插帧并将帧率设置为 60 FPS 识别错误 任务出错: 战斗出错 diff --git a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml index e747eb9ca4..7b450878f8 100644 --- a/src/MaaWpfGui/Res/Localizations/zh-tw.xaml +++ b/src/MaaWpfGui/Res/Localizations/zh-tw.xaml @@ -1215,6 +1215,9 @@ C:\\leidian\\LDPlayer9\n 最快截圖用時: {0}ms ({1}) 截圖用時較長 (平均: {0}ms),執行自動戰鬥類功能(如自動肉鴿)時可能表現異常。若使用 MuMu、雷電模擬器請嘗試開啟截圖增強 截圖用時過長 (平均: {0}ms),自動戰鬥類功能(如自動肉鴿)很可能無法正常執行,建議嘗試重啟或更換模擬器!若使用 MuMu、雷電模擬器請嘗試開啟截圖增強 + 偵測到模擬器設定幀率為 {0} FPS,低於遊戲原生幀率(60 FPS),可能開啟了背景降幀或幀率設定過低,建議將模擬器幀率設定為 60 FPS + 偵測到模擬器設定幀率過低({0} FPS),可能導致任務異常,請關閉模擬器背景降幀並將幀率設定為 60 FPS + 偵測到模擬器設定幀率為 {0} FPS,高於遊戲原生幀率上限(60 FPS),可能是模擬器插幀功能,可能導致畫面異常或辨識不穩定,建議關閉插幀並將幀率設定為 60 FPS 辨識錯誤 任務出錯: 戰鬥出錯