Compare commits

..

15 Commits

Author SHA1 Message Date
Simon Chan
fb6b223622 Add --camera-facing
Add an option to select the camera by its lens facing (any, front, back
or external).

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-10-27 09:46:41 +02:00
Romain Vimont
cb7fea31cb Make camera id optional
If no camera id is provided, use the first camera available.
2023-10-26 23:39:53 +02:00
Romain Vimont
05aa988946 Handle camera disconnection 2023-10-26 23:39:53 +02:00
Romain Vimont
39ed0d7cbc Automatically select audio source
If --audio-source is not specified, select the default value
according to the video source:
 - for display mirroring, use device audio by default;
 - for camera mirroring, use microphone by default.
2023-10-26 23:39:53 +02:00
Romain Vimont
f92c22e331 DONOTMERGE workaround for Android 11 testing 2023-10-26 23:39:53 +02:00
Simon Chan
89049db1dd Add camera mirroring
Add --video-source=camera, and related options:
 - --camera-id=ID: select the camera (ids are listed by --list-cameras);
 - --camera-size=WIDTHxHEIGHT: select the capture size.

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-10-26 23:39:52 +02:00
Romain Vimont
88083483b0 Add --list-camera-sizes 2023-10-26 23:06:34 +02:00
Simon Chan
3d2f39fc8a Add --list-cameras
Add an option to list the device cameras.

Co-authored-by: Romain Vimont <rom@rom1v.com>
2023-10-26 23:06:34 +02:00
Romain Vimont
986bceb269 Factorize --list- options handling
This will limit code duplication as more list options will be added.
2023-10-26 23:06:34 +02:00
Romain Vimont
9d01140826 Make separator configurable for parsing integers
The separator was hardcoded to ':'. This will allow to reuse the
function to parse sizes as WIDTHxHEIGHT.
2023-10-26 23:06:34 +02:00
Simon Chan
616544d995 Extract SurfaceCapture from ScreenEncoder
Extract an interface SurfaceCapture from ScreenEncoder, representing a
video source which can be rendered to a Surface for encoding.

Split ScreenEncoder into:
 - ScreenCapture, implementing SurfaceCapture to capture the device
   screen,
 - SurfaceEncoder, to encode any SurfaceCapture.

This separation prepares the introduction of another SurfaceCapture
implementation to capture the camera instead of the device screen.

Co-authored-by: Romain Vimont <rom@rom1v.com>
2023-10-26 23:06:34 +02:00
Romain Vimont
3c1a2eb87a Rename --display to --display-id
The option is named "display id" everywhere.

This will be consistent with --camera-id (there will be many camera
options, so an option --camera would be confusing).
2023-10-26 23:06:33 +02:00
Romain Vimont
b7ad652a75 Move empty string test for crop option parsing
For consistency with other options.
2023-10-26 22:42:46 +02:00
Avinash Sonawane
76a99a7fcd Replace raw number by its name
PR #4373 <https://github.com/Genymobile/scrcpy/pull/4373>

Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-10-25 16:13:36 +02:00
Avinash Sonawane
68b55ef2fe Replace sprintf() with safer snprintf()
PR #4373 <https://github.com/Genymobile/scrcpy/pull/4373>

Co-authored-by: Romain Vimont <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
2023-10-25 16:13:34 +02:00
18 changed files with 299 additions and 37 deletions

View File

@@ -10,10 +10,13 @@ _scrcpy() {
--audio-source= --audio-source=
--audio-output-buffer= --audio-output-buffer=
-b --video-bit-rate= -b --video-bit-rate=
--camera-id=
--camera-facing=
--camera-size=
--crop= --crop=
-d --select-usb -d --select-usb
--disable-screensaver --disable-screensaver
--display= --display-id=
--display-buffer= --display-buffer=
-e --select-tcpip -e --select-tcpip
-f --fullscreen -f --fullscreen
@@ -74,6 +77,7 @@ _scrcpy() {
--video-codec= --video-codec=
--video-codec-options= --video-codec-options=
--video-encoder= --video-encoder=
--video-source=
-w --stay-awake -w --stay-awake
--window-borderless --window-borderless
--window-title= --window-title=
@@ -93,10 +97,18 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur")) COMPREPLY=($(compgen -W 'opus aac raw' -- "$cur"))
return return
;; ;;
--video-source)
COMPREPLY=($(compgen -W 'display camera' -- "$cur"))
return
;;
--audio-source) --audio-source)
COMPREPLY=($(compgen -W 'output mic' -- "$cur")) COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
return return
;; ;;
--camera-facing)
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--lock-video-orientation) --lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur")) COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
return return
@@ -141,8 +153,10 @@ _scrcpy() {
|--audio-codec-options \ |--audio-codec-options \
|--audio-encoder \ |--audio-encoder \
|--audio-output-buffer \ |--audio-output-buffer \
|--camera-id \
|--camera-size \
|--crop \ |--crop \
|--display \ |--display-id \
|--display-buffer \ |--display-buffer \
|--max-fps \ |--max-fps \
|-m|--max-size \ |-m|--max-size \

View File

@@ -17,10 +17,13 @@ arguments=(
'--audio-source=[Select the audio source]:source:(output mic)' '--audio-source=[Select the audio source]:source:(output mic)'
'--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]' '--audio-output-buffer=[Configure the size of the SDL audio output buffer (in milliseconds)]'
{-b,--video-bit-rate=}'[Encode the video at the given bit-rate]' {-b,--video-bit-rate=}'[Encode the video at the given bit-rate]'
'--camera-id=[Specify the camera id to mirror]'
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
'--camera-size=[Specify an explicit camera capture size]'
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]' '--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
{-d,--select-usb}'[Use USB device]' {-d,--select-usb}'[Use USB device]'
'--disable-screensaver[Disable screensaver while scrcpy is running]' '--disable-screensaver[Disable screensaver while scrcpy is running]'
'--display=[Specify the display id to mirror]' '--display-id=[Specify the display id to mirror]'
'--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]' '--display-buffer=[Add a buffering delay \(in milliseconds\) before displaying]'
{-e,--select-tcpip}'[Use TCP/IP device]' {-e,--select-tcpip}'[Use TCP/IP device]'
{-f,--fullscreen}'[Start in fullscreen]' {-f,--fullscreen}'[Start in fullscreen]'
@@ -78,6 +81,7 @@ arguments=(
'--video-codec=[Select the video codec]:codec:(h264 h265 av1)' '--video-codec=[Select the video codec]:codec:(h264 h265 av1)'
'--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]' '--video-codec-options=[Set a list of comma-separated key\:type=value options for the device video encoder]'
'--video-encoder=[Use a specific MediaCodec video encoder]' '--video-encoder=[Use a specific MediaCodec video encoder]'
'--video-source=[Select the video source]:source:(display camera)'
{-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]' {-w,--stay-awake}'[Keep the device on while scrcpy is running, when the device is plugged in]'
'--window-borderless[Disable window decorations \(display borderless window\)]' '--window-borderless[Disable window decorations \(display borderless window\)]'
'--window-title=[Set a custom window title]' '--window-title=[Set a custom window title]'

View File

@@ -75,6 +75,22 @@ Encode the video at the given bit rate, expressed in bits/s. Unit suffixes are s
Default is 8M (8000000). Default is 8M (8000000).
.TP
.BI "\-\-camera\-id " id
Specify the device camera id to mirror.
The available camera ids can be listed by \-\-list\-cameras.
.TP
.BI "\-\-camera\-facing " facing
Select the device camera by its facing direction.
Possible values are "front", "back" and "external".
.TP
.BI "\-\-camera\-size " width\fRx\fIheight
Specify an explicit camera capture size.
.TP .TP
.BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy .BI "\-\-crop " width\fR:\fIheight\fR:\fIx\fR:\fIy
Crop the device screen on the server. Crop the device screen on the server.
@@ -94,7 +110,7 @@ Also see \fB\-e\fR (\fB\-\-select\-tcpip\fR).
Disable screensaver while scrcpy is running. Disable screensaver while scrcpy is running.
.TP .TP
.BI "\-\-display " id .BI "\-\-display\-id " id
Specify the device display id to mirror. Specify the device display id to mirror.
The available display ids can be listed by \-\-list\-displays. The available display ids can be listed by \-\-list\-displays.
@@ -432,6 +448,12 @@ Use a specific MediaCodec video encoder (depending on the codec provided by \fB\
The available encoders can be listed by \-\-list\-encoders. The available encoders can be listed by \-\-list\-encoders.
.TP
.BI "\-\-video\-source " source
Select the video source (display or camera).
Default is display.
.TP .TP
.B \-w, \-\-stay-awake .B \-w, \-\-stay-awake
Keep the device on while scrcpy is running, when the device is plugged in. Keep the device on while scrcpy is running, when the device is plugged in.

View File

@@ -218,8 +218,16 @@ sc_adb_forward(struct sc_intr *intr, const char *serial, uint16_t local_port,
const char *device_socket_name, unsigned flags) { const char *device_socket_name, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
assert(r >= 0 && (size_t) r < sizeof(local));
r = snprintf(remote, sizeof(remote), "localabstract:%s",
device_socket_name);
if (r < 0 || (size_t) r >= sizeof(remote)) {
LOGE("Could not write socket name");
return false;
}
assert(serial); assert(serial);
const char *const argv[] = const char *const argv[] =
@@ -233,7 +241,9 @@ bool
sc_adb_forward_remove(struct sc_intr *intr, const char *serial, sc_adb_forward_remove(struct sc_intr *intr, const char *serial,
uint16_t local_port, unsigned flags) { uint16_t local_port, unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
sprintf(local, "tcp:%" PRIu16, local_port); int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
assert(r >= 0 && (size_t) r < sizeof(local));
(void) r;
assert(serial); assert(serial);
const char *const argv[] = const char *const argv[] =
@@ -249,8 +259,16 @@ sc_adb_reverse(struct sc_intr *intr, const char *serial,
unsigned flags) { unsigned flags) {
char local[4 + 5 + 1]; // tcp:PORT char local[4 + 5 + 1]; // tcp:PORT
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
sprintf(local, "tcp:%" PRIu16, local_port); int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port);
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); assert(r >= 0 && (size_t) r < sizeof(local));
r = snprintf(remote, sizeof(remote), "localabstract:%s",
device_socket_name);
if (r < 0 || (size_t) r >= sizeof(remote)) {
LOGE("Could not write socket name");
return false;
}
assert(serial); assert(serial);
const char *const argv[] = const char *const argv[] =
SC_ADB_COMMAND("-s", serial, "reverse", remote, local); SC_ADB_COMMAND("-s", serial, "reverse", remote, local);
@@ -263,7 +281,12 @@ bool
sc_adb_reverse_remove(struct sc_intr *intr, const char *serial, sc_adb_reverse_remove(struct sc_intr *intr, const char *serial,
const char *device_socket_name, unsigned flags) { const char *device_socket_name, unsigned flags) {
char remote[108 + 14 + 1]; // localabstract:NAME char remote[108 + 14 + 1]; // localabstract:NAME
snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name); int r = snprintf(remote, sizeof(remote), "localabstract:%s",
device_socket_name);
if (r < 0 || (size_t) r >= sizeof(remote)) {
LOGE("Device socket name too long");
return false;
}
assert(serial); assert(serial);
const char *const argv[] = const char *const argv[] =
@@ -333,7 +356,9 @@ bool
sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port, sc_adb_tcpip(struct sc_intr *intr, const char *serial, uint16_t port,
unsigned flags) { unsigned flags) {
char port_string[5 + 1]; char port_string[5 + 1];
sprintf(port_string, "%" PRIu16, port); int r = snprintf(port_string, sizeof(port_string), "%" PRIu16, port);
assert(r >= 0 && (size_t) r < sizeof(port_string));
(void) r;
assert(serial); assert(serial);
const char *const argv[] = const char *const argv[] =

View File

@@ -32,6 +32,7 @@ enum {
OPT_WINDOW_BORDERLESS, OPT_WINDOW_BORDERLESS,
OPT_MAX_FPS, OPT_MAX_FPS,
OPT_LOCK_VIDEO_ORIENTATION, OPT_LOCK_VIDEO_ORIENTATION,
OPT_DISPLAY,
OPT_DISPLAY_ID, OPT_DISPLAY_ID,
OPT_ROTATION, OPT_ROTATION,
OPT_RENDER_DRIVER, OPT_RENDER_DRIVER,
@@ -85,6 +86,7 @@ enum {
OPT_LIST_CAMERA_SIZES, OPT_LIST_CAMERA_SIZES,
OPT_CAMERA_ID, OPT_CAMERA_ID,
OPT_CAMERA_SIZE, OPT_CAMERA_SIZE,
OPT_CAMERA_FACING,
}; };
struct sc_option { struct sc_option {
@@ -203,13 +205,18 @@ static const struct sc_option options[] = {
}, },
{ {
.longopt_id = OPT_CAMERA_ID, .longopt_id = OPT_CAMERA_ID,
.longopt = "camera", .longopt = "camera-id",
.argdesc = "id", .argdesc = "id",
.text = "Specify the device camera id to mirror when using " .text = "Specify the device camera id to mirror.\n"
"--video-source=camera.\n"
"The available camera ids can be listed by:\n" "The available camera ids can be listed by:\n"
" scrcpy --list-cameras\n" " scrcpy --list-cameras",
"Default is \"auto\" (the first one)", },
{
.longopt_id = OPT_CAMERA_FACING,
.longopt = "camera-facing",
.argdesc = "facing",
.text = "Select the device camera by its facing direction.\n"
"Possible values are \"front\", \"back\" and \"external\".",
}, },
{ {
.longopt_id = OPT_CAMERA_SIZE, .longopt_id = OPT_CAMERA_SIZE,
@@ -253,9 +260,15 @@ static const struct sc_option options[] = {
.text = "Disable screensaver while scrcpy is running.", .text = "Disable screensaver while scrcpy is running.",
}, },
{ {
.longopt_id = OPT_DISPLAY_ID, // deprecated
.longopt_id = OPT_DISPLAY,
.longopt = "display", .longopt = "display",
.argdesc = "id", .argdesc = "id",
},
{
.longopt_id = OPT_DISPLAY_ID,
.longopt = "display-id",
.argdesc = "id",
.text = "Specify the device display id to mirror.\n" .text = "Specify the device display id to mirror.\n"
"The available display ids can be listed by:\n" "The available display ids can be listed by:\n"
" scrcpy --list-displays\n" " scrcpy --list-displays\n"
@@ -1694,6 +1707,34 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
return false; return false;
} }
static bool
parse_camera_facing(const char *optarg, enum sc_camera_facing *facing) {
if (!strcmp(optarg, "front")) {
*facing = SC_CAMERA_FACING_FRONT;
return true;
}
if (!strcmp(optarg, "back")) {
*facing = SC_CAMERA_FACING_BACK;
return true;
}
if (!strcmp(optarg, "external")) {
*facing = SC_CAMERA_FACING_EXTERNAL;
return true;
}
if (*optarg == '\0') {
// Empty string is a valid value (equivalent to not passing the option)
*facing = SC_CAMERA_FACING_ANY;
return true;
}
LOGE("Unsupported camera facing: %s (expected front, back or external)",
optarg);
return false;
}
static bool static bool
parse_time_limit(const char *s, sc_tick *tick) { parse_time_limit(const char *s, sc_tick *tick) {
long value; long value;
@@ -1756,6 +1797,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_CROP: case OPT_CROP:
opts->crop = optarg; opts->crop = optarg;
break; break;
case OPT_DISPLAY:
LOGW("--display is deprecated, use --display-id instead.");
// fall through
case OPT_DISPLAY_ID: case OPT_DISPLAY_ID:
if (!parse_display_id(optarg, &opts->display_id)) { if (!parse_display_id(optarg, &opts->display_id)) {
return false; return false;
@@ -2091,6 +2135,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_CAMERA_SIZE: case OPT_CAMERA_SIZE:
opts->camera_size = optarg; opts->camera_size = optarg;
break; break;
case OPT_CAMERA_FACING:
if (!parse_camera_facing(optarg, &opts->camera_facing)) {
return false;
}
break;
default: default:
// getopt prints the error message on stderr // getopt prints the error message on stderr
return false; return false;
@@ -2185,15 +2234,19 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
} }
if (opts->video_source == SC_VIDEO_SOURCE_CAMERA) { if (opts->video_source == SC_VIDEO_SOURCE_CAMERA) {
if (opts->display_id) {
LOGE("--display-id is only available with --video-source=display");
return false;
}
if (opts->lock_video_orientation != if (opts->lock_video_orientation !=
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) { SC_LOCK_VIDEO_ORIENTATION_UNLOCKED) {
LOGE("--lock-video-orientation is not supported for camera"); LOGE("--lock-video-orientation is not supported for camera");
return false; return false;
} }
if (!opts->camera_id) { if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
LOGE("Camera id must be specified by --camera=ID " LOGE("Could not specify both --camera-id and --camera-facing");
"(list the available ids with --list-cameras)");
return false; return false;
} }
@@ -2206,6 +2259,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGI("Camera video source: control disabled"); LOGI("Camera video source: control disabled");
opts->control = false; opts->control = false;
} }
} else if (opts->camera_id
|| opts->camera_facing != SC_CAMERA_FACING_ANY
|| opts->camera_size) {
LOGE("Camera options are only available with --video-source=camera");
return false;
} }
if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) { if (opts->audio && opts->audio_source == SC_AUDIO_SOURCE_AUTO) {

View File

@@ -21,6 +21,7 @@ const struct scrcpy_options scrcpy_options_default = {
.record_format = SC_RECORD_FORMAT_AUTO, .record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT, .keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT, .mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
.camera_facing = SC_CAMERA_FACING_ANY,
.port_range = { .port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST, .first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST, .last = DEFAULT_LOCAL_PORT_RANGE_LAST,

View File

@@ -55,6 +55,13 @@ enum sc_audio_source {
SC_AUDIO_SOURCE_MIC, SC_AUDIO_SOURCE_MIC,
}; };
enum sc_camera_facing {
SC_CAMERA_FACING_ANY,
SC_CAMERA_FACING_FRONT,
SC_CAMERA_FACING_BACK,
SC_CAMERA_FACING_EXTERNAL,
};
enum sc_lock_video_orientation { enum sc_lock_video_orientation {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1, SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts // lock the current orientation when scrcpy starts
@@ -133,6 +140,7 @@ struct scrcpy_options {
enum sc_record_format record_format; enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode; enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode; enum sc_mouse_input_mode mouse_input_mode;
enum sc_camera_facing camera_facing;
struct sc_port_range port_range; struct sc_port_range port_range;
uint32_t tunnel_host; uint32_t tunnel_host;
uint16_t tunnel_port; uint16_t tunnel_port;

View File

@@ -353,6 +353,7 @@ scrcpy(struct scrcpy_options *options) {
.audio_codec = options->audio_codec, .audio_codec = options->audio_codec,
.video_source = options->video_source, .video_source = options->video_source,
.audio_source = options->audio_source, .audio_source = options->audio_source,
.camera_facing = options->camera_facing,
.crop = options->crop, .crop = options->crop,
.port_range = options->port_range, .port_range = options->port_range,
.tunnel_host = options->tunnel_host, .tunnel_host = options->tunnel_host,

View File

@@ -183,6 +183,20 @@ sc_server_get_codec_name(enum sc_codec codec) {
} }
} }
static const char *
sc_server_get_camera_facing_name(enum sc_camera_facing camera_facing) {
switch (camera_facing) {
case SC_CAMERA_FACING_FRONT:
return "front";
case SC_CAMERA_FACING_BACK:
return "back";
case SC_CAMERA_FACING_EXTERNAL:
return "external";
default:
return NULL;
}
}
static sc_pid static sc_pid
execute_server(struct sc_server *server, execute_server(struct sc_server *server,
const struct sc_server_params *params) { const struct sc_server_params *params) {
@@ -285,6 +299,10 @@ execute_server(struct sc_server *server,
if (params->camera_size) { if (params->camera_size) {
ADD_PARAM("camera_size=%s", params->camera_size); ADD_PARAM("camera_size=%s", params->camera_size);
} }
if (params->camera_facing != SC_CAMERA_FACING_ANY) {
ADD_PARAM("camera_facing=%s",
sc_server_get_camera_facing_name(params->camera_facing));
}
if (params->show_touches) { if (params->show_touches) {
ADD_PARAM("show_touches=true"); ADD_PARAM("show_touches=true");
} }

View File

@@ -28,6 +28,7 @@ struct sc_server_params {
enum sc_codec audio_codec; enum sc_codec audio_codec;
enum sc_video_source video_source; enum sc_video_source video_source;
enum sc_audio_source audio_source; enum sc_audio_source audio_source;
enum sc_camera_facing camera_facing;
const char *crop; const char *crop;
const char *video_codec_options; const char *video_codec_options;
const char *audio_codec_options; const char *audio_codec_options;

View File

@@ -27,7 +27,8 @@
// keyboard support, though OS could support more keys via modifying the report // keyboard support, though OS could support more keys via modifying the report
// desc. 6 should be enough for scrcpy. // desc. 6 should be enough for scrcpy.
#define HID_KEYBOARD_MAX_KEYS 6 #define HID_KEYBOARD_MAX_KEYS 6
#define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS) #define HID_KEYBOARD_EVENT_SIZE \
(HID_KEYBOARD_INDEX_KEYS + HID_KEYBOARD_MAX_KEYS)
#define HID_RESERVED 0x00 #define HID_RESERVED 0x00
#define HID_ERROR_ROLL_OVER 0x01 #define HID_ERROR_ROLL_OVER 0x01

View File

@@ -269,21 +269,25 @@ static void test_parse_integer_with_suffix(void) {
char buf[32]; char buf[32];
sprintf(buf, "%ldk", LONG_MAX / 2000); int r = snprintf(buf, sizeof(buf), "%ldk", LONG_MAX / 2000);
assert(r >= 0 && (size_t) r < sizeof(buf));
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(ok); assert(ok);
assert(value == LONG_MAX / 2000 * 1000); assert(value == LONG_MAX / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MAX / 2000); r = snprintf(buf, sizeof(buf), "%ldm", LONG_MAX / 2000);
assert(r >= 0 && (size_t) r < sizeof(buf));
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(!ok); assert(!ok);
sprintf(buf, "%ldk", LONG_MIN / 2000); r = snprintf(buf, sizeof(buf), "%ldk", LONG_MIN / 2000);
assert(r >= 0 && (size_t) r < sizeof(buf));
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(ok); assert(ok);
assert(value == LONG_MIN / 2000 * 1000); assert(value == LONG_MIN / 2000 * 1000);
sprintf(buf, "%ldm", LONG_MIN / 2000); r = snprintf(buf, sizeof(buf), "%ldm", LONG_MIN / 2000);
assert(r >= 0 && (size_t) r < sizeof(buf));
ok = sc_str_parse_integer_with_suffix(buf, &value); ok = sc_str_parse_integer_with_suffix(buf, &value);
assert(!ok); assert(!ok);
} }

View File

@@ -143,7 +143,7 @@ If several displays are available on the Android device, it is possible to
select the display to mirror: select the display to mirror:
```bash ```bash
scrcpy --display=1 scrcpy --display-id=1
``` ```
The list of display ids can be retrieved by: The list of display ids can be retrieved by:

View File

@@ -6,7 +6,9 @@ import android.annotation.SuppressLint;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest; import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.OutputConfiguration;
@@ -26,7 +28,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class CameraCapture extends SurfaceCapture { public class CameraCapture extends SurfaceCapture {
private final String cameraId; public static class CameraSelection {
private String explicitCameraId;
private CameraFacing cameraFacing;
public CameraSelection(String explicitCameraId, CameraFacing cameraFacing) {
this.explicitCameraId = explicitCameraId;
this.cameraFacing = cameraFacing;
}
boolean hasId() {
return explicitCameraId != null;
}
boolean hasProperties() {
return cameraFacing != null;
}
}
private final CameraSelection cameraSelection;
private final Size explicitSize; private final Size explicitSize;
private HandlerThread cameraThread; private HandlerThread cameraThread;
@@ -36,8 +56,8 @@ public class CameraCapture extends SurfaceCapture {
private final AtomicBoolean disconnected = new AtomicBoolean(); private final AtomicBoolean disconnected = new AtomicBoolean();
public CameraCapture(String cameraId, Size explicitSize) { public CameraCapture(CameraSelection cameraSelection, Size explicitSize) {
this.cameraId = cameraId; this.cameraSelection = cameraSelection;
this.explicitSize = explicitSize; this.explicitSize = explicitSize;
} }
@@ -49,12 +69,49 @@ public class CameraCapture extends SurfaceCapture {
cameraExecutor = new HandlerExecutor(cameraHandler); cameraExecutor = new HandlerExecutor(cameraHandler);
try { try {
String cameraId = selectCamera(cameraSelection);
if (cameraId == null) {
throw new IOException("No matching camera found");
}
Ln.i("Using camera '" + cameraId + "'");
cameraDevice = openCamera(cameraId); cameraDevice = openCamera(cameraId);
} catch (CameraAccessException | InterruptedException e) { } catch (CameraAccessException | InterruptedException e) {
throw new IOException(e); throw new IOException(e);
} }
} }
private String selectCamera(CameraSelection cameraSelection) throws CameraAccessException {
if (cameraSelection.hasId()) {
return cameraSelection.explicitCameraId;
}
CameraManager cameraManager = ServiceManager.getCameraManager();
String[] cameraIds = cameraManager.getCameraIdList();
if (!cameraSelection.hasProperties()) {
// Use the first one
return cameraIds.length > 0 ? cameraIds[0] : null;
}
for (String cameraId : cameraIds) {
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
if (cameraSelection.cameraFacing != null) {
int facing = characteristics.get(CameraCharacteristics.LENS_FACING);
if (cameraSelection.cameraFacing.value() != facing) {
// Does not match
continue;
}
}
return cameraId;
}
// Not found
return null;
}
@Override @Override
public void start(Surface surface) throws IOException { public void start(Surface surface) throws IOException {
try { try {

View File

@@ -0,0 +1,31 @@
package com.genymobile.scrcpy;
import android.hardware.camera2.CameraCharacteristics;
public enum CameraFacing {
FRONT("front", CameraCharacteristics.LENS_FACING_FRONT),
BACK("back", CameraCharacteristics.LENS_FACING_BACK),
EXTERNAL("external", CameraCharacteristics.LENS_FACING_EXTERNAL);
private final String name;
private final int value;
CameraFacing(String name, int value) {
this.name = name;
this.value = value;
}
int value() {
return value;
}
static CameraFacing findByName(String name) {
for (CameraFacing facing : CameraFacing.values()) {
if (name.equals(facing.name)) {
return facing;
}
}
return null;
}
}

View File

@@ -54,7 +54,7 @@ public final class LogUtils {
builder.append("\n (none)"); builder.append("\n (none)");
} else { } else {
for (int id : displayIds) { for (int id : displayIds) {
builder.append("\n --display=").append(id).append(" ("); builder.append("\n --display-id=").append(id).append(" (");
DisplayInfo displayInfo = displayManager.getDisplayInfo(id); DisplayInfo displayInfo = displayManager.getDisplayInfo(id);
if (displayInfo != null) { if (displayInfo != null) {
Size size = displayInfo.getSize(); Size size = displayInfo.getSize();
@@ -90,7 +90,7 @@ public final class LogUtils {
builder.append("\n (none)"); builder.append("\n (none)");
} else { } else {
for (String id : cameraIds) { for (String id : cameraIds) {
builder.append("\n --video-source=camera --camera=").append(id); builder.append("\n --video-source=camera --camera-id=").append(id);
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
int facing = characteristics.get(CameraCharacteristics.LENS_FACING); int facing = characteristics.get(CameraCharacteristics.LENS_FACING);
builder.append(" (").append(getCameraFacingName(facing)).append(", "); builder.append(" (").append(getCameraFacingName(facing)).append(", ");

View File

@@ -26,6 +26,7 @@ public class Options {
private int displayId; private int displayId;
private String cameraId; private String cameraId;
private Size cameraSize; private Size cameraSize;
private CameraFacing cameraFacing;
private boolean showTouches; private boolean showTouches;
private boolean stayAwake; private boolean stayAwake;
private List<CodecOption> videoCodecOptions; private List<CodecOption> videoCodecOptions;
@@ -126,6 +127,10 @@ public class Options {
return cameraSize; return cameraSize;
} }
public CameraFacing getCameraFacing() {
return cameraFacing;
}
public boolean getShowTouches() { public boolean getShowTouches() {
return showTouches; return showTouches;
} }
@@ -292,7 +297,9 @@ public class Options {
options.tunnelForward = Boolean.parseBoolean(value); options.tunnelForward = Boolean.parseBoolean(value);
break; break;
case "crop": case "crop":
if (!value.isEmpty()) {
options.crop = parseCrop(value); options.crop = parseCrop(value);
}
break; break;
case "control": case "control":
options.control = Boolean.parseBoolean(value); options.control = Boolean.parseBoolean(value);
@@ -349,11 +356,22 @@ public class Options {
options.listCameraSizes = Boolean.parseBoolean(value); options.listCameraSizes = Boolean.parseBoolean(value);
break; break;
case "camera_id": case "camera_id":
if (!value.isEmpty()) {
options.cameraId = value; options.cameraId = value;
}
break; break;
case "camera_size": case "camera_size":
options.cameraSize = parseSize(value); options.cameraSize = parseSize(value);
break; break;
case "camera_facing":
if (!value.isEmpty()) {
CameraFacing facing = CameraFacing.findByName(value);
if (facing == null) {
throw new IllegalArgumentException("Camera facing " + value + " not supported");
}
options.cameraFacing = facing;
}
break;
case "send_device_meta": case "send_device_meta":
options.sendDeviceMeta = Boolean.parseBoolean(value); options.sendDeviceMeta = Boolean.parseBoolean(value);
break; break;
@@ -385,9 +403,6 @@ public class Options {
} }
private static Rect parseCrop(String crop) { private static Rect parseCrop(String crop) {
if (crop.isEmpty()) {
return null;
}
// input format: "width:height:x:y" // input format: "width:height:x:y"
String[] tokens = crop.split(":"); String[] tokens = crop.split(":");
if (tokens.length != 4) { if (tokens.length != 4) {

View File

@@ -138,7 +138,9 @@ public final class Server {
if (options.getVideoSource() == VideoSource.DISPLAY) { if (options.getVideoSource() == VideoSource.DISPLAY) {
surfaceCapture = new ScreenCapture(device); surfaceCapture = new ScreenCapture(device);
} else { } else {
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraSize()); CameraCapture.CameraSelection cameraSelection = new CameraCapture.CameraSelection(options.getCameraId(),
options.getCameraFacing());
surfaceCapture = new CameraCapture(cameraSelection, options.getCameraSize());
} }
SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(),
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());