mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-08-10 10:08:52 +00:00
Merge da72a3978f
into f01231dff8
This commit is contained in:
commit
1d7780a40e
23 changed files with 453 additions and 9 deletions
|
@ -18,6 +18,8 @@ _scrcpy() {
|
|||
--camera-fps=
|
||||
--camera-high-speed
|
||||
--camera-size=
|
||||
--camera-zoom-step=
|
||||
--camera-torch
|
||||
--capture-orientation=
|
||||
--crop=
|
||||
-d --select-usb
|
||||
|
@ -197,6 +199,8 @@ _scrcpy() {
|
|||
|--camera-id \
|
||||
|--camera-fps \
|
||||
|--camera-size \
|
||||
|--camera-zoom-step \
|
||||
|--camera-torch \
|
||||
|--crop \
|
||||
|--display-id \
|
||||
|--max-fps \
|
||||
|
|
|
@ -25,6 +25,8 @@ arguments=(
|
|||
'--camera-facing=[Select the device camera by its facing direction]:facing:(front back external)'
|
||||
'--camera-fps=[Specify the camera capture frame rate]'
|
||||
'--camera-size=[Specify an explicit camera capture size]'
|
||||
'--camera-zoom-step=[Specify the camera zoom step value]'
|
||||
'--camera-torch[Turns the torch on when starting the camera]'
|
||||
'--capture-orientation=[Set the capture video orientation]:orientation:(0 90 180 270 flip0 flip90 flip180 flip270 @0 @90 @180 @270 @flip0 @flip90 @flip180 @flip270)'
|
||||
'--crop=[\[width\:height\:x\:y\] Crop the device screen on the server]'
|
||||
{-d,--select-usb}'[Use USB device]'
|
||||
|
|
|
@ -37,6 +37,7 @@ src = [
|
|||
'src/hid/hid_gamepad.c',
|
||||
'src/hid/hid_keyboard.c',
|
||||
'src/hid/hid_mouse.c',
|
||||
'src/im/camera.c',
|
||||
'src/trait/frame_source.c',
|
||||
'src/trait/packet_source.c',
|
||||
'src/uhid/gamepad_uhid.c',
|
||||
|
|
10
app/scrcpy.1
10
app/scrcpy.1
|
@ -131,6 +131,16 @@ The available camera ids can be listed by \fB\-\-list\-cameras\fR.
|
|||
.BI "\-\-camera\-size " width\fRx\fIheight
|
||||
Specify an explicit camera capture size.
|
||||
|
||||
.TP
|
||||
.BI \-\-camera\-torch
|
||||
Turns the torch on when starting the camera.
|
||||
|
||||
.TP
|
||||
.BI "\-\-camera\-zoom\-step " step
|
||||
Specify the camera zoom step value.
|
||||
|
||||
Default is 0.025.
|
||||
|
||||
.TP
|
||||
.BI "\-\-capture\-orientation " value
|
||||
Possible values are 0, 90, 180, 270, flip0, flip90, flip180 and flip270, possibly prefixed by '@'.
|
||||
|
|
|
@ -93,6 +93,8 @@ enum {
|
|||
OPT_CAMERA_AR,
|
||||
OPT_CAMERA_FPS,
|
||||
OPT_CAMERA_HIGH_SPEED,
|
||||
OPT_CAMERA_ZOOM_STEP,
|
||||
OPT_CAMERA_TORCH,
|
||||
OPT_DISPLAY_ORIENTATION,
|
||||
OPT_RECORD_ORIENTATION,
|
||||
OPT_ORIENTATION,
|
||||
|
@ -563,6 +565,17 @@ static const struct sc_option options[] = {
|
|||
.text = "Limit the frame rate of screen capture (officially supported "
|
||||
"since Android 10, but may work on earlier versions).",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_CAMERA_ZOOM_STEP,
|
||||
.longopt = "camera-zoom-step",
|
||||
.argdesc = "value",
|
||||
.text = "Specify the camera zoom step value.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_CAMERA_TORCH,
|
||||
.longopt = "camera-torch",
|
||||
.text = "Turns the torch on when starting the camera.",
|
||||
},
|
||||
{
|
||||
.longopt_id = OPT_MOUSE,
|
||||
.longopt = "mouse",
|
||||
|
@ -1142,6 +1155,18 @@ static const struct sc_shortcut shortcuts[] = {
|
|||
.shortcuts = { "Right-click (when screen is off)" },
|
||||
.text = "Power on",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+q" },
|
||||
.text = "Camera toggle torch",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+1" },
|
||||
.text = "Camera zoom in",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+2" },
|
||||
.text = "Camera zoom out",
|
||||
},
|
||||
{
|
||||
.shortcuts = { "MOD+o" },
|
||||
.text = "Turn device screen off (keep mirroring)",
|
||||
|
@ -2420,6 +2445,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||
case OPT_MAX_FPS:
|
||||
opts->max_fps = optarg;
|
||||
break;
|
||||
case OPT_CAMERA_ZOOM_STEP:
|
||||
opts->camera_zoom_step = optarg;
|
||||
break;
|
||||
case OPT_CAMERA_TORCH:
|
||||
opts->camera_torch = true;
|
||||
break;
|
||||
case 'm':
|
||||
if (!parse_max_size(optarg, &opts->max_size)) {
|
||||
return false;
|
||||
|
@ -3104,11 +3135,6 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||
LOGE("--camera-high-speed requires an explicit --camera-fps value");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts->control) {
|
||||
LOGI("Camera video source: control disabled");
|
||||
opts->control = false;
|
||||
}
|
||||
} else if (opts->camera_id
|
||||
|| opts->camera_ar
|
||||
|| opts->camera_facing != SC_CAMERA_FACING_ANY
|
||||
|
|
|
@ -187,6 +187,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
|||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
|
||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_TOGGLE_TORCH:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN:
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
|
||||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
// no additional data
|
||||
return 1;
|
||||
|
@ -318,6 +321,15 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
|||
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
|
||||
LOG_CMSG("reset video");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_TOGGLE_TORCH:
|
||||
LOG_CMSG("toggle camera torch");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN:
|
||||
LOG_CMSG("camera zoom in");
|
||||
break;
|
||||
case SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT:
|
||||
LOG_CMSG("camera zoom out");
|
||||
break;
|
||||
default:
|
||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||
break;
|
||||
|
|
|
@ -43,6 +43,9 @@ enum sc_control_msg_type {
|
|||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||
SC_CONTROL_MSG_TYPE_START_APP,
|
||||
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_TOGGLE_TORCH,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN,
|
||||
SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT,
|
||||
};
|
||||
|
||||
enum sc_copy_key {
|
||||
|
|
193
app/src/im/camera.c
Normal file
193
app/src/im/camera.c
Normal file
|
@ -0,0 +1,193 @@
|
|||
#include "input_manager.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "android/input.h"
|
||||
#include "android/keycodes.h"
|
||||
#include "input_events.h"
|
||||
#include "screen.h"
|
||||
#include "shortcut_mod.h"
|
||||
#include "util/log.h"
|
||||
#include "camera.h"
|
||||
|
||||
static void
|
||||
camera_toggle_torch(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_TOGGLE_TORCH;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request camera toggle torch");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
camera_zoom_in(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_IN;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request camera zoom in");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
camera_zoom_out(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_CAMERA_ZOOM_OUT;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request camera zoom out");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
reset_video(struct sc_input_manager *im) {
|
||||
assert(im->controller);
|
||||
|
||||
struct sc_control_msg msg;
|
||||
msg.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO;
|
||||
|
||||
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||
LOGW("Could not request reset video");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
apply_orientation_transform(struct sc_input_manager *im,
|
||||
enum sc_orientation transform) {
|
||||
struct sc_screen *screen = im->screen;
|
||||
enum sc_orientation new_orientation =
|
||||
sc_orientation_apply(screen->orientation, transform);
|
||||
sc_screen_set_orientation(screen, new_orientation);
|
||||
}
|
||||
|
||||
static void
|
||||
sc_input_manager_process_key_camera(struct sc_input_manager *im,
|
||||
const SDL_KeyboardEvent *event) {
|
||||
bool control = im->controller;
|
||||
bool paused = im->screen->paused;
|
||||
bool video = im->screen->video;
|
||||
|
||||
SDL_Keycode sdl_keycode = event->keysym.sym;
|
||||
uint16_t mod = event->keysym.mod;
|
||||
bool down = event->type == SDL_KEYDOWN;
|
||||
bool shift = event->keysym.mod & KMOD_SHIFT;
|
||||
bool repeat = event->repeat;
|
||||
|
||||
uint16_t mods = im->sdl_shortcut_mods;
|
||||
bool is_shortcut = sc_shortcut_mods_is_shortcut_mod(mods, mod)
|
||||
|| sc_shortcut_mods_is_shortcut_key(mods, sdl_keycode);
|
||||
|
||||
if (down && !repeat) {
|
||||
if (sdl_keycode == im->last_keycode && mod == im->last_mod) {
|
||||
++im->key_repeat;
|
||||
} else {
|
||||
im->key_repeat = 0;
|
||||
im->last_keycode = sdl_keycode;
|
||||
im->last_mod = mod;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_shortcut) {
|
||||
switch (sdl_keycode) {
|
||||
// Camera
|
||||
case SDLK_1:
|
||||
if (control && video && !shift && down) {
|
||||
camera_zoom_in(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_2:
|
||||
if (control && video && !shift && down) {
|
||||
camera_zoom_out(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_q:
|
||||
if (control && video && !shift && !repeat && down) {
|
||||
camera_toggle_torch(im);
|
||||
}
|
||||
return;
|
||||
// Window
|
||||
case SDLK_f:
|
||||
if (video && !shift && !repeat && down) {
|
||||
sc_screen_toggle_fullscreen(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_w:
|
||||
if (video && !shift && !repeat && down) {
|
||||
sc_screen_resize_to_fit(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_g:
|
||||
if (video && !shift && !repeat && down) {
|
||||
sc_screen_resize_to_pixel_perfect(im->screen);
|
||||
}
|
||||
return;
|
||||
case SDLK_DOWN:
|
||||
if (shift) {
|
||||
if (video && !repeat && down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_UP:
|
||||
if (shift) {
|
||||
if (video && !repeat && down) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_180);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_LEFT:
|
||||
if (video && !repeat && down) {
|
||||
if (shift) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_0);
|
||||
} else {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_270);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case SDLK_RIGHT:
|
||||
if (video && !repeat && down) {
|
||||
if (shift) {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_FLIP_0);
|
||||
} else {
|
||||
apply_orientation_transform(im,
|
||||
SC_ORIENTATION_90);
|
||||
}
|
||||
}
|
||||
return;
|
||||
// Video
|
||||
case SDLK_r:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
reset_video(im);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
sc_input_manager_handle_event_camera(struct sc_input_manager *im,
|
||||
const SDL_Event *event) {
|
||||
switch (event->type) {
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP:
|
||||
sc_input_manager_process_key_camera(im, &event->key);
|
||||
break;
|
||||
}
|
||||
}
|
11
app/src/im/camera.h
Normal file
11
app/src/im/camera.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#ifndef SC_CAMERA_H
|
||||
#define SC_CAMERA_H
|
||||
|
||||
#include "input_manager.h"
|
||||
|
||||
#include <SDL2/SDL_events.h>
|
||||
|
||||
void sc_input_manager_handle_event_camera(struct sc_input_manager *im,
|
||||
const SDL_Event *event);
|
||||
|
||||
#endif
|
|
@ -52,6 +52,8 @@ const struct scrcpy_options scrcpy_options_default = {
|
|||
.video_bit_rate = 0,
|
||||
.audio_bit_rate = 0,
|
||||
.max_fps = NULL,
|
||||
.camera_zoom_step = NULL,
|
||||
.camera_torch = false,
|
||||
.capture_orientation = SC_ORIENTATION_0,
|
||||
.capture_orientation_lock = SC_ORIENTATION_UNLOCKED,
|
||||
.display_orientation = SC_ORIENTATION_0,
|
||||
|
|
|
@ -262,6 +262,7 @@ struct scrcpy_options {
|
|||
uint32_t audio_bit_rate;
|
||||
const char *max_fps; // float to be parsed by the server
|
||||
const char *angle; // float to be parsed by the server
|
||||
const char *camera_zoom_step; // float to be parsed by the server
|
||||
enum sc_orientation capture_orientation;
|
||||
enum sc_orientation_lock capture_orientation_lock;
|
||||
enum sc_orientation display_orientation;
|
||||
|
@ -314,6 +315,7 @@ struct scrcpy_options {
|
|||
bool require_audio;
|
||||
bool kill_adb_on_close;
|
||||
bool camera_high_speed;
|
||||
bool camera_torch;
|
||||
#define SC_OPTION_LIST_ENCODERS 0x1
|
||||
#define SC_OPTION_LIST_DISPLAYS 0x2
|
||||
#define SC_OPTION_LIST_CAMERAS 0x4
|
||||
|
|
|
@ -471,6 +471,8 @@ scrcpy(struct scrcpy_options *options) {
|
|||
.power_on = options->power_on,
|
||||
.kill_adb_on_close = options->kill_adb_on_close,
|
||||
.camera_high_speed = options->camera_high_speed,
|
||||
.camera_zoom_step = options->camera_zoom_step,
|
||||
.camera_torch = options->camera_torch,
|
||||
.vd_destroy_content = options->vd_destroy_content,
|
||||
.vd_system_decorations = options->vd_system_decorations,
|
||||
.list = options->list,
|
||||
|
@ -804,6 +806,7 @@ aoa_complete:
|
|||
|
||||
struct sc_screen_params screen_params = {
|
||||
.video = options->video_playback,
|
||||
.video_source = options->video_source,
|
||||
.controller = controller,
|
||||
.fp = fp,
|
||||
.kp = kp,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "icon.h"
|
||||
#include "options.h"
|
||||
#include "util/log.h"
|
||||
#include "im/camera.h"
|
||||
|
||||
#define DISPLAY_MARGINS 96
|
||||
|
||||
|
@ -335,6 +336,7 @@ sc_screen_init(struct sc_screen *screen,
|
|||
screen->orientation = SC_ORIENTATION_0;
|
||||
|
||||
screen->video = params->video;
|
||||
screen->video_source = params->video_source;
|
||||
|
||||
screen->req.x = params->window_x;
|
||||
screen->req.y = params->window_y;
|
||||
|
@ -867,7 +869,15 @@ sc_screen_handle_event(struct sc_screen *screen, const SDL_Event *event) {
|
|||
return true;
|
||||
}
|
||||
|
||||
switch (screen->video_source) {
|
||||
case SC_VIDEO_SOURCE_DISPLAY:
|
||||
sc_input_manager_handle_event(&screen->im, event);
|
||||
break;
|
||||
case SC_VIDEO_SOURCE_CAMERA:
|
||||
sc_input_manager_handle_event_camera(&screen->im, event);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,8 @@ struct sc_screen {
|
|||
|
||||
bool paused;
|
||||
AVFrame *resume_frame;
|
||||
|
||||
enum sc_video_source video_source;
|
||||
};
|
||||
|
||||
struct sc_screen_params {
|
||||
|
@ -100,6 +102,8 @@ struct sc_screen_params {
|
|||
|
||||
bool fullscreen;
|
||||
bool start_fps_counter;
|
||||
|
||||
enum sc_video_source video_source;
|
||||
};
|
||||
|
||||
// initialize screen, create window, renderer and texture (window is hidden)
|
||||
|
|
|
@ -354,6 +354,13 @@ execute_server(struct sc_server *server,
|
|||
if (params->camera_fps) {
|
||||
ADD_PARAM("camera_fps=%" PRIu16, params->camera_fps);
|
||||
}
|
||||
if (params->camera_zoom_step) {
|
||||
VALIDATE_STRING(params->camera_zoom_step);
|
||||
ADD_PARAM("camera_zoom_step=%s", params->camera_zoom_step);
|
||||
}
|
||||
if (params->camera_torch) {
|
||||
ADD_PARAM("camera_torch=true");
|
||||
}
|
||||
if (params->camera_high_speed) {
|
||||
ADD_PARAM("camera_high_speed=true");
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ struct sc_server_params {
|
|||
uint32_t audio_bit_rate;
|
||||
const char *max_fps; // float to be parsed by the server
|
||||
const char *angle; // float to be parsed by the server
|
||||
const char *camera_zoom_step; // float to be parsed by the server
|
||||
bool camera_torch;
|
||||
sc_tick screen_off_timeout;
|
||||
enum sc_orientation capture_orientation;
|
||||
enum sc_orientation_lock capture_orientation_lock;
|
||||
|
|
|
@ -147,6 +147,28 @@ scrcpy --video-source=camera --camera-size=1920x1080 --camera-fps=240
|
|||
[high speed]: https://developer.android.com/reference/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession
|
||||
|
||||
|
||||
## Torch
|
||||
|
||||
- <kbd>MOD</kbd>+<kbd>q</kbd> to toggle the torch.
|
||||
|
||||
To turn the torch on when starting the camera, add `--camera-torch`.
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --camera-torch
|
||||
```
|
||||
|
||||
## Zoom
|
||||
|
||||
- <kbd>MOD</kbd>+<kbd>1</kbd> to zoom in.
|
||||
- <kbd>MOD</kbd>+<kbd>2</kbd> to zoom out.
|
||||
|
||||
To change the camera zoom step, set `--camera-zoom-step`.
|
||||
|
||||
```bash
|
||||
scrcpy --video-source=camera --camera-zoom-step=0.025
|
||||
```
|
||||
|
||||
|
||||
## Brace expansion tip
|
||||
|
||||
All camera options start with `--camera-`, so if your shell supports it, you can
|
||||
|
|
|
@ -53,6 +53,9 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
|
|||
| Inject computer clipboard text | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>v</kbd>
|
||||
| Open keyboard settings (HID keyboard only) | <kbd>MOD</kbd>+<kbd>k</kbd>
|
||||
| Enable/disable FPS counter (on stdout) | <kbd>MOD</kbd>+<kbd>i</kbd>
|
||||
| Camera torch toggle | <kbd>MOD</kbd>+<kbd>q</kbd>
|
||||
| Camera zoom in | <kbd>MOD</kbd>+<kbd>1</kbd>
|
||||
| Camera zoom out | <kbd>MOD</kbd>+<kbd>2</kbd>
|
||||
| Pinch-to-zoom/rotate | <kbd>Ctrl</kbd>+_click-and-move_
|
||||
| Tilt vertically (slide with 2 fingers) | <kbd>Shift</kbd>+_click-and-move_
|
||||
| Tilt horizontally (slide with 2 fingers) | <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+_click-and-move_
|
||||
|
|
|
@ -46,6 +46,8 @@ public class Options {
|
|||
private CameraAspectRatio cameraAspectRatio;
|
||||
private int cameraFps;
|
||||
private boolean cameraHighSpeed;
|
||||
private float cameraZoomStep;
|
||||
private boolean cameraTorch;
|
||||
private boolean showTouches;
|
||||
private boolean stayAwake;
|
||||
private int screenOffTimeout = -1;
|
||||
|
@ -176,6 +178,14 @@ public class Options {
|
|||
return cameraHighSpeed;
|
||||
}
|
||||
|
||||
public float getCameraZoomStep() {
|
||||
return cameraZoomStep;
|
||||
}
|
||||
|
||||
public boolean getCameraTorch() {
|
||||
return cameraTorch;
|
||||
}
|
||||
|
||||
public boolean getShowTouches() {
|
||||
return showTouches;
|
||||
}
|
||||
|
@ -474,6 +484,12 @@ public class Options {
|
|||
case "camera_high_speed":
|
||||
options.cameraHighSpeed = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "camera_zoom_step":
|
||||
options.cameraZoomStep = Float.parseFloat(value);
|
||||
break;
|
||||
case "camera_torch":
|
||||
options.cameraTorch = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "new_display":
|
||||
options.newDisplay = parseNewDisplay(value);
|
||||
break;
|
||||
|
|
|
@ -25,6 +25,9 @@ public final class ControlMessage {
|
|||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
|
||||
public static final int TYPE_START_APP = 16;
|
||||
public static final int TYPE_RESET_VIDEO = 17;
|
||||
public static final int TYPE_CAMERA_TOGGLE_TORCH = 18;
|
||||
public static final int TYPE_CAMERA_ZOOM_IN = 19;
|
||||
public static final int TYPE_CAMERA_ZOOM_OUT = 20;
|
||||
|
||||
public static final long SEQUENCE_INVALID = 0;
|
||||
|
||||
|
|
|
@ -46,6 +46,9 @@ public class ControlMessageReader {
|
|||
case ControlMessage.TYPE_COLLAPSE_PANELS:
|
||||
case ControlMessage.TYPE_ROTATE_DEVICE:
|
||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||
case ControlMessage.TYPE_CAMERA_TOGGLE_TORCH:
|
||||
case ControlMessage.TYPE_CAMERA_ZOOM_IN:
|
||||
case ControlMessage.TYPE_CAMERA_ZOOM_OUT:
|
||||
case ControlMessage.TYPE_RESET_VIDEO:
|
||||
return ControlMessage.createEmpty(type);
|
||||
case ControlMessage.TYPE_UHID_CREATE:
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.genymobile.scrcpy.device.Position;
|
|||
import com.genymobile.scrcpy.device.Size;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.SurfaceCapture;
|
||||
import com.genymobile.scrcpy.video.VirtualDisplayListener;
|
||||
import com.genymobile.scrcpy.wrappers.ClipboardManager;
|
||||
|
@ -99,6 +100,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
|
||||
// Used for resetting video encoding on RESET_VIDEO message
|
||||
private SurfaceCapture surfaceCapture;
|
||||
private CameraCapture cameraCapture;
|
||||
|
||||
public Controller(ControlChannel controlChannel, CleanUp cleanUp, Options options) {
|
||||
this.displayId = options.getDisplayId();
|
||||
|
@ -150,6 +152,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
|
||||
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
|
||||
this.surfaceCapture = surfaceCapture;
|
||||
if (this.surfaceCapture instanceof CameraCapture) {
|
||||
this.cameraCapture = (CameraCapture) this.surfaceCapture;
|
||||
}
|
||||
}
|
||||
|
||||
private UhidManager getUhidManager() {
|
||||
|
@ -331,6 +336,15 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
case ControlMessage.TYPE_RESET_VIDEO:
|
||||
resetVideo();
|
||||
break;
|
||||
case ControlMessage.TYPE_CAMERA_TOGGLE_TORCH:
|
||||
cameraToggleTorch();
|
||||
break;
|
||||
case ControlMessage.TYPE_CAMERA_ZOOM_IN:
|
||||
cameraZoomIn();
|
||||
break;
|
||||
case ControlMessage.TYPE_CAMERA_ZOOM_OUT:
|
||||
cameraZoomOut();
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
|
@ -754,4 +768,22 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
|||
surfaceCapture.requestInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
private void cameraToggleTorch() {
|
||||
if (cameraCapture != null) {
|
||||
cameraCapture.toggleTorch();
|
||||
}
|
||||
}
|
||||
|
||||
private void cameraZoomIn() {
|
||||
if (cameraCapture != null) {
|
||||
cameraCapture.zoomIn();
|
||||
}
|
||||
}
|
||||
|
||||
private void cameraZoomOut() {
|
||||
if (cameraCapture != null) {
|
||||
cameraCapture.zoomOut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,13 @@ public class CameraCapture extends SurfaceCapture {
|
|||
private Handler cameraHandler;
|
||||
private CameraDevice cameraDevice;
|
||||
private Executor cameraExecutor;
|
||||
private CameraCaptureSession session;
|
||||
private CaptureRequest.Builder requestBuilder;
|
||||
private boolean torchAvailable = false;
|
||||
private boolean torchOn = false;
|
||||
private float zoomLevel = 1f;
|
||||
private float zoomStep = 0.025f;
|
||||
private Range<Float> zoomRange = new Range<>(1f, 1f);
|
||||
|
||||
private final AtomicBoolean disconnected = new AtomicBoolean();
|
||||
|
||||
|
@ -90,6 +97,10 @@ public class CameraCapture extends SurfaceCapture {
|
|||
this.captureOrientation = options.getCaptureOrientation();
|
||||
assert captureOrientation != null;
|
||||
this.angle = options.getAngle();
|
||||
if (options.getCameraZoomStep() > 0f) {
|
||||
this.zoomStep = options.getCameraZoomStep();
|
||||
}
|
||||
this.torchOn = options.getCameraTorch();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -106,6 +117,19 @@ public class CameraCapture extends SurfaceCapture {
|
|||
}
|
||||
|
||||
Ln.i("Using camera '" + cameraId + "'");
|
||||
|
||||
CameraCharacteristics characteristics = ServiceManager.getCameraManager().getCameraCharacteristics(cameraId);
|
||||
|
||||
torchAvailable = Boolean.TRUE.equals(secureGet(characteristics, CameraCharacteristics.FLASH_INFO_AVAILABLE));
|
||||
if (!torchAvailable && torchOn) {
|
||||
Ln.w("Camera flash unit not found.");
|
||||
}
|
||||
|
||||
zoomRange = secureGet(characteristics, CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE);
|
||||
if (zoomRange != null) {
|
||||
Ln.d(String.format("Camera zoom range: [%f, %f]", zoomRange.getLower(), zoomRange.getUpper()));
|
||||
}
|
||||
|
||||
cameraDevice = openCamera(cameraId);
|
||||
} catch (CameraAccessException | InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
|
@ -262,7 +286,7 @@ public class CameraCapture extends SurfaceCapture {
|
|||
}
|
||||
|
||||
try {
|
||||
CameraCaptureSession session = createCaptureSession(cameraDevice, surface);
|
||||
session = createCaptureSession(cameraDevice, surface);
|
||||
CaptureRequest request = createCaptureRequest(surface);
|
||||
setRepeatingRequest(session, request);
|
||||
} catch (CameraAccessException | InterruptedException e) {
|
||||
|
@ -381,13 +405,17 @@ public class CameraCapture extends SurfaceCapture {
|
|||
}
|
||||
|
||||
private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException {
|
||||
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||
requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||
requestBuilder.addTarget(surface);
|
||||
|
||||
if (fps > 0) {
|
||||
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps));
|
||||
}
|
||||
|
||||
if (torchOn && torchAvailable) {
|
||||
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
|
||||
}
|
||||
|
||||
return requestBuilder.build();
|
||||
}
|
||||
|
||||
|
@ -421,6 +449,51 @@ public class CameraCapture extends SurfaceCapture {
|
|||
|
||||
@Override
|
||||
public void requestInvalidate() {
|
||||
// do nothing (the user could not request a reset anyway for now, since there is no controller for camera mirroring)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
private static <T> T secureGet(CameraCharacteristics characteristics, CameraCharacteristics.Key<T> key) {
|
||||
try {
|
||||
return characteristics.get(key);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Ln.w("Failed to get characteristic for key: " + key.getName(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void toggleTorch() {
|
||||
if (torchAvailable) {
|
||||
torchOn = !torchOn;
|
||||
try {
|
||||
requestBuilder.set(CaptureRequest.FLASH_MODE,
|
||||
(torchOn) ? CaptureRequest.FLASH_MODE_TORCH : CaptureRequest.FLASH_MODE_OFF);
|
||||
setRepeatingRequest(session, requestBuilder.build());
|
||||
} catch (CameraAccessException | InterruptedException e) {
|
||||
Ln.e("Camera toggle torch error: ", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setZoom(float level) {
|
||||
if (zoomRange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
float zoomClamped = Math.max(zoomRange.getLower(), Math.min(level, zoomRange.getUpper()));
|
||||
requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomClamped);
|
||||
setRepeatingRequest(session, requestBuilder.build());
|
||||
zoomLevel = zoomClamped;
|
||||
} catch (CameraAccessException | InterruptedException e) {
|
||||
Ln.e("Camera set zoom error: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void zoomIn() {
|
||||
setZoom(zoomLevel += zoomStep);
|
||||
}
|
||||
|
||||
public void zoomOut() {
|
||||
setZoom(zoomLevel -= zoomStep);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue