Add --camera-facing
Add an option to select the camera by its lens facing (front, back or external). PR #4213 <https://github.com/Genymobile/scrcpy/pull/4213> Co-authored-by: Romain Vimont <rom@rom1v.com> Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
parent
7f8d079c8c
commit
faebb7d70a
@ -11,6 +11,7 @@ _scrcpy() {
|
|||||||
--audio-output-buffer=
|
--audio-output-buffer=
|
||||||
-b --video-bit-rate=
|
-b --video-bit-rate=
|
||||||
--camera-id=
|
--camera-id=
|
||||||
|
--camera-facing=
|
||||||
--camera-size=
|
--camera-size=
|
||||||
--crop=
|
--crop=
|
||||||
-d --select-usb
|
-d --select-usb
|
||||||
@ -104,6 +105,10 @@ _scrcpy() {
|
|||||||
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
|
||||||
|
@ -18,6 +18,7 @@ arguments=(
|
|||||||
'--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-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]'
|
'--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]'
|
||||||
|
@ -81,6 +81,12 @@ Specify the device camera id to mirror.
|
|||||||
|
|
||||||
The available camera ids can be listed by \-\-list\-cameras.
|
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
|
.TP
|
||||||
.BI "\-\-camera\-size " width\fRx\fIheight
|
.BI "\-\-camera\-size " width\fRx\fIheight
|
||||||
Specify an explicit camera capture size.
|
Specify an explicit camera capture size.
|
||||||
|
@ -86,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 {
|
||||||
@ -210,6 +211,13 @@ static const struct sc_option options[] = {
|
|||||||
"The available camera ids can be listed by:\n"
|
"The available camera ids can be listed by:\n"
|
||||||
" scrcpy --list-cameras",
|
" scrcpy --list-cameras",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
.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,
|
||||||
.longopt = "camera-size",
|
.longopt = "camera-size",
|
||||||
@ -1700,6 +1708,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;
|
||||||
@ -2100,6 +2136,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;
|
||||||
@ -2199,6 +2240,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts->camera_id && opts->camera_facing != SC_CAMERA_FACING_ANY) {
|
||||||
|
LOGE("Could not specify both --camera-id and --camera-facing");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!opts->camera_size) {
|
if (!opts->camera_size) {
|
||||||
LOGE("Camera size must be specified by --camera-size");
|
LOGE("Camera size must be specified by --camera-size");
|
||||||
return false;
|
return false;
|
||||||
@ -2208,7 +2254,9 @@ 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_size) {
|
} 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");
|
LOGE("Camera options are only available with --video-source=camera");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -6,6 +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.CameraManager;
|
||||||
import android.hardware.camera2.CaptureFailure;
|
import android.hardware.camera2.CaptureFailure;
|
||||||
@ -28,6 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
public class CameraCapture extends SurfaceCapture {
|
public class CameraCapture extends SurfaceCapture {
|
||||||
|
|
||||||
private final String explicitCameraId;
|
private final String explicitCameraId;
|
||||||
|
private final CameraFacing cameraFacing;
|
||||||
private final Size explicitSize;
|
private final Size explicitSize;
|
||||||
|
|
||||||
private HandlerThread cameraThread;
|
private HandlerThread cameraThread;
|
||||||
@ -37,8 +39,9 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
|
|
||||||
private final AtomicBoolean disconnected = new AtomicBoolean();
|
private final AtomicBoolean disconnected = new AtomicBoolean();
|
||||||
|
|
||||||
public CameraCapture(String explicitCameraId, Size explicitSize) {
|
public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize) {
|
||||||
this.explicitCameraId = explicitCameraId;
|
this.explicitCameraId = explicitCameraId;
|
||||||
|
this.cameraFacing = cameraFacing;
|
||||||
this.explicitSize = explicitSize;
|
this.explicitSize = explicitSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +53,7 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
cameraExecutor = new HandlerExecutor(cameraHandler);
|
cameraExecutor = new HandlerExecutor(cameraHandler);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String cameraId = selectCamera(explicitCameraId);
|
String cameraId = selectCamera(explicitCameraId, cameraFacing);
|
||||||
if (cameraId == null) {
|
if (cameraId == null) {
|
||||||
throw new IOException("No matching camera found");
|
throw new IOException("No matching camera found");
|
||||||
}
|
}
|
||||||
@ -62,7 +65,7 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String selectCamera(String explicitCameraId) throws CameraAccessException {
|
private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException {
|
||||||
if (explicitCameraId != null) {
|
if (explicitCameraId != null) {
|
||||||
return explicitCameraId;
|
return explicitCameraId;
|
||||||
}
|
}
|
||||||
@ -70,8 +73,22 @@ public class CameraCapture extends SurfaceCapture {
|
|||||||
CameraManager cameraManager = ServiceManager.getCameraManager();
|
CameraManager cameraManager = ServiceManager.getCameraManager();
|
||||||
|
|
||||||
String[] cameraIds = cameraManager.getCameraIdList();
|
String[] cameraIds = cameraManager.getCameraIdList();
|
||||||
// Use the first one
|
if (cameraFacing == null) {
|
||||||
return cameraIds.length > 0 ? cameraIds[0] : null;
|
// Use the first one
|
||||||
|
return cameraIds.length > 0 ? cameraIds[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String cameraId : cameraIds) {
|
||||||
|
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(cameraId);
|
||||||
|
|
||||||
|
int facing = characteristics.get(CameraCharacteristics.LENS_FACING);
|
||||||
|
if (cameraFacing.value() == facing) {
|
||||||
|
return cameraId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not found
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
33
server/src/main/java/com/genymobile/scrcpy/CameraFacing.java
Normal file
33
server/src/main/java/com/genymobile/scrcpy/CameraFacing.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package com.genymobile.scrcpy;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.hardware.camera2.CameraCharacteristics;
|
||||||
|
|
||||||
|
public enum CameraFacing {
|
||||||
|
FRONT("front", CameraCharacteristics.LENS_FACING_FRONT),
|
||||||
|
BACK("back", CameraCharacteristics.LENS_FACING_BACK),
|
||||||
|
@SuppressLint("InlinedApi") // introduced in API 23
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
}
|
}
|
||||||
@ -360,6 +365,15 @@ public class Options {
|
|||||||
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;
|
||||||
|
@ -137,7 +137,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 {
|
||||||
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraSize());
|
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), 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());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user