Compare commits

..

4 Commits

Author SHA1 Message Date
Romain Vimont
7bb91638ad Improve FAQ 2020-03-19 19:22:58 +01:00
Yogev Boaron Ben-Har
bc7508427b Add scoop instructions for Windows
PR #1202 <https://github.com/Genymobile/scrcpy/pull/1202>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2020-03-14 17:11:49 +01:00
Romain Vimont
24ade6ad77 Simplify Chocolatey documentation 2020-03-14 17:07:20 +01:00
Romain Vimont
c396758b4e Remove link to Windows 32 bits release
Binaries created with MinGW (even a simple Hello World) are detected as
malware by some anti-virus. For some reason, only the 32 bits version of
scrcpy is impacted.

Since users should use the 64 bits version by default anyway, remove the
link to the 32 bits version from the main page.

The 32 bits release is still available in the "releases" tab.

See <https://github.com/Genymobile/scrcpy/issues/1102>
2020-03-03 21:39:27 +01:00
16 changed files with 210 additions and 253 deletions

149
FAQ.md
View File

@@ -3,19 +3,102 @@
Here are the common reported problems and their status. Here are the common reported problems and their status.
### On Windows, my device is not detected ## `adb` issues
The most common is your device not being detected by `adb`, or is unauthorized. `scrcpy` execute `adb` commands to initialize the connection with the device. If
Check everything is ok by calling: `adb` fails, then scrcpy will not work.
adb devices In that case, it will print this error:
Windows may need some [drivers] to detect your device. > ERROR: "adb push" returned with value 1
This is typically not a bug in _scrcpy_, but a problem in your environment.
To find out the cause, execute:
```bash
adb devices
```
### `adb` not found
You need `adb` accessible from your `PATH`.
On Windows, the current directory is in your `PATH`, and `adb.exe` is included
in the release, so it should work out-of-the-box.
### Device unauthorized
Check [stackoverflow][device-unauthorized].
[device-unauthorized]: https://stackoverflow.com/questions/23081263/adb-android-device-unauthorized
### Device not detected
If your device is not detected, you may need some [drivers] (on Windows).
[drivers]: https://developer.android.com/studio/run/oem-usb.html [drivers]: https://developer.android.com/studio/run/oem-usb.html
### I can only mirror, I cannot interact with the device ### Several devices connected
If several devices are connected, you will encounter this error:
> adb: error: failed to get feature set: more than one device/emulator
the identifier of the device you want to mirror must be provided:
```bash
scrcpy -s 01234567890abcdef
```
Note that if your device is connected over TCP/IP, you'll get this message:
> adb: error: more than one device/emulator
> ERROR: "adb reverse" returned with value 1
> WARN: 'adb reverse' failed, fallback to 'adb forward'
This is expected (due to a bug on old Android versions, see [#5]), but in that
case, scrcpy fallbacks to a different method, which should work.
[#5]: https://github.com/Genymobile/scrcpy/issues/5
### Conflicts between adb versions
> adb server version (41) doesn't match this client (39); killing...
This error occurs when you use several `adb` versions simultaneously. You must
find the program using a different `adb` version, and use the same `adb` version
everywhere.
You could overwrite the `adb` binary in the other program, or ask _scrcpy_ to
use a specific `adb` binary, by setting the `ADB` environment variable:
```bash
set ADB=/path/to/your/adb
scrcpy
```
### Device disconnected
If _scrcpy_ stops itself with the warning "Device disconnected", then the
`adb` connection has been closed.
Try with another USB cable or plug it into another USB port. See [#281] and
[#283].
[#281]: https://github.com/Genymobile/scrcpy/issues/281
[#283]: https://github.com/Genymobile/scrcpy/issues/283
## Control issues
### Mouse and keyboard do not work
On some devices, you may need to enable an option to allow [simulating input]. On some devices, you may need to enable an option to allow [simulating input].
In developer options, enable: In developer options, enable:
@@ -29,18 +112,25 @@ In developer options, enable:
### Mouse clicks at wrong location ### Mouse clicks at wrong location
On MacOS, with HiDPI support and multiple screens, input location are wrongly On MacOS, with HiDPI support and multiple screens, input location are wrongly
scaled. See [issue 15]. scaled. See [#15].
[issue 15]: https://github.com/Genymobile/scrcpy/issues/15 [#15]: https://github.com/Genymobile/scrcpy/issues/15
A workaround is to build with HiDPI support disabled: Open _scrcpy_ directly on the monitor you use it.
```bash
meson x --buildtype release -Dhidpi_support=false
```
However, the video will be displayed at lower resolution. ### Special characters do not work
Injecting text input is [limited to ASCII characters][text-input]. A trick
allows to also inject some [accented characters][accented-characters], but
that's all. See [#37].
[text-input]: https://github.com/Genymobile/scrcpy/issues?q=is%3Aopen+is%3Aissue+label%3Aunicode
[accented-characters]: https://blog.rom1v.com/2018/03/introducing-scrcpy/#handle-accented-characters
[#37]: https://github.com/Genymobile/scrcpy/issues/37
## Client issues
### The quality is low on HiDPI display ### The quality is low on HiDPI display
@@ -51,6 +141,11 @@ On Windows, you may need to configure the [scaling behavior].
[scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723 [scaling behavior]: https://github.com/Genymobile/scrcpy/issues/40#issuecomment-424466723
If your computer definition is far smaller than your screen, then you'll get
poor quality. See [#40].
[#40]: https://github.com/Genymobile/scrcpy/issues/40
### KWin compositor crashes ### KWin compositor crashes
@@ -61,19 +156,29 @@ As a workaround, [disable "Block compositing"][kwin].
[kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613 [kwin]: https://github.com/Genymobile/scrcpy/issues/114#issuecomment-378778613
### I get an error "Could not open video stream" ## Crashes
### Exception
There may be many reasons. One common cause is that the hardware encoder of your There may be many reasons. One common cause is that the hardware encoder of your
device is not able to encode at the given definition: device is not able to encode at the given definition:
``` > ```
ERROR: Exception on thread Thread[main,5,main] > ERROR: Exception on thread Thread[main,5,main]
android.media.MediaCodec$CodecException: Error 0xfffffc0e > android.media.MediaCodec$CodecException: Error 0xfffffc0e
... > ...
Exit due to uncaughtException in main thread: > Exit due to uncaughtException in main thread:
ERROR: Could not open video stream > ERROR: Could not open video stream
INFO: Initial texture: 1080x2336 > INFO: Initial texture: 1080x2336
``` > ```
or
> ```
> ERROR: Exception on thread Thread[main,5,main]
> java.lang.IllegalStateException
> at android.media.MediaCodec.native_dequeueOutputBuffer(Native Method)
> ```
Just try with a lower definition: Just try with a lower definition:

View File

@@ -66,15 +66,12 @@ hard).
### Windows ### Windows
For Windows, for simplicity, prebuilt archives with all the dependencies For Windows, for simplicity, a prebuilt archive with all the dependencies
(including `adb`) are available: (including `adb`) is available:
- [`scrcpy-win32-v1.12.1.zip`][direct-win32]
_(SHA-256: 0f4b3b063536b50a2df05dc42c760f9cc0093a9a26dbdf02d8232c74dab43480)_
- [`scrcpy-win64-v1.12.1.zip`][direct-win64] - [`scrcpy-win64-v1.12.1.zip`][direct-win64]
_(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_ _(SHA-256: 57d34b6d16cfd9fe169bc37c4df58ebd256d05c1ea3febc63d9cb0a027ab47c9)_
[direct-win32]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win32-v1.12.1.zip
[direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip [direct-win64]: https://github.com/Genymobile/scrcpy/releases/download/v1.12.1/scrcpy-win64-v1.12.1.zip
It is also available in [Chocolatey]: It is also available in [Chocolatey]:
@@ -83,14 +80,18 @@ It is also available in [Chocolatey]:
```bash ```bash
choco install scrcpy choco install scrcpy
choco install adb # if you don't have it yet
``` ```
You need `adb`, accessible from your `PATH`. If you don't have it yet: And in [Scoop]:
```bash ```bash
choco install adb scoop install scrcpy
scoop install adb # if you don't have it yet
``` ```
[Scoop]: https://scoop.sh
You can also [build the app manually][BUILD]. You can also [build the app manually][BUILD].
@@ -177,21 +178,6 @@ scrcpy --crop 1224:1440:0:0 # 1224x1440 at offset (0,0)
If `--max-size` is also specified, resizing is applied after cropping. If `--max-size` is also specified, resizing is applied after cropping.
#### Lock video orientation
To lock the orientation of the mirroring:
```bash
scrcpy --lock-video-orientation 0 # natural orientation
scrcpy --lock-video-orientation 1 # 90° counterclockwise
scrcpy --lock-video-orientation 2 # 180°
scrcpy --lock-video-orientation 3 # 90° clockwise
```
This affects recording orientation.
### Recording ### Recording
It is possible to record the screen while mirroring: It is possible to record the screen while mirroring:

View File

@@ -106,12 +106,6 @@ conf.set('DEFAULT_LOCAL_PORT', '27183')
# overridden by option --max-size # overridden by option --max-size
conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited conf.set('DEFAULT_MAX_SIZE', '0') # 0: unlimited
# the default video orientation
# natural device orientation is 0 and each increment adds 90 degrees
# counterclockwise
# overridden by option --lock-video-orientation
conf.set('DEFAULT_LOCK_VIDEO_ORIENTATION', '-1') # -1: unlocked
# the default video bitrate, in bits/second # the default video bitrate, in bits/second
# overridden by option --bit-rate # overridden by option --bit-rate
conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps conf.set('DEFAULT_BIT_RATE', '8000000') # 8Mbps

View File

@@ -41,12 +41,6 @@ Start in fullscreen.
.B \-h, \-\-help .B \-h, \-\-help
Print this help. Print this help.
.TP
.BI "\-\-lock\-video\-orientation " value
Lock video orientation to \fIvalue\fR. Values are integers in the range [-1..3]. Natural device orientation is 0 and each increment adds 90 degrees counterclockwise.
Default is -1 (unlocked).
.TP .TP
.BI "\-\-max\-fps " value .BI "\-\-max\-fps " value
Limit the framerate of screen capture (only supported on devices with Android >= 10). Limit the framerate of screen capture (only supported on devices with Android >= 10).

View File

@@ -41,12 +41,6 @@ scrcpy_print_usage(const char *arg0) {
" -h, --help\n" " -h, --help\n"
" Print this help.\n" " Print this help.\n"
"\n" "\n"
" --lock-video-orientation value\n"
" Lock video orientation to value. Values are integers in the\n"
" range [-1..3]. Natural device orientation is 0 and each\n"
" increment adds 90 degrees counterclockwise.\n"
" Default is %d%s.\n"
"\n"
" --max-fps value\n" " --max-fps value\n"
" Limit the frame rate of screen capture (only supported on\n" " Limit the frame rate of screen capture (only supported on\n"
" devices with Android >= 10).\n" " devices with Android >= 10).\n"
@@ -199,7 +193,6 @@ scrcpy_print_usage(const char *arg0) {
arg0, arg0,
DEFAULT_BIT_RATE, DEFAULT_BIT_RATE,
DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)", DEFAULT_MAX_SIZE, DEFAULT_MAX_SIZE ? "" : " (unlimited)",
DEFAULT_LOCK_VIDEO_ORIENTATION, DEFAULT_LOCK_VIDEO_ORIENTATION >= 0 ? "" : " (unlocked)",
DEFAULT_LOCAL_PORT); DEFAULT_LOCAL_PORT);
} }
@@ -266,19 +259,6 @@ parse_max_fps(const char *s, uint16_t *max_fps) {
return true; return true;
} }
static bool
parse_lock_video_orientation(const char *s, int8_t *lock_video_orientation) {
long value;
bool ok = parse_integer_arg(s, &value, false, -1, 3,
"lock video orientation");
if (!ok) {
return false;
}
*lock_video_orientation = (int8_t) value;
return true;
}
static bool static bool
parse_window_position(const char *s, int16_t *position) { parse_window_position(const char *s, int16_t *position) {
long value; long value;
@@ -347,54 +327,51 @@ guess_record_format(const char *filename) {
return 0; return 0;
} }
#define OPT_RENDER_EXPIRED_FRAMES 1000 #define OPT_RENDER_EXPIRED_FRAMES 1000
#define OPT_WINDOW_TITLE 1001 #define OPT_WINDOW_TITLE 1001
#define OPT_PUSH_TARGET 1002 #define OPT_PUSH_TARGET 1002
#define OPT_ALWAYS_ON_TOP 1003 #define OPT_ALWAYS_ON_TOP 1003
#define OPT_CROP 1004 #define OPT_CROP 1004
#define OPT_RECORD_FORMAT 1005 #define OPT_RECORD_FORMAT 1005
#define OPT_PREFER_TEXT 1006 #define OPT_PREFER_TEXT 1006
#define OPT_WINDOW_X 1007 #define OPT_WINDOW_X 1007
#define OPT_WINDOW_Y 1008 #define OPT_WINDOW_Y 1008
#define OPT_WINDOW_WIDTH 1009 #define OPT_WINDOW_WIDTH 1009
#define OPT_WINDOW_HEIGHT 1010 #define OPT_WINDOW_HEIGHT 1010
#define OPT_WINDOW_BORDERLESS 1011 #define OPT_WINDOW_BORDERLESS 1011
#define OPT_MAX_FPS 1012 #define OPT_MAX_FPS 1012
#define OPT_LOCK_VIDEO_ORIENTATION 1013
bool bool
scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) { scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
static const struct option long_options[] = { static const struct option long_options[] = {
{"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP}, {"always-on-top", no_argument, NULL, OPT_ALWAYS_ON_TOP},
{"bit-rate", required_argument, NULL, 'b'}, {"bit-rate", required_argument, NULL, 'b'},
{"crop", required_argument, NULL, OPT_CROP}, {"crop", required_argument, NULL, OPT_CROP},
{"fullscreen", no_argument, NULL, 'f'}, {"fullscreen", no_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'}, {"help", no_argument, NULL, 'h'},
{"lock-video-orientation", required_argument, NULL, {"max-fps", required_argument, NULL, OPT_MAX_FPS},
OPT_LOCK_VIDEO_ORIENTATION}, {"max-size", required_argument, NULL, 'm'},
{"max-fps", required_argument, NULL, OPT_MAX_FPS}, {"no-control", no_argument, NULL, 'n'},
{"max-size", required_argument, NULL, 'm'}, {"no-display", no_argument, NULL, 'N'},
{"no-control", no_argument, NULL, 'n'}, {"port", required_argument, NULL, 'p'},
{"no-display", no_argument, NULL, 'N'}, {"push-target", required_argument, NULL, OPT_PUSH_TARGET},
{"port", required_argument, NULL, 'p'}, {"record", required_argument, NULL, 'r'},
{"push-target", required_argument, NULL, OPT_PUSH_TARGET}, {"record-format", required_argument, NULL, OPT_RECORD_FORMAT},
{"record", required_argument, NULL, 'r'}, {"render-expired-frames", no_argument, NULL,
{"record-format", required_argument, NULL, OPT_RECORD_FORMAT}, OPT_RENDER_EXPIRED_FRAMES},
{"render-expired-frames", no_argument, NULL, {"serial", required_argument, NULL, 's'},
OPT_RENDER_EXPIRED_FRAMES}, {"show-touches", no_argument, NULL, 't'},
{"serial", required_argument, NULL, 's'}, {"turn-screen-off", no_argument, NULL, 'S'},
{"show-touches", no_argument, NULL, 't'}, {"prefer-text", no_argument, NULL, OPT_PREFER_TEXT},
{"turn-screen-off", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'v'},
{"prefer-text", no_argument, NULL, OPT_PREFER_TEXT}, {"window-title", required_argument, NULL, OPT_WINDOW_TITLE},
{"version", no_argument, NULL, 'v'}, {"window-x", required_argument, NULL, OPT_WINDOW_X},
{"window-title", required_argument, NULL, OPT_WINDOW_TITLE}, {"window-y", required_argument, NULL, OPT_WINDOW_Y},
{"window-x", required_argument, NULL, OPT_WINDOW_X}, {"window-width", required_argument, NULL, OPT_WINDOW_WIDTH},
{"window-y", required_argument, NULL, OPT_WINDOW_Y}, {"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT},
{"window-width", required_argument, NULL, OPT_WINDOW_WIDTH}, {"window-borderless", no_argument, NULL,
{"window-height", required_argument, NULL, OPT_WINDOW_HEIGHT}, OPT_WINDOW_BORDERLESS},
{"window-borderless", no_argument, NULL, {NULL, 0, NULL, 0 },
OPT_WINDOW_BORDERLESS},
{NULL, 0, NULL, 0 },
}; };
struct scrcpy_options *opts = &args->opts; struct scrcpy_options *opts = &args->opts;
@@ -440,11 +417,6 @@ scrcpy_parse_args(struct scrcpy_cli_args *args, int argc, char *argv[]) {
return false; return false;
} }
break; break;
case OPT_LOCK_VIDEO_ORIENTATION:
if (!parse_lock_video_orientation(optarg, &opts->lock_video_orientation)) {
return false;
}
break;
case 'n': case 'n':
opts->control = false; opts->control = false;
break; break;

View File

@@ -284,7 +284,6 @@ scrcpy(const struct scrcpy_options *options) {
.max_size = options->max_size, .max_size = options->max_size,
.bit_rate = options->bit_rate, .bit_rate = options->bit_rate,
.max_fps = options->max_fps, .max_fps = options->max_fps,
.lock_video_orientation = options->lock_video_orientation,
.control = options->control, .control = options->control,
}; };
if (!server_start(&server, options->serial, &params)) { if (!server_start(&server, options->serial, &params)) {

View File

@@ -19,7 +19,6 @@ struct scrcpy_options {
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation;
int16_t window_x; int16_t window_x;
int16_t window_y; int16_t window_y;
uint16_t window_width; uint16_t window_width;
@@ -46,7 +45,6 @@ struct scrcpy_options {
.max_size = DEFAULT_MAX_SIZE, \ .max_size = DEFAULT_MAX_SIZE, \
.bit_rate = DEFAULT_BIT_RATE, \ .bit_rate = DEFAULT_BIT_RATE, \
.max_fps = 0, \ .max_fps = 0, \
.lock_video_orientation = DEFAULT_LOCK_VIDEO_ORIENTATION, \
.window_x = -1, \ .window_x = -1, \
.window_y = -1, \ .window_y = -1, \
.window_width = 0, \ .window_width = 0, \

View File

@@ -124,11 +124,9 @@ execute_server(struct server *server, const struct server_params *params) {
char max_size_string[6]; char max_size_string[6];
char bit_rate_string[11]; char bit_rate_string[11];
char max_fps_string[6]; char max_fps_string[6];
char lock_video_orientation_string[3];
sprintf(max_size_string, "%"PRIu16, params->max_size); sprintf(max_size_string, "%"PRIu16, params->max_size);
sprintf(bit_rate_string, "%"PRIu32, params->bit_rate); sprintf(bit_rate_string, "%"PRIu32, params->bit_rate);
sprintf(max_fps_string, "%"PRIu16, params->max_fps); sprintf(max_fps_string, "%"PRIu16, params->max_fps);
sprintf(lock_video_orientation_string, "%"PRIi8, params->lock_video_orientation);
const char *const cmd[] = { const char *const cmd[] = {
"shell", "shell",
"CLASSPATH=" DEVICE_SERVER_PATH, "CLASSPATH=" DEVICE_SERVER_PATH,
@@ -144,7 +142,6 @@ execute_server(struct server *server, const struct server_params *params) {
max_size_string, max_size_string,
bit_rate_string, bit_rate_string,
max_fps_string, max_fps_string,
lock_video_orientation_string,
server->tunnel_forward ? "true" : "false", server->tunnel_forward ? "true" : "false",
params->crop ? params->crop : "-", params->crop ? params->crop : "-",
"true", // always send frame meta (packet boundaries + timestamp) "true", // always send frame meta (packet boundaries + timestamp)

View File

@@ -36,7 +36,6 @@ struct server_params {
uint16_t max_size; uint16_t max_size;
uint32_t bit_rate; uint32_t bit_rate;
uint16_t max_fps; uint16_t max_fps;
int8_t lock_video_orientation;
bool control; bool control;
}; };

View File

@@ -48,7 +48,6 @@ static void test_options(void) {
"--fullscreen", "--fullscreen",
"--max-fps", "30", "--max-fps", "30",
"--max-size", "1024", "--max-size", "1024",
"--lock-video-orientation", "2",
// "--no-control" is not compatible with "--turn-screen-off" // "--no-control" is not compatible with "--turn-screen-off"
// "--no-display" is not compatible with "--fulscreen" // "--no-display" is not compatible with "--fulscreen"
"--port", "1234", "--port", "1234",
@@ -79,7 +78,6 @@ static void test_options(void) {
assert(opts->fullscreen); assert(opts->fullscreen);
assert(opts->max_fps == 30); assert(opts->max_fps == 30);
assert(opts->max_size == 1024); assert(opts->max_size == 1024);
assert(opts->lock_video_orientation == 2);
assert(opts->port == 1234); assert(opts->port == 1234);
assert(!strcmp(opts->push_target, "/sdcard/Movies")); assert(!strcmp(opts->push_target, "/sdcard/Movies"));
assert(!strcmp(opts->record_filename, "file")); assert(!strcmp(opts->record_filename, "file"));

View File

@@ -22,13 +22,10 @@ public final class Device {
private final ServiceManager serviceManager = new ServiceManager(); private final ServiceManager serviceManager = new ServiceManager();
private final int lockedVideoOrientation;
private ScreenInfo screenInfo; private ScreenInfo screenInfo;
private RotationListener rotationListener; private RotationListener rotationListener;
public Device(Options options) { public Device(Options options) {
lockedVideoOrientation = options.getLockedVideoOrientation();
screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize()); screenInfo = computeScreenInfo(options.getCrop(), options.getMaxSize());
registerRotationWatcher(new IRotationWatcher.Stub() { registerRotationWatcher(new IRotationWatcher.Stub() {
@Override @Override
@@ -51,11 +48,11 @@ public final class Device {
private ScreenInfo computeScreenInfo(Rect crop, int maxSize) { private ScreenInfo computeScreenInfo(Rect crop, int maxSize) {
DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo(); DisplayInfo displayInfo = serviceManager.getDisplayManager().getDisplayInfo();
int rotation = displayInfo.getRotation(); boolean rotated = (displayInfo.getRotation() & 1) != 0;
Size deviceSize = displayInfo.getSize(); 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 (rotated) {
// the crop (provided by the user) is expressed in the natural orientation // the crop (provided by the user) is expressed in the natural orientation
crop = flipRect(crop); crop = flipRect(crop);
} }
@@ -67,7 +64,7 @@ public final class Device {
} }
Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize); Size videoSize = computeVideoSize(contentRect.width(), contentRect.height(), maxSize);
return new ScreenInfo(contentRect, videoSize, rotation); return new ScreenInfo(contentRect, videoSize, rotated);
} }
private static String formatCrop(Rect rect) { private static String formatCrop(Rect rect) {
@@ -102,55 +99,22 @@ public final class Device {
return new Size(w, h); return new Size(w, h);
} }
/**
* Return the rotation to apply to the device rotation to get the requested locked video orientation
*
* @param deviceRotation the device rotation
* @return the rotation offset
*/
public int getVideoRotation(int deviceRotation) {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (deviceRotation + 4 - lockedVideoOrientation) % 4;
}
/**
* Return the rotation to apply to the requested locked video orientation to get the device rotation
* @param deviceRotation the device rotation
* @return the (reverse) rotation offset
*/
private int getReverseVideoRotation(int deviceRotation) {
if (lockedVideoOrientation == -1) {
// no offset
return 0;
}
return (lockedVideoOrientation + 4 - deviceRotation) % 4;
}
public Point getPhysicalPoint(Position position) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock // it hides the field on purpose, to read it with a lock
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization ScreenInfo screenInfo = getScreenInfo(); // read with synchronization
Size videoSize = screenInfo.getVideoSize(); Size videoSize = screenInfo.getVideoSize();
Size clientVideoSize = position.getScreenSize();
int deviceRotation = screenInfo.getRotation();
int reverseVideoRotation = getReverseVideoRotation(deviceRotation);
// reverse the video rotation to apply the events
Position devicePosition = position.rotate(reverseVideoRotation);
Size clientVideoSize = devicePosition.getScreenSize();
if (!videoSize.equals(clientVideoSize)) { if (!videoSize.equals(clientVideoSize)) {
// The client sends a click relative to a video with wrong dimensions, // The client sends a click relative to a video with wrong dimensions,
// the device may have been rotated since the event was generated, so ignore the event // the device may have been rotated since the event was generated, so ignore the event
return null; return null;
} }
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
Point point = devicePosition.getPoint(); Point point = position.getPoint();
int convertedX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth(); int scaledX = contentRect.left + point.getX() * contentRect.width() / videoSize.getWidth();
int convertedY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight(); int scaledY = contentRect.top + point.getY() * contentRect.height() / videoSize.getHeight();
return new Point(convertedX, convertedY); return new Point(scaledX, scaledY);
} }
public static String getDeviceName() { public static String getDeviceName() {

View File

@@ -6,7 +6,6 @@ public class Options {
private int maxSize; private int maxSize;
private int bitRate; private int bitRate;
private int maxFps; private int maxFps;
private int lockedVideoOrientation;
private boolean tunnelForward; private boolean tunnelForward;
private Rect crop; private Rect crop;
private boolean sendFrameMeta; // send PTS so that the client may record properly private boolean sendFrameMeta; // send PTS so that the client may record properly
@@ -36,14 +35,6 @@ public class Options {
this.maxFps = maxFps; this.maxFps = maxFps;
} }
public int getLockedVideoOrientation() {
return lockedVideoOrientation;
}
public void setLockedVideoOrientation(int lockedVideoOrientation) {
this.lockedVideoOrientation = lockedVideoOrientation;
}
public boolean isTunnelForward() { public boolean isTunnelForward() {
return tunnelForward; return tunnelForward;
} }

View File

@@ -23,19 +23,6 @@ public class Position {
return screenSize; return screenSize;
} }
public Position rotate(int rotation) {
switch (rotation) {
case 1:
return new Position(new Point(screenSize.getHeight() - point.getY(), point.getX()), screenSize.rotate());
case 2:
return new Position(new Point(screenSize.getWidth() - point.getX(), screenSize.getHeight() - point.getY()), screenSize);
case 3:
return new Position(new Point(point.getY(), screenSize.getWidth() - point.getX()), screenSize.rotate());
default:
return this;
}
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) {

View File

@@ -27,21 +27,19 @@ public class ScreenEncoder implements Device.RotationListener {
private int bitRate; private int bitRate;
private int maxFps; private int maxFps;
private int lockedVideoOrientation;
private int iFrameInterval; private int iFrameInterval;
private boolean sendFrameMeta; private boolean sendFrameMeta;
private long ptsOrigin; private long ptsOrigin;
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation, int iFrameInterval) { public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int iFrameInterval) {
this.sendFrameMeta = sendFrameMeta; this.sendFrameMeta = sendFrameMeta;
this.bitRate = bitRate; this.bitRate = bitRate;
this.maxFps = maxFps; this.maxFps = maxFps;
this.lockedVideoOrientation = lockedVideoOrientation;
this.iFrameInterval = iFrameInterval; this.iFrameInterval = iFrameInterval;
} }
public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps, int lockedVideoOrientation) { public ScreenEncoder(boolean sendFrameMeta, int bitRate, int maxFps) {
this(sendFrameMeta, bitRate, maxFps, lockedVideoOrientation, DEFAULT_I_FRAME_INTERVAL); this(sendFrameMeta, bitRate, maxFps, DEFAULT_I_FRAME_INTERVAL);
} }
@Override @Override
@@ -64,14 +62,12 @@ public class ScreenEncoder implements Device.RotationListener {
do { do {
MediaCodec codec = createCodec(); MediaCodec codec = createCodec();
IBinder display = createDisplay(); IBinder display = createDisplay();
ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = device.getScreenInfo().getContentRect();
Rect contentRect = screenInfo.getContentRect(); Rect videoRect = device.getScreenInfo().getVideoSize().toRect();
Rect videoRect = screenInfo.getVideoSize().toRect(); setSize(format, videoRect.width(), videoRect.height());
int videoRotation = device.getVideoRotation(screenInfo.getRotation());
setSize(format, videoRotation, videoRect.width(), videoRect.height());
configure(codec, format); configure(codec, format);
Surface surface = codec.createInputSurface(); Surface surface = codec.createInputSurface();
setDisplaySurface(display, surface, videoRotation, contentRect, videoRect); setDisplaySurface(display, surface, contentRect, videoRect);
codec.start(); codec.start();
try { try {
alive = encode(codec, fd); alive = encode(codec, fd);
@@ -171,21 +167,16 @@ public class ScreenEncoder implements Device.RotationListener {
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} }
private static void setSize(MediaFormat format, int orientation, int width, int height) { private static void setSize(MediaFormat format, int width, int height) {
if (orientation % 2 == 0) { format.setInteger(MediaFormat.KEY_WIDTH, width);
format.setInteger(MediaFormat.KEY_WIDTH, width); format.setInteger(MediaFormat.KEY_HEIGHT, height);
format.setInteger(MediaFormat.KEY_HEIGHT, height);
return;
}
format.setInteger(MediaFormat.KEY_WIDTH, height);
format.setInteger(MediaFormat.KEY_HEIGHT, width);
} }
private static void setDisplaySurface(IBinder display, Surface surface, int orientation, Rect deviceRect, Rect displayRect) { private static void setDisplaySurface(IBinder display, Surface surface, Rect deviceRect, Rect displayRect) {
SurfaceControl.openTransaction(); SurfaceControl.openTransaction();
try { try {
SurfaceControl.setDisplaySurface(display, surface); SurfaceControl.setDisplaySurface(display, surface);
SurfaceControl.setDisplayProjection(display, orientation, deviceRect, displayRect); SurfaceControl.setDisplayProjection(display, 0, deviceRect, displayRect);
SurfaceControl.setDisplayLayerStack(display, 0); SurfaceControl.setDisplayLayerStack(display, 0);
} finally { } finally {
SurfaceControl.closeTransaction(); SurfaceControl.closeTransaction();

View File

@@ -5,12 +5,12 @@ import android.graphics.Rect;
public final class ScreenInfo { public final class ScreenInfo {
private final Rect contentRect; // device size, possibly cropped private final Rect contentRect; // device size, possibly cropped
private final Size videoSize; private final Size videoSize;
private final int rotation; private final boolean rotated;
public ScreenInfo(Rect contentRect, Size videoSize, int rotation) { public ScreenInfo(Rect contentRect, Size videoSize, boolean rotated) {
this.contentRect = contentRect; this.contentRect = contentRect;
this.videoSize = videoSize; this.videoSize = videoSize;
this.rotation = rotation; this.rotated = rotated;
} }
public Rect getContentRect() { public Rect getContentRect() {
@@ -21,25 +21,11 @@ public final class ScreenInfo {
return videoSize; return videoSize;
} }
public int getRotation() { public ScreenInfo withRotation(int rotation) {
return rotation; boolean newRotated = (rotation & 1) != 0;
} if (rotated == newRotated) {
public ScreenInfo withRotation(int newRotation) {
if (newRotation == rotation) {
return this; return this;
} }
// true if changed between portrait and landscape return new ScreenInfo(Device.flipRect(contentRect), videoSize.rotate(), newRotated);
boolean orientationChanged = (rotation + newRotation) % 2 != 0;
Rect newContentRect;
Size newVideoSize;
if (orientationChanged) {
newContentRect = Device.flipRect(contentRect);
newVideoSize = videoSize.rotate();
} else {
newContentRect = contentRect;
newVideoSize = videoSize;
}
return new ScreenInfo(newContentRect, newVideoSize, newRotation);
} }
} }

View File

@@ -19,8 +19,7 @@ public final class Server {
final Device device = new Device(options); final Device device = new Device(options);
boolean tunnelForward = options.isTunnelForward(); boolean tunnelForward = options.isTunnelForward();
try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) { try (DesktopConnection connection = DesktopConnection.open(device, tunnelForward)) {
ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps(), ScreenEncoder screenEncoder = new ScreenEncoder(options.getSendFrameMeta(), options.getBitRate(), options.getMaxFps());
options.getLockedVideoOrientation());
if (options.getControl()) { if (options.getControl()) {
Controller controller = new Controller(device, connection); Controller controller = new Controller(device, connection);
@@ -80,8 +79,8 @@ public final class Server {
"The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")"); "The server version (" + clientVersion + ") does not match the client " + "(" + BuildConfig.VERSION_NAME + ")");
} }
if (args.length != 9) { if (args.length != 8) {
throw new IllegalArgumentException("Expecting 9 parameters"); throw new IllegalArgumentException("Expecting 8 parameters");
} }
Options options = new Options(); Options options = new Options();
@@ -95,20 +94,17 @@ public final class Server {
int maxFps = Integer.parseInt(args[3]); int maxFps = Integer.parseInt(args[3]);
options.setMaxFps(maxFps); options.setMaxFps(maxFps);
int lockedVideoOrientation = Integer.parseInt(args[4]);
options.setLockedVideoOrientation(lockedVideoOrientation);
// use "adb forward" instead of "adb tunnel"? (so the server must listen) // use "adb forward" instead of "adb tunnel"? (so the server must listen)
boolean tunnelForward = Boolean.parseBoolean(args[5]); boolean tunnelForward = Boolean.parseBoolean(args[4]);
options.setTunnelForward(tunnelForward); options.setTunnelForward(tunnelForward);
Rect crop = parseCrop(args[6]); Rect crop = parseCrop(args[5]);
options.setCrop(crop); options.setCrop(crop);
boolean sendFrameMeta = Boolean.parseBoolean(args[7]); boolean sendFrameMeta = Boolean.parseBoolean(args[6]);
options.setSendFrameMeta(sendFrameMeta); options.setSendFrameMeta(sendFrameMeta);
boolean control = Boolean.parseBoolean(args[8]); boolean control = Boolean.parseBoolean(args[7]);
options.setControl(control); options.setControl(control);
return options; return options;