mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-08-03 14:18:45 +00:00
scrcpy-server: update to 1.24
Signed-off-by: Ujhhgtg <feyxiexzf@gmail.com>
This commit is contained in:
parent
b61818f23e
commit
c0744287c8
15 changed files with 249 additions and 122 deletions
|
@ -6,8 +6,8 @@ android {
|
||||||
applicationId "com.genymobile.scrcpy"
|
applicationId "com.genymobile.scrcpy"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 31
|
targetSdkVersion 31
|
||||||
versionCode 12100
|
versionCode 12400
|
||||||
versionName "1.21"
|
versionName "1.24"
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
7
server/build_without_gradle.sh
Normal file → Executable file
7
server/build_without_gradle.sh
Normal file → Executable file
|
@ -12,10 +12,9 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
SCRCPY_DEBUG=false
|
SCRCPY_DEBUG=false
|
||||||
SCRCPY_VERSION_NAME=1.21
|
SCRCPY_VERSION_NAME=1.24
|
||||||
|
|
||||||
PLATFORM_VERSION=31
|
PLATFORM=${ANDROID_PLATFORM:-31}
|
||||||
PLATFORM=${ANDROID_PLATFORM:-$PLATFORM_VERSION}
|
|
||||||
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
|
BUILD_TOOLS=${ANDROID_BUILD_TOOLS:-31.0.0}
|
||||||
|
|
||||||
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
BUILD_DIR="$(realpath ${BUILD_DIR:-build_manual})"
|
||||||
|
@ -57,7 +56,7 @@ javac -bootclasspath "$ANDROID_JAR" -cp "$CLASSES_DIR" -d "$CLASSES_DIR" \
|
||||||
echo "Dexing..."
|
echo "Dexing..."
|
||||||
cd "$CLASSES_DIR"
|
cd "$CLASSES_DIR"
|
||||||
|
|
||||||
if [[ $PLATFORM_VERSION -lt 31 ]]
|
if [[ $PLATFORM -lt 31 ]]
|
||||||
then
|
then
|
||||||
# use dx
|
# use dx
|
||||||
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
"$ANDROID_HOME/build-tools/$BUILD_TOOLS/dx" --dex \
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
apply plugin: 'checkstyle'
|
|
||||||
check.dependsOn 'checkstyle'
|
|
||||||
|
|
||||||
checkstyle {
|
|
||||||
toolVersion = '6.19'
|
|
||||||
}
|
|
||||||
|
|
||||||
task checkstyle(type: Checkstyle) {
|
|
||||||
description = "Check Java style with Checkstyle"
|
|
||||||
configFile = rootProject.file("config/checkstyle/checkstyle.xml")
|
|
||||||
source = javaSources()
|
|
||||||
classpath = files()
|
|
||||||
ignoreFailures = true
|
|
||||||
}
|
|
||||||
|
|
||||||
def javaSources() {
|
|
||||||
def files = []
|
|
||||||
android.sourceSets.each { sourceSet ->
|
|
||||||
sourceSet.java.each { javaSource ->
|
|
||||||
javaSource.getSrcDirs().each {
|
|
||||||
if (it.exists()) {
|
|
||||||
files.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files
|
|
||||||
}
|
|
|
@ -71,12 +71,13 @@ public final class ControlMessage {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll) {
|
public static ControlMessage createInjectScrollEvent(Position position, int hScroll, int vScroll, int buttons) {
|
||||||
ControlMessage msg = new ControlMessage();
|
ControlMessage msg = new ControlMessage();
|
||||||
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
msg.type = TYPE_INJECT_SCROLL_EVENT;
|
||||||
msg.position = position;
|
msg.position = position;
|
||||||
msg.hScroll = hScroll;
|
msg.hScroll = hScroll;
|
||||||
msg.vScroll = vScroll;
|
msg.vScroll = vScroll;
|
||||||
|
msg.buttons = buttons;
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ public class ControlMessageReader {
|
||||||
|
|
||||||
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
static final int INJECT_KEYCODE_PAYLOAD_LENGTH = 13;
|
||||||
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
static final int INJECT_TOUCH_EVENT_PAYLOAD_LENGTH = 27;
|
||||||
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
|
static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 24;
|
||||||
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
static final int BACK_OR_SCREEN_ON_LENGTH = 1;
|
||||||
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
|
||||||
static final int GET_CLIPBOARD_LENGTH = 1;
|
static final int GET_CLIPBOARD_LENGTH = 1;
|
||||||
|
@ -154,7 +154,8 @@ public class ControlMessageReader {
|
||||||
Position position = readPosition(buffer);
|
Position position = readPosition(buffer);
|
||||||
int hScroll = buffer.getInt();
|
int hScroll = buffer.getInt();
|
||||||
int vScroll = buffer.getInt();
|
int vScroll = buffer.getInt();
|
||||||
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll);
|
int buttons = buffer.getInt();
|
||||||
|
return ControlMessage.createInjectScrollEvent(position, hScroll, vScroll, buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlMessage parseBackOrScreenOnEvent() {
|
private ControlMessage parseBackOrScreenOnEvent() {
|
||||||
|
|
|
@ -22,6 +22,7 @@ public class Controller {
|
||||||
private final DesktopConnection connection;
|
private final DesktopConnection connection;
|
||||||
private final DeviceMessageSender sender;
|
private final DeviceMessageSender sender;
|
||||||
private final boolean clipboardAutosync;
|
private final boolean clipboardAutosync;
|
||||||
|
private final boolean powerOn;
|
||||||
|
|
||||||
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
private final KeyCharacterMap charMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
|
||||||
|
|
||||||
|
@ -32,10 +33,11 @@ public class Controller {
|
||||||
|
|
||||||
private boolean keepPowerModeOff;
|
private boolean keepPowerModeOff;
|
||||||
|
|
||||||
public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync) {
|
public Controller(Device device, DesktopConnection connection, boolean clipboardAutosync, boolean powerOn) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.clipboardAutosync = clipboardAutosync;
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
|
this.powerOn = powerOn;
|
||||||
initPointers();
|
initPointers();
|
||||||
sender = new DeviceMessageSender(connection);
|
sender = new DeviceMessageSender(connection);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +58,7 @@ public class Controller {
|
||||||
|
|
||||||
public void control() throws IOException {
|
public void control() throws IOException {
|
||||||
// on start, power on the device
|
// on start, power on the device
|
||||||
if (!Device.isScreenOn()) {
|
if (powerOn && !Device.isScreenOn()) {
|
||||||
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, Device.INJECT_MODE_ASYNC);
|
||||||
|
|
||||||
// dirty hack
|
// dirty hack
|
||||||
|
@ -98,7 +100,7 @@ public class Controller {
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
case ControlMessage.TYPE_INJECT_SCROLL_EVENT:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll());
|
injectScroll(msg.getPosition(), msg.getHScroll(), msg.getVScroll(), msg.getButtons());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
case ControlMessage.TYPE_BACK_OR_SCREEN_ON:
|
||||||
|
@ -221,7 +223,7 @@ public class Controller {
|
||||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean injectScroll(Position position, int hScroll, int vScroll) {
|
private boolean injectScroll(Position position, int hScroll, int vScroll, int buttons) {
|
||||||
long now = SystemClock.uptimeMillis();
|
long now = SystemClock.uptimeMillis();
|
||||||
Point point = device.getPhysicalPoint(position);
|
Point point = device.getPhysicalPoint(position);
|
||||||
if (point == null) {
|
if (point == null) {
|
||||||
|
@ -239,7 +241,7 @@ public class Controller {
|
||||||
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll);
|
||||||
|
|
||||||
MotionEvent event = MotionEvent
|
MotionEvent event = MotionEvent
|
||||||
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
.obtain(lastTouchDown, now, MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, buttons, 1f, 1f, DEFAULT_DEVICE_ID, 0,
|
||||||
InputDevice.SOURCE_MOUSE, 0);
|
InputDevice.SOURCE_MOUSE, 0);
|
||||||
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
return device.injectEvent(event, Device.INJECT_MODE_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,13 @@ public final class DesktopConnection implements Closeable {
|
||||||
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
|
private DesktopConnection(LocalSocket videoSocket, LocalSocket controlSocket) throws IOException {
|
||||||
this.videoSocket = videoSocket;
|
this.videoSocket = videoSocket;
|
||||||
this.controlSocket = controlSocket;
|
this.controlSocket = controlSocket;
|
||||||
controlInputStream = controlSocket.getInputStream();
|
if (controlSocket != null) {
|
||||||
controlOutputStream = controlSocket.getOutputStream();
|
controlInputStream = controlSocket.getInputStream();
|
||||||
|
controlOutputStream = controlSocket.getOutputStream();
|
||||||
|
} else {
|
||||||
|
controlInputStream = null;
|
||||||
|
controlOutputStream = null;
|
||||||
|
}
|
||||||
videoFd = videoSocket.getFileDescriptor();
|
videoFd = videoSocket.getFileDescriptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,50 +46,55 @@ public final class DesktopConnection implements Closeable {
|
||||||
return localSocket;
|
return localSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DesktopConnection open(Device device, boolean tunnelForward) throws IOException {
|
public static DesktopConnection open(boolean tunnelForward, boolean control, boolean sendDummyByte) throws IOException {
|
||||||
LocalSocket videoSocket;
|
LocalSocket videoSocket;
|
||||||
LocalSocket controlSocket;
|
LocalSocket controlSocket = null;
|
||||||
if (tunnelForward) {
|
if (tunnelForward) {
|
||||||
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
|
LocalServerSocket localServerSocket = new LocalServerSocket(SOCKET_NAME);
|
||||||
try {
|
try {
|
||||||
videoSocket = localServerSocket.accept();
|
videoSocket = localServerSocket.accept();
|
||||||
// send one byte so the client may read() to detect a connection error
|
if (sendDummyByte) {
|
||||||
videoSocket.getOutputStream().write(0);
|
// send one byte so the client may read() to detect a connection error
|
||||||
try {
|
videoSocket.getOutputStream().write(0);
|
||||||
controlSocket = localServerSocket.accept();
|
}
|
||||||
} catch (IOException | RuntimeException e) {
|
if (control) {
|
||||||
videoSocket.close();
|
try {
|
||||||
throw e;
|
controlSocket = localServerSocket.accept();
|
||||||
|
} catch (IOException | RuntimeException e) {
|
||||||
|
videoSocket.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
localServerSocket.close();
|
localServerSocket.close();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
videoSocket = connect(SOCKET_NAME);
|
videoSocket = connect(SOCKET_NAME);
|
||||||
try {
|
if (control) {
|
||||||
controlSocket = connect(SOCKET_NAME);
|
try {
|
||||||
} catch (IOException | RuntimeException e) {
|
controlSocket = connect(SOCKET_NAME);
|
||||||
videoSocket.close();
|
} catch (IOException | RuntimeException e) {
|
||||||
throw e;
|
videoSocket.close();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DesktopConnection connection = new DesktopConnection(videoSocket, controlSocket);
|
return new DesktopConnection(videoSocket, controlSocket);
|
||||||
Size videoSize = device.getScreenInfo().getVideoSize();
|
|
||||||
connection.send(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
|
||||||
return connection;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
videoSocket.shutdownInput();
|
videoSocket.shutdownInput();
|
||||||
videoSocket.shutdownOutput();
|
videoSocket.shutdownOutput();
|
||||||
videoSocket.close();
|
videoSocket.close();
|
||||||
controlSocket.shutdownInput();
|
if (controlSocket != null) {
|
||||||
controlSocket.shutdownOutput();
|
controlSocket.shutdownInput();
|
||||||
controlSocket.close();
|
controlSocket.shutdownOutput();
|
||||||
|
controlSocket.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send(String deviceName, int width, int height) throws IOException {
|
public void sendDeviceMeta(String deviceName, int width, int height) throws IOException {
|
||||||
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
|
byte[] buffer = new byte[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||||
|
|
||||||
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
byte[] deviceNameBytes = deviceName.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
|
@ -42,6 +42,11 @@ public final class Device {
|
||||||
void onClipboardTextChanged(String text);
|
void onClipboardTextChanged(String text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Size deviceSize;
|
||||||
|
private final Rect crop;
|
||||||
|
private int maxSize;
|
||||||
|
private final int lockVideoOrientation;
|
||||||
|
|
||||||
private ScreenInfo screenInfo;
|
private ScreenInfo screenInfo;
|
||||||
private RotationListener rotationListener;
|
private RotationListener rotationListener;
|
||||||
private ClipboardListener clipboardListener;
|
private ClipboardListener clipboardListener;
|
||||||
|
@ -69,7 +74,12 @@ public final class Device {
|
||||||
|
|
||||||
int displayInfoFlags = displayInfo.getFlags();
|
int displayInfoFlags = displayInfo.getFlags();
|
||||||
|
|
||||||
screenInfo = ScreenInfo.computeScreenInfo(displayInfo, options.getCrop(), options.getMaxSize(), options.getLockVideoOrientation());
|
deviceSize = displayInfo.getSize();
|
||||||
|
crop = options.getCrop();
|
||||||
|
maxSize = options.getMaxSize();
|
||||||
|
lockVideoOrientation = options.getLockVideoOrientation();
|
||||||
|
|
||||||
|
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
|
||||||
layerStack = displayInfo.getLayerStack();
|
layerStack = displayInfo.getLayerStack();
|
||||||
|
|
||||||
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
SERVICE_MANAGER.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
|
||||||
|
@ -123,6 +133,11 @@ public final class Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void setMaxSize(int newMaxSize) {
|
||||||
|
maxSize = newMaxSize;
|
||||||
|
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized ScreenInfo getScreenInfo() {
|
public synchronized ScreenInfo getScreenInfo() {
|
||||||
return screenInfo;
|
return screenInfo;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ public class Options {
|
||||||
private int lockVideoOrientation = -1;
|
private int lockVideoOrientation = -1;
|
||||||
private boolean tunnelForward;
|
private boolean tunnelForward;
|
||||||
private Rect crop;
|
private Rect crop;
|
||||||
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
|
||||||
private boolean control = true;
|
private boolean control = true;
|
||||||
private int displayId;
|
private int displayId;
|
||||||
private boolean showTouches;
|
private boolean showTouches;
|
||||||
|
@ -21,6 +20,14 @@ public class Options {
|
||||||
private String encoderName;
|
private String encoderName;
|
||||||
private boolean powerOffScreenOnClose;
|
private boolean powerOffScreenOnClose;
|
||||||
private boolean clipboardAutosync = true;
|
private boolean clipboardAutosync = true;
|
||||||
|
private boolean downsizeOnError = true;
|
||||||
|
private boolean cleanup = true;
|
||||||
|
private boolean powerOn = true;
|
||||||
|
|
||||||
|
// Options not used by the scrcpy client, but useful to use scrcpy-server directly
|
||||||
|
private boolean sendDeviceMeta = true; // send device name and size
|
||||||
|
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
||||||
|
private boolean sendDummyByte = true; // write a byte on start to detect connection issues
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
public Ln.Level getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
|
@ -78,14 +85,6 @@ public class Options {
|
||||||
this.crop = crop;
|
this.crop = crop;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean getSendFrameMeta() {
|
|
||||||
return sendFrameMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSendFrameMeta(boolean sendFrameMeta) {
|
|
||||||
this.sendFrameMeta = sendFrameMeta;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean getControl() {
|
public boolean getControl() {
|
||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
@ -149,4 +148,52 @@ public class Options {
|
||||||
public void setClipboardAutosync(boolean clipboardAutosync) {
|
public void setClipboardAutosync(boolean clipboardAutosync) {
|
||||||
this.clipboardAutosync = clipboardAutosync;
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getDownsizeOnError() {
|
||||||
|
return downsizeOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDownsizeOnError(boolean downsizeOnError) {
|
||||||
|
this.downsizeOnError = downsizeOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getCleanup() {
|
||||||
|
return cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCleanup(boolean cleanup) {
|
||||||
|
this.cleanup = cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getPowerOn() {
|
||||||
|
return powerOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPowerOn(boolean powerOn) {
|
||||||
|
this.powerOn = powerOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSendDeviceMeta() {
|
||||||
|
return sendDeviceMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendDeviceMeta(boolean sendDeviceMeta) {
|
||||||
|
this.sendDeviceMeta = sendDeviceMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSendFrameMeta() {
|
||||||
|
return sendFrameMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendFrameMeta(boolean sendFrameMeta) {
|
||||||
|
this.sendFrameMeta = sendFrameMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getSendDummyByte() {
|
||||||
|
return sendDummyByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSendDummyByte(boolean sendDummyByte) {
|
||||||
|
this.sendDummyByte = sendDummyByte;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,24 +25,33 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
private static final int REPEAT_FRAME_DELAY_US = 100_000; // repeat after 100ms
|
||||||
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
|
private static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
|
||||||
|
|
||||||
private static final int NO_PTS = -1;
|
// Keep the values in descending order
|
||||||
|
private static final int[] MAX_SIZE_FALLBACK = {2560, 1920, 1600, 1280, 1024, 800};
|
||||||
|
|
||||||
|
private static final long PACKET_FLAG_CONFIG = 1L << 63;
|
||||||
|
private static final long PACKET_FLAG_KEY_FRAME = 1L << 62;
|
||||||
|
|
||||||
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
private final AtomicBoolean rotationChanged = new AtomicBoolean();
|
||||||
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
private final ByteBuffer headerBuffer = ByteBuffer.allocate(12);
|
||||||
|
|
||||||
private String encoderName;
|
private final String encoderName;
|
||||||
private List<CodecOption> codecOptions;
|
private final List<CodecOption> codecOptions;
|
||||||
private int bitRate;
|
private final int bitRate;
|
||||||
private int maxFps;
|
private final int maxFps;
|
||||||
private boolean sendFrameMeta;
|
private final boolean sendFrameMeta;
|
||||||
|
private final boolean downsizeOnError;
|
||||||
private long ptsOrigin;
|
private long ptsOrigin;
|
||||||
|
|
||||||
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName) {
|
private boolean firstFrameSent;
|
||||||
|
|
||||||
|
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, List<CodecOption> codecOptions, String encoderName,
|
||||||
|
boolean downsizeOnError) {
|
||||||
this.sendFrameMeta = sendFrameMeta;
|
this.sendFrameMeta = sendFrameMeta;
|
||||||
this.bitRate = bitRate;
|
this.bitRate = bitRate;
|
||||||
this.maxFps = maxFps;
|
this.maxFps = maxFps;
|
||||||
this.codecOptions = codecOptions;
|
this.codecOptions = codecOptions;
|
||||||
this.encoderName = encoderName;
|
this.encoderName = encoderName;
|
||||||
|
this.downsizeOnError = downsizeOnError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -81,20 +90,41 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
|
||||||
int videoRotation = screenInfo.getVideoRotation();
|
int videoRotation = screenInfo.getVideoRotation();
|
||||||
int layerStack = device.getLayerStack();
|
int layerStack = device.getLayerStack();
|
||||||
|
|
||||||
setSize(format, videoRect.width(), videoRect.height());
|
setSize(format, videoRect.width(), videoRect.height());
|
||||||
configure(codec, format);
|
|
||||||
Surface surface = codec.createInputSurface();
|
Surface surface = null;
|
||||||
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
|
||||||
codec.start();
|
|
||||||
try {
|
try {
|
||||||
|
configure(codec, format);
|
||||||
|
surface = codec.createInputSurface();
|
||||||
|
setDisplaySurface(display, surface, videoRotation, contentRect, unlockedVideoRect, layerStack);
|
||||||
|
codec.start();
|
||||||
|
|
||||||
alive = encode(codec, fd);
|
alive = encode(codec, fd);
|
||||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||||
codec.stop();
|
codec.stop();
|
||||||
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||||
|
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
if (!downsizeOnError || firstFrameSent) {
|
||||||
|
// Fail immediately
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newMaxSize = chooseMaxSizeFallback(screenInfo.getVideoSize());
|
||||||
|
if (newMaxSize == 0) {
|
||||||
|
// Definitively fail
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retry with a smaller device size
|
||||||
|
Ln.i("Retrying with -m" + newMaxSize + "...");
|
||||||
|
device.setMaxSize(newMaxSize);
|
||||||
|
alive = true;
|
||||||
} finally {
|
} finally {
|
||||||
destroyDisplay(display);
|
destroyDisplay(display);
|
||||||
codec.release();
|
codec.release();
|
||||||
surface.release();
|
if (surface != null) {
|
||||||
|
surface.release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} while (alive);
|
} while (alive);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -102,6 +132,18 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int chooseMaxSizeFallback(Size failedSize) {
|
||||||
|
int currentMaxSize = Math.max(failedSize.getWidth(), failedSize.getHeight());
|
||||||
|
for (int value : MAX_SIZE_FALLBACK) {
|
||||||
|
if (value < currentMaxSize) {
|
||||||
|
// We found a smaller value to reduce the video size
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No fallback, fail definitively
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
|
private boolean encode(MediaCodec codec, FileDescriptor fd) throws IOException {
|
||||||
boolean eof = false;
|
boolean eof = false;
|
||||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||||
|
@ -122,6 +164,10 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
IO.writeFully(fd, codecBuffer);
|
IO.writeFully(fd, codecBuffer);
|
||||||
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
|
||||||
|
// If this is not a config packet, then it contains a frame
|
||||||
|
firstFrameSent = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (outputBufferId >= 0) {
|
if (outputBufferId >= 0) {
|
||||||
|
@ -138,12 +184,15 @@ public class ScreenEncoder implements Device.RotationListener {
|
||||||
|
|
||||||
long pts;
|
long pts;
|
||||||
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
|
||||||
pts = NO_PTS; // non-media data packet
|
pts = PACKET_FLAG_CONFIG; // non-media data packet
|
||||||
} else {
|
} else {
|
||||||
if (ptsOrigin == 0) {
|
if (ptsOrigin == 0) {
|
||||||
ptsOrigin = bufferInfo.presentationTimeUs;
|
ptsOrigin = bufferInfo.presentationTimeUs;
|
||||||
}
|
}
|
||||||
pts = bufferInfo.presentationTimeUs - ptsOrigin;
|
pts = bufferInfo.presentationTimeUs - ptsOrigin;
|
||||||
|
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
|
||||||
|
pts |= PACKET_FLAG_KEY_FRAME;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
headerBuffer.putLong(pts);
|
headerBuffer.putLong(pts);
|
||||||
|
|
|
@ -80,15 +80,12 @@ public final class ScreenInfo {
|
||||||
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
|
return new ScreenInfo(newContentRect, newUnlockedVideoSize, newDeviceRotation, lockedVideoOrientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ScreenInfo computeScreenInfo(DisplayInfo displayInfo, Rect crop, int maxSize, int lockedVideoOrientation) {
|
public static ScreenInfo computeScreenInfo(int rotation, Size deviceSize, Rect crop, int maxSize, int lockedVideoOrientation) {
|
||||||
int rotation = displayInfo.getRotation();
|
|
||||||
|
|
||||||
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
if (lockedVideoOrientation == Device.LOCK_VIDEO_ORIENTATION_INITIAL) {
|
||||||
// The user requested to lock the video orientation to the current orientation
|
// The user requested to lock the video orientation to the current orientation
|
||||||
lockedVideoOrientation = rotation;
|
lockedVideoOrientation = rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
Size deviceSize = displayInfo.getSize();
|
|
||||||
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
Rect contentRect = new Rect(0, 0, deviceSize.getWidth(), deviceSize.getHeight());
|
||||||
if (crop != null) {
|
if (crop != null) {
|
||||||
if (rotation % 2 != 0) { // 180s preserve dimensions
|
if (rotation % 2 != 0) { // 180s preserve dimensions
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.genymobile.scrcpy;
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.media.MediaCodec;
|
|
||||||
import android.media.MediaCodecInfo;
|
import android.media.MediaCodecInfo;
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -12,7 +11,6 @@ import java.util.Locale;
|
||||||
|
|
||||||
public final class Server {
|
public final class Server {
|
||||||
|
|
||||||
|
|
||||||
private Server() {
|
private Server() {
|
||||||
// not instantiable
|
// not instantiable
|
||||||
}
|
}
|
||||||
|
@ -20,6 +18,7 @@ public final class Server {
|
||||||
private static void initAndCleanUp(Options options) {
|
private static void initAndCleanUp(Options options) {
|
||||||
boolean mustDisableShowTouchesOnCleanUp = false;
|
boolean mustDisableShowTouchesOnCleanUp = false;
|
||||||
int restoreStayOn = -1;
|
int restoreStayOn = -1;
|
||||||
|
boolean restoreNormalPowerMode = options.getControl(); // only restore power mode if control is enabled
|
||||||
if (options.getShowTouches() || options.getStayAwake()) {
|
if (options.getShowTouches() || options.getStayAwake()) {
|
||||||
Settings settings = Device.getSettings();
|
Settings settings = Device.getSettings();
|
||||||
if (options.getShowTouches()) {
|
if (options.getShowTouches()) {
|
||||||
|
@ -51,10 +50,13 @@ public final class Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (options.getCleanup()) {
|
||||||
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, true, options.getPowerOffScreenOnClose());
|
try {
|
||||||
} catch (IOException e) {
|
CleanUp.configure(options.getDisplayId(), restoreStayOn, mustDisableShowTouchesOnCleanUp, restoreNormalPowerMode,
|
||||||
Ln.e("Could not configure cleanup", e);
|
options.getPowerOffScreenOnClose());
|
||||||
|
} catch (IOException e) {
|
||||||
|
Ln.e("Could not configure cleanup", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,15 +68,21 @@ public final class Server {
|
||||||
Thread initThread = startInitThread(options);
|
Thread initThread = startInitThread(options);
|
||||||
|
|
||||||
boolean tunnelForward = options.isTunnelForward();
|
boolean tunnelForward = options.isTunnelForward();
|
||||||
|
boolean control = options.getControl();
|
||||||
|
boolean sendDummyByte = options.getSendDummyByte();
|
||||||
|
|
||||||
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
|
try (DesktopConnection connection = DesktopConnection.open(tunnelForward, control, sendDummyByte)) {
|
||||||
|
if (options.getSendDeviceMeta()) {
|
||||||
|
Size videoSize = device.getScreenInfo().getVideoSize();
|
||||||
|
connection.sendDeviceMeta(Device.getDeviceName(), videoSize.getWidth(), videoSize.getHeight());
|
||||||
|
}
|
||||||
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
|
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), codecOptions,
|
||||||
options.getEncoderName());
|
options.getEncoderName(), options.getDownsizeOnError());
|
||||||
|
|
||||||
Thread controllerThread = null;
|
Thread controllerThread = null;
|
||||||
Thread deviceMessageSenderThread = null;
|
Thread deviceMessageSenderThread = null;
|
||||||
if (options.getControl()) {
|
if (control) {
|
||||||
final Controller controller = new Controller(device, connection, options.getClipboardAutosync());
|
final Controller controller = new Controller(device, connection, options.getClipboardAutosync(), options.getPowerOn());
|
||||||
|
|
||||||
// asynchronous
|
// asynchronous
|
||||||
controllerThread = startController(controller);
|
controllerThread = startController(controller);
|
||||||
|
@ -199,10 +207,6 @@ public final class Server {
|
||||||
Rect crop = parseCrop(value);
|
Rect crop = parseCrop(value);
|
||||||
options.setCrop(crop);
|
options.setCrop(crop);
|
||||||
break;
|
break;
|
||||||
case "send_frame_meta":
|
|
||||||
boolean sendFrameMeta = Boolean.parseBoolean(value);
|
|
||||||
options.setSendFrameMeta(sendFrameMeta);
|
|
||||||
break;
|
|
||||||
case "control":
|
case "control":
|
||||||
boolean control = Boolean.parseBoolean(value);
|
boolean control = Boolean.parseBoolean(value);
|
||||||
options.setControl(control);
|
options.setControl(control);
|
||||||
|
@ -236,6 +240,38 @@ public final class Server {
|
||||||
boolean clipboardAutosync = Boolean.parseBoolean(value);
|
boolean clipboardAutosync = Boolean.parseBoolean(value);
|
||||||
options.setClipboardAutosync(clipboardAutosync);
|
options.setClipboardAutosync(clipboardAutosync);
|
||||||
break;
|
break;
|
||||||
|
case "downsize_on_error":
|
||||||
|
boolean downsizeOnError = Boolean.parseBoolean(value);
|
||||||
|
options.setDownsizeOnError(downsizeOnError);
|
||||||
|
break;
|
||||||
|
case "cleanup":
|
||||||
|
boolean cleanup = Boolean.parseBoolean(value);
|
||||||
|
options.setCleanup(cleanup);
|
||||||
|
break;
|
||||||
|
case "power_on":
|
||||||
|
boolean powerOn = Boolean.parseBoolean(value);
|
||||||
|
options.setPowerOn(powerOn);
|
||||||
|
break;
|
||||||
|
case "send_device_meta":
|
||||||
|
boolean sendDeviceMeta = Boolean.parseBoolean(value);
|
||||||
|
options.setSendDeviceMeta(sendDeviceMeta);
|
||||||
|
break;
|
||||||
|
case "send_frame_meta":
|
||||||
|
boolean sendFrameMeta = Boolean.parseBoolean(value);
|
||||||
|
options.setSendFrameMeta(sendFrameMeta);
|
||||||
|
break;
|
||||||
|
case "send_dummy_byte":
|
||||||
|
boolean sendDummyByte = Boolean.parseBoolean(value);
|
||||||
|
options.setSendDummyByte(sendDummyByte);
|
||||||
|
break;
|
||||||
|
case "raw_video_stream":
|
||||||
|
boolean rawVideoStream = Boolean.parseBoolean(value);
|
||||||
|
if (rawVideoStream) {
|
||||||
|
options.setSendDeviceMeta(false);
|
||||||
|
options.setSendFrameMeta(false);
|
||||||
|
options.setSendDummyByte(false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown server option: " + key);
|
Ln.w("Unknown server option: " + key);
|
||||||
break;
|
break;
|
||||||
|
@ -262,16 +298,6 @@ public final class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void suggestFix(Throwable e) {
|
private static void suggestFix(Throwable e) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
if (e instanceof MediaCodec.CodecException) {
|
|
||||||
MediaCodec.CodecException mce = (MediaCodec.CodecException) e;
|
|
||||||
if (mce.getErrorCode() == 0xfffffc0e) {
|
|
||||||
Ln.e("The hardware encoder is not able to encode at the given definition.");
|
|
||||||
Ln.e("Try with a lower definition:");
|
|
||||||
Ln.e(" scrcpy -m 1024");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (e instanceof InvalidDisplayIdException) {
|
if (e instanceof InvalidDisplayIdException) {
|
||||||
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
InvalidDisplayIdException idie = (InvalidDisplayIdException) e;
|
||||||
int[] displayIds = idie.getAvailableDisplayIds();
|
int[] displayIds = idie.getAvailableDisplayIds();
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.Ln;
|
import com.genymobile.scrcpy.Ln;
|
||||||
|
|
||||||
import android.os.IInterface;
|
|
||||||
import android.view.InputEvent;
|
import android.view.InputEvent;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
@ -14,12 +13,12 @@ public final class InputManager {
|
||||||
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
|
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1;
|
||||||
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
|
public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2;
|
||||||
|
|
||||||
private final IInterface manager;
|
private final android.hardware.input.InputManager manager;
|
||||||
private Method injectInputEventMethod;
|
private Method injectInputEventMethod;
|
||||||
|
|
||||||
private static Method setDisplayIdMethod;
|
private static Method setDisplayIdMethod;
|
||||||
|
|
||||||
public InputManager(IInterface manager) {
|
public InputManager(android.hardware.input.InputManager manager) {
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.annotation.SuppressLint;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.IInterface;
|
import android.os.IInterface;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
@SuppressLint("PrivateApi,DiscouragedPrivateApi")
|
||||||
|
@ -56,7 +57,13 @@ public final class ServiceManager {
|
||||||
|
|
||||||
public InputManager getInputManager() {
|
public InputManager getInputManager() {
|
||||||
if (inputManager == null) {
|
if (inputManager == null) {
|
||||||
inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager"));
|
try {
|
||||||
|
Method getInstanceMethod = android.hardware.input.InputManager.class.getDeclaredMethod("getInstance");
|
||||||
|
android.hardware.input.InputManager im = (android.hardware.input.InputManager) getInstanceMethod.invoke(null);
|
||||||
|
inputManager = new InputManager(im);
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return inputManager;
|
return inputManager;
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,7 @@ public class ControlMessageReaderTest {
|
||||||
dos.writeShort(1920);
|
dos.writeShort(1920);
|
||||||
dos.writeInt(1);
|
dos.writeInt(1);
|
||||||
dos.writeInt(-1);
|
dos.writeInt(-1);
|
||||||
|
dos.writeInt(1);
|
||||||
|
|
||||||
byte[] packet = bos.toByteArray();
|
byte[] packet = bos.toByteArray();
|
||||||
|
|
||||||
|
@ -144,6 +145,7 @@ public class ControlMessageReaderTest {
|
||||||
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
Assert.assertEquals(1920, event.getPosition().getScreenSize().getHeight());
|
||||||
Assert.assertEquals(1, event.getHScroll());
|
Assert.assertEquals(1, event.getHScroll());
|
||||||
Assert.assertEquals(-1, event.getVScroll());
|
Assert.assertEquals(-1, event.getVScroll());
|
||||||
|
Assert.assertEquals(1, event.getButtons());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue