Compare commits

..

9 Commits

Author SHA1 Message Date
Romain Vimont
3638e8ea17 Handle camera disconnection 2023-10-25 00:20:34 +02:00
Romain Vimont
88f4a035ab 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-25 00:20:34 +02:00
Romain Vimont
92e0898661 DONOTMERGE workaround for Android 11 testing 2023-10-25 00:20:34 +02:00
Simon Chan
3e65b587ae Add camera mirroring
Add --video-source=camera, and related options:
 - --camera=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-25 00:20:34 +02:00
Romain Vimont
06a3f13bd0 Add --list-camera-sizes 2023-10-25 00:20:34 +02:00
Simon Chan
79309f8fd5 Add --list-cameras
Add an option to list the device cameras.

Co-authored-by: Romain Vimont <rom@rom1v.com>
2023-10-25 00:20:34 +02:00
Romain Vimont
6145ad2f12 Factorize --list- options handling
This will limit code duplication as more list options will be added.
2023-10-25 00:20:34 +02:00
Romain Vimont
1d7c74d0f3 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-25 00:20:08 +02:00
Simon Chan
e4ac021da4 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-24 23:29:06 +02:00
18 changed files with 40 additions and 287 deletions

View File

@@ -10,13 +10,10 @@ _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-id= --display=
--display-buffer= --display-buffer=
-e --select-tcpip -e --select-tcpip
-f --fullscreen -f --fullscreen
@@ -77,7 +74,6 @@ _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=
@@ -97,18 +93,10 @@ _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
@@ -153,10 +141,8 @@ _scrcpy() {
|--audio-codec-options \ |--audio-codec-options \
|--audio-encoder \ |--audio-encoder \
|--audio-output-buffer \ |--audio-output-buffer \
|--camera-id \
|--camera-size \
|--crop \ |--crop \
|--display-id \ |--display \
|--display-buffer \ |--display-buffer \
|--max-fps \ |--max-fps \
|-m|--max-size \ |-m|--max-size \

View File

@@ -17,13 +17,10 @@ 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-id=[Specify the display id to mirror]' '--display=[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]'
@@ -81,7 +78,6 @@ 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,22 +75,6 @@ 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.
@@ -110,7 +94,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 " id .BI "\-\-display " 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.
@@ -448,12 +432,6 @@ 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,16 +218,8 @@ 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);
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[] =
@@ -241,9 +233,7 @@ 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
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port); sprintf(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[] =
@@ -259,16 +249,8 @@ 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
int r = snprintf(local, sizeof(local), "tcp:%" PRIu16, local_port); sprintf(local, "tcp:%" PRIu16, local_port);
assert(r >= 0 && (size_t) r < sizeof(local)); snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
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);
@@ -281,12 +263,7 @@ 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
int r = snprintf(remote, sizeof(remote), "localabstract:%s", snprintf(remote, sizeof(remote), "localabstract:%s", device_socket_name);
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[] =
@@ -356,9 +333,7 @@ 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];
int r = snprintf(port_string, sizeof(port_string), "%" PRIu16, port); sprintf(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,7 +32,6 @@ 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,
@@ -86,7 +85,6 @@ 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 {
@@ -205,18 +203,13 @@ static const struct sc_option options[] = {
}, },
{ {
.longopt_id = OPT_CAMERA_ID, .longopt_id = OPT_CAMERA_ID,
.longopt = "camera-id", .longopt = "camera",
.argdesc = "id", .argdesc = "id",
.text = "Specify the device camera id to mirror.\n" .text = "Specify the device camera id to mirror when using "
"--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", " scrcpy --list-cameras\n"
}, "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,
@@ -259,15 +252,9 @@ static const struct sc_option options[] = {
.longopt = "disable-screensaver", .longopt = "disable-screensaver",
.text = "Disable screensaver while scrcpy is running.", .text = "Disable screensaver while scrcpy is running.",
}, },
{
// deprecated
.longopt_id = OPT_DISPLAY,
.longopt = "display",
.argdesc = "id",
},
{ {
.longopt_id = OPT_DISPLAY_ID, .longopt_id = OPT_DISPLAY_ID,
.longopt = "display-id", .longopt = "display",
.argdesc = "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"
@@ -1707,34 +1694,6 @@ 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;
@@ -1797,9 +1756,6 @@ 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;
@@ -2135,11 +2091,6 @@ 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;
@@ -2240,6 +2191,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
return false; return false;
} }
if (!opts->camera_id) {
LOGE("Camera id must be specified by --camera=ID "
"(list the available ids with --list-cameras)");
return false;
}
if (!opts->camera_size) { if (!opts->camera_size) {
LOGE("Camera size must be specified by --camera-size=WIDTHxHEIGHT"); LOGE("Camera size must be specified by --camera-size=WIDTHxHEIGHT");
return false; return false;

View File

@@ -21,7 +21,6 @@ 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,13 +55,6 @@ 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
@@ -140,7 +133,6 @@ 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,7 +353,6 @@ 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,20 +183,6 @@ 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) {
@@ -299,10 +285,6 @@ 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,7 +28,6 @@ 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,8 +27,7 @@
// 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 \ #define HID_KEYBOARD_EVENT_SIZE (2 + HID_KEYBOARD_MAX_KEYS)
(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,25 +269,21 @@ static void test_parse_integer_with_suffix(void) {
char buf[32]; char buf[32];
int r = snprintf(buf, sizeof(buf), "%ldk", LONG_MAX / 2000); sprintf(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);
r = snprintf(buf, sizeof(buf), "%ldm", LONG_MAX / 2000); sprintf(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);
r = snprintf(buf, sizeof(buf), "%ldk", LONG_MIN / 2000); sprintf(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);
r = snprintf(buf, sizeof(buf), "%ldm", LONG_MIN / 2000); sprintf(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-id=1 scrcpy --display=1
``` ```
The list of display ids can be retrieved by: The list of display ids can be retrieved by:

View File

@@ -6,9 +6,7 @@ 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;
@@ -28,25 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class CameraCapture extends SurfaceCapture { public class CameraCapture extends SurfaceCapture {
public static class CameraSelection { private final String cameraId;
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;
@@ -56,8 +36,8 @@ public class CameraCapture extends SurfaceCapture {
private final AtomicBoolean disconnected = new AtomicBoolean(); private final AtomicBoolean disconnected = new AtomicBoolean();
public CameraCapture(CameraSelection cameraSelection, Size explicitSize) { public CameraCapture(String cameraId, Size explicitSize) {
this.cameraSelection = cameraSelection; this.cameraId = cameraId;
this.explicitSize = explicitSize; this.explicitSize = explicitSize;
} }
@@ -69,49 +49,12 @@ 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

@@ -1,31 +0,0 @@
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-id=").append(id).append(" ("); builder.append("\n --display=").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-id=").append(id); builder.append("\n --video-source=camera --camera=").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,7 +26,6 @@ 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;
@@ -127,10 +126,6 @@ public class Options {
return cameraSize; return cameraSize;
} }
public CameraFacing getCameraFacing() {
return cameraFacing;
}
public boolean getShowTouches() { public boolean getShowTouches() {
return showTouches; return showTouches;
} }
@@ -297,9 +292,7 @@ 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);
@@ -356,22 +349,11 @@ 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;
@@ -403,6 +385,9 @@ 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,9 +138,7 @@ public final class Server {
if (options.getVideoSource() == VideoSource.DISPLAY) { if (options.getVideoSource() == VideoSource.DISPLAY) {
surfaceCapture = new ScreenCapture(device); surfaceCapture = new ScreenCapture(device);
} else { } else {
CameraCapture.CameraSelection cameraSelection = new CameraCapture.CameraSelection(options.getCameraId(), surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraSize());
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());