feat: 优化不跟手的问题
This commit is contained in:
@@ -42,6 +42,9 @@ public class InputCommandHandler {
|
||||
case "LONG_PRESS":
|
||||
handleLongPress(command);
|
||||
break;
|
||||
case "MOTION_EVENT":
|
||||
handleMotionEvent(command);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unknown action: " + action);
|
||||
}
|
||||
@@ -50,6 +53,15 @@ public class InputCommandHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMotionEvent(JsonObject command) {
|
||||
int action = command.get("motionAction").getAsInt();
|
||||
float relX = command.get("x").getAsFloat();
|
||||
float relY = command.get("y").getAsFloat();
|
||||
int x = (int) (relX * screenWidth);
|
||||
int y = (int) (relY * screenHeight);
|
||||
inputExecutor.injectMotionEvent(action, x, y);
|
||||
}
|
||||
|
||||
private void handleTouch(JsonObject command) {
|
||||
float relX = command.get("x").getAsFloat();
|
||||
float relY = command.get("y").getAsFloat();
|
||||
|
||||
@@ -8,4 +8,5 @@ public interface InputExecutor {
|
||||
void injectLongPress(int x, int y);
|
||||
void injectSwipe(int x1, int y1, int x2, int y2, long duration);
|
||||
void injectKeyEvent(int keyCode);
|
||||
void injectMotionEvent(int action, int x, int y);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ public class ShellInputUtils implements InputExecutor {
|
||||
executeShellCommand(String.format(Locale.US, "input keyevent %d", keyCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectMotionEvent(int action, int x, int y) {
|
||||
// Shell 命令不支持高效的连续手势追踪,这里仅作兼容处理
|
||||
if (action == 0) { // ACTION_DOWN
|
||||
injectTap(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void executeShellCommand(String command) {
|
||||
try {
|
||||
Log.d(TAG, "Executing shell command: " + command);
|
||||
|
||||
@@ -19,6 +19,7 @@ public class SystemInputUtils implements InputExecutor {
|
||||
private Object mInputManager;
|
||||
private Method mInjectInputEventMethod;
|
||||
private static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
|
||||
private long mDownTime;
|
||||
|
||||
public SystemInputUtils() {
|
||||
try {
|
||||
@@ -73,6 +74,15 @@ public class SystemInputUtils implements InputExecutor {
|
||||
injectKeyEvent(now, SystemClock.uptimeMillis(), KeyEvent.ACTION_UP, keyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectMotionEvent(int action, int x, int y) {
|
||||
long now = SystemClock.uptimeMillis();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mDownTime = now;
|
||||
}
|
||||
injectMotionEvent(mDownTime, now, action, x, y);
|
||||
}
|
||||
|
||||
private void injectMotionEvent(long downTime, long eventTime, int action, float x, float y) {
|
||||
MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0);
|
||||
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
|
||||
|
||||
@@ -93,9 +93,23 @@ public class ScreenCaptureService extends Service {
|
||||
} else {
|
||||
wm.getDefaultDisplay().getMetrics(dm);
|
||||
}
|
||||
int width = dm.widthPixels;
|
||||
int height = dm.heightPixels;
|
||||
|
||||
int realWidth = dm.widthPixels;
|
||||
int realHeight = dm.heightPixels;
|
||||
|
||||
// 分辨率缩放逻辑:如果设备分辨率大于 1080p,则等比例缩小到 1080p 以内
|
||||
int captureWidth = realWidth;
|
||||
int captureHeight = realHeight;
|
||||
int maxResolution = 1920; // 1080p 的长边
|
||||
|
||||
if (Math.max(realWidth, realHeight) > maxResolution) {
|
||||
float scale = (float) maxResolution / Math.max(realWidth, realHeight);
|
||||
captureWidth = Math.round(realWidth * scale);
|
||||
captureHeight = Math.round(realHeight * scale);
|
||||
// 确保是偶数,部分编码器要求宽高为偶数
|
||||
if (captureWidth % 2 != 0) captureWidth--;
|
||||
if (captureHeight % 2 != 0) captureHeight--;
|
||||
}
|
||||
|
||||
float refreshRate = 30;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
try {
|
||||
@@ -106,10 +120,14 @@ public class ScreenCaptureService extends Service {
|
||||
if (fps > 60) fps = 60; // 限制最大 60fps
|
||||
if (fps <= 0) fps = 30;
|
||||
|
||||
Log.i(TAG, "Screen resolution: " + width + "x" + height + " @ " + fps + "fps");
|
||||
inputHandler = new InputCommandHandler(width, height);
|
||||
Log.i(TAG, "Real resolution: " + realWidth + "x" + realHeight);
|
||||
Log.i(TAG, "Capture resolution: " + captureWidth + "x" + captureHeight + " @ " + fps + "fps");
|
||||
|
||||
startScreenCapture(resultCode, resultData, serverUrl, deviceId, width, height, fps);
|
||||
// 输入处理器必须使用真实分辨率进行坐标映射
|
||||
inputHandler = new InputCommandHandler(realWidth, realHeight);
|
||||
|
||||
// 开启屏幕捕获,使用计算出的 capture 分辨率
|
||||
startScreenCapture(resultCode, resultData, serverUrl, deviceId, captureWidth, captureHeight, fps);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
|
||||
@@ -208,6 +208,11 @@ public class WebRtcClient {
|
||||
peerConnection.createAnswer(new AppSdpObserver(new AppSdpObserver.SdpEventListener() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription sdp) {
|
||||
// 【关键修复】在 setLocalDescription 之前修改 SDP
|
||||
// 这样本地和远端都会统一使用优化后的 H264 配置
|
||||
String modifiedSdpDescription = optimizeSdp(sdp.description);
|
||||
SessionDescription modifiedSdp = new SessionDescription(sdp.type, modifiedSdpDescription);
|
||||
|
||||
peerConnection.setLocalDescription(new AppSdpObserver(new AppSdpObserver.SdpEventListener() {
|
||||
@Override
|
||||
public void onCreateSuccess(SessionDescription s) {
|
||||
@@ -215,19 +220,20 @@ public class WebRtcClient {
|
||||
|
||||
@Override
|
||||
public void onSetSuccess() {
|
||||
// 尝试通过 SDP Munging 提高码率和优化编码
|
||||
String modifiedSdp = optimizeSdp(sdp.description);
|
||||
sendAnswer(modifiedSdp, controllerId);
|
||||
Log.e(TAG, "onSetSuccess: modifiedSdp = " + modifiedSdpDescription);
|
||||
sendAnswer(modifiedSdpDescription, controllerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
Log.e(TAG, "setLocalDescription onCreateFailure: " + error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
Log.e(TAG, "setLocalDescription onSetFailure: " + error);
|
||||
}
|
||||
}), sdp);
|
||||
}), modifiedSdp);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -236,6 +242,7 @@ public class WebRtcClient {
|
||||
|
||||
@Override
|
||||
public void onCreateFailure(String error) {
|
||||
Log.e(TAG, "createAnswer onCreateFailure: " + error);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -250,6 +257,7 @@ public class WebRtcClient {
|
||||
|
||||
@Override
|
||||
public void onSetFailure(String error) {
|
||||
Log.e(TAG, "setRemoteDescription onSetFailure: " + error);
|
||||
}
|
||||
}), remoteSdp);
|
||||
}
|
||||
@@ -318,38 +326,68 @@ public class WebRtcClient {
|
||||
}
|
||||
|
||||
private String optimizeSdp(String sdp) {
|
||||
String[] lines = sdp.split("\r\n");
|
||||
// 使用 \n 分割以提高兼容性,并处理可能的 \r
|
||||
String[] lines = sdp.split("\n");
|
||||
StringBuilder newSdp = new StringBuilder();
|
||||
|
||||
// 优先使用 H264
|
||||
String h264Payload = null;
|
||||
|
||||
// 首先找到 H264 的 payload type
|
||||
List<String> h264Payloads = new ArrayList<>();
|
||||
// 首先找到所有的 H264 payload type (可能包含多个 profile)
|
||||
for (String line : lines) {
|
||||
if (line.startsWith("a=rtpmap:") && line.contains("H264/90000")) {
|
||||
h264Payload = line.split(":")[1].split(" ")[0];
|
||||
break;
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.startsWith("a=rtpmap:") && trimmedLine.contains("H264/90000")) {
|
||||
String payload = trimmedLine.split(":")[1].split(" ")[0];
|
||||
h264Payloads.add(payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (h264Payloads.isEmpty()) {
|
||||
return sdp;
|
||||
}
|
||||
|
||||
for (String line : lines) {
|
||||
if (line.startsWith("m=video") && h264Payload != null) {
|
||||
// 将 H264 payload 移到最前面以优先使用硬件加速编码
|
||||
String[] parts = line.split(" ");
|
||||
StringBuilder mLine = new StringBuilder(parts[0] + " " + parts[1] + " " + parts[2]);
|
||||
mLine.append(" ").append(h264Payload);
|
||||
for (int i = 3; i < parts.length; i++) {
|
||||
if (!parts[i].equals(h264Payload)) {
|
||||
mLine.append(" ").append(parts[i]);
|
||||
String trimmedLine = line.trim();
|
||||
if (trimmedLine.isEmpty()) continue;
|
||||
|
||||
if (trimmedLine.startsWith("m=video")) {
|
||||
// 将所有 H264 payload 移到最前面以优先使用
|
||||
String[] parts = trimmedLine.split(" ");
|
||||
if (parts.length > 3) {
|
||||
StringBuilder mLine = new StringBuilder(parts[0] + " " + parts[1] + " " + parts[2]);
|
||||
// 先添加所有的 H264 payload
|
||||
for (String h264 : h264Payloads) {
|
||||
mLine.append(" ").append(h264);
|
||||
}
|
||||
// 再添加其余的 payload
|
||||
for (int i = 3; i < parts.length; i++) {
|
||||
if (!h264Payloads.contains(parts[i])) {
|
||||
mLine.append(" ").append(parts[i]);
|
||||
}
|
||||
}
|
||||
newSdp.append(mLine).append("\r\n");
|
||||
} else {
|
||||
newSdp.append(trimmedLine).append("\r\n");
|
||||
}
|
||||
} else if (trimmedLine.startsWith("a=fmtp:")) {
|
||||
boolean isH264 = false;
|
||||
for (String h264 : h264Payloads) {
|
||||
if (trimmedLine.startsWith("a=fmtp:" + h264)) {
|
||||
isH264 = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
newSdp.append(mLine).append("\r\n");
|
||||
} else if (h264Payload != null && line.startsWith("a=fmtp:" + h264Payload)) {
|
||||
// 为 H264 添加起步码率和最大码率限制 (单位: kbps)
|
||||
// 提高起步码率能显著改善初始画质
|
||||
newSdp.append(line).append(";x-google-start-bitrate=5000;x-google-max-bitrate=50000;x-google-min-bitrate=2000\r\n");
|
||||
if (isH264) {
|
||||
// 为 H264 注入码率优化参数
|
||||
String bonus = ";x-google-start-bitrate=5000;x-google-max-bitrate=50000;x-google-min-bitrate=2000";
|
||||
if (!trimmedLine.contains("x-google-start-bitrate")) {
|
||||
newSdp.append(trimmedLine).append(bonus).append("\r\n");
|
||||
} else {
|
||||
newSdp.append(trimmedLine).append("\r\n");
|
||||
}
|
||||
} else {
|
||||
newSdp.append(trimmedLine).append("\r\n");
|
||||
}
|
||||
} else {
|
||||
newSdp.append(line).append("\r\n");
|
||||
newSdp.append(trimmedLine).append("\r\n");
|
||||
}
|
||||
}
|
||||
return newSdp.toString();
|
||||
|
||||
Reference in New Issue
Block a user