This commit is contained in:
Tommie 2025-07-20 12:57:37 -04:00 committed by GitHub
commit 1d7780a40e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 453 additions and 9 deletions

View file

@ -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 \

View file

@ -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]'

View file

@ -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',

View file

@ -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 '@'.

View file

@ -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

View file

@ -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;

View file

@ -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
View 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
View 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

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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;
}
sc_input_manager_handle_event(&screen->im, event);
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;
}

View file

@ -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)

View file

@ -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");
}

View file

@ -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;

View file

@ -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

View file

@ -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_

View file

@ -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;

View file

@ -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;

View file

@ -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:

View file

@ -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();
}
}
}

View file

@ -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);
}
}