Skip to content

Commit

Permalink
Add --camera-facing
Browse files Browse the repository at this point in the history
Add an option to select the camera by its lens facing (front, back or
external).

PR #4213 <#4213>

Co-authored-by: Romain Vimont <[email protected]>
Signed-off-by: Romain Vimont <[email protected]>
  • Loading branch information
yume-chan and rom1v committed Oct 31, 2023
1 parent 7f8d079 commit faebb7d
Show file tree
Hide file tree
Showing 13 changed files with 160 additions and 7 deletions.
5 changes: 5 additions & 0 deletions app/data/bash-completion/scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ _scrcpy() {
--audio-output-buffer=
-b --video-bit-rate=
--camera-id=
--camera-facing=
--camera-size=
--crop=
-d --select-usb
Expand Down Expand Up @@ -104,6 +105,10 @@ _scrcpy() {
COMPREPLY=($(compgen -W 'output mic' -- "$cur"))
return
;;
--camera-facing)
COMPREPLY=($(compgen -W 'front back external' -- "$cur"))
return
;;
--lock-video-orientation)
COMPREPLY=($(compgen -W 'unlocked initial 0 1 2 3' -- "$cur"))
return
Expand Down
1 change: 1 addition & 0 deletions app/data/zsh-completion/_scrcpy
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ arguments=(
'--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]'
'--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]'
{-d,--select-usb}'[Use USB device]'
Expand Down
6 changes: 6 additions & 0 deletions app/scrcpy.1
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ 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.
Expand Down
50 changes: 49 additions & 1 deletion app/src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ enum {
OPT_LIST_CAMERA_SIZES,
OPT_CAMERA_ID,
OPT_CAMERA_SIZE,
OPT_CAMERA_FACING,
};

struct sc_option {
Expand Down Expand Up @@ -210,6 +211,13 @@ static const struct sc_option options[] = {
"The available camera ids can be listed by:\n"
" 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 = "camera-size",
Expand Down Expand Up @@ -1700,6 +1708,34 @@ parse_audio_source(const char *optarg, enum sc_audio_source *source) {
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
parse_time_limit(const char *s, sc_tick *tick) {
long value;
Expand Down Expand Up @@ -2100,6 +2136,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_CAMERA_SIZE:
opts->camera_size = optarg;
break;
case OPT_CAMERA_FACING:
if (!parse_camera_facing(optarg, &opts->camera_facing)) {
return false;
}
break;
default:
// getopt prints the error message on stderr
return false;
Expand Down Expand Up @@ -2199,6 +2240,11 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
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) {
LOGE("Camera size must be specified by --camera-size");
return false;
Expand All @@ -2208,7 +2254,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
LOGI("Camera video source: control disabled");
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");
return false;
}
Expand Down
1 change: 1 addition & 0 deletions app/src/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const struct scrcpy_options scrcpy_options_default = {
.record_format = SC_RECORD_FORMAT_AUTO,
.keyboard_input_mode = SC_KEYBOARD_INPUT_MODE_INJECT,
.mouse_input_mode = SC_MOUSE_INPUT_MODE_INJECT,
.camera_facing = SC_CAMERA_FACING_ANY,
.port_range = {
.first = DEFAULT_LOCAL_PORT_RANGE_FIRST,
.last = DEFAULT_LOCAL_PORT_RANGE_LAST,
Expand Down
8 changes: 8 additions & 0 deletions app/src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ enum sc_audio_source {
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 {
SC_LOCK_VIDEO_ORIENTATION_UNLOCKED = -1,
// lock the current orientation when scrcpy starts
Expand Down Expand Up @@ -133,6 +140,7 @@ struct scrcpy_options {
enum sc_record_format record_format;
enum sc_keyboard_input_mode keyboard_input_mode;
enum sc_mouse_input_mode mouse_input_mode;
enum sc_camera_facing camera_facing;
struct sc_port_range port_range;
uint32_t tunnel_host;
uint16_t tunnel_port;
Expand Down
1 change: 1 addition & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ scrcpy(struct scrcpy_options *options) {
.audio_codec = options->audio_codec,
.video_source = options->video_source,
.audio_source = options->audio_source,
.camera_facing = options->camera_facing,
.crop = options->crop,
.port_range = options->port_range,
.tunnel_host = options->tunnel_host,
Expand Down
18 changes: 18 additions & 0 deletions app/src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
execute_server(struct sc_server *server,
const struct sc_server_params *params) {
Expand Down Expand Up @@ -285,6 +299,10 @@ execute_server(struct sc_server *server,
if (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) {
ADD_PARAM("show_touches=true");
}
Expand Down
1 change: 1 addition & 0 deletions app/src/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ struct sc_server_params {
enum sc_codec audio_codec;
enum sc_video_source video_source;
enum sc_audio_source audio_source;
enum sc_camera_facing camera_facing;
const char *crop;
const char *video_codec_options;
const char *audio_codec_options;
Expand Down
27 changes: 22 additions & 5 deletions server/src/main/java/com/genymobile/scrcpy/CameraCapture.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import android.annotation.TargetApi;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureFailure;
Expand All @@ -28,6 +29,7 @@
public class CameraCapture extends SurfaceCapture {

private final String explicitCameraId;
private final CameraFacing cameraFacing;
private final Size explicitSize;

private HandlerThread cameraThread;
Expand All @@ -37,8 +39,9 @@ public class CameraCapture extends SurfaceCapture {

private final AtomicBoolean disconnected = new AtomicBoolean();

public CameraCapture(String explicitCameraId, Size explicitSize) {
public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize) {
this.explicitCameraId = explicitCameraId;
this.cameraFacing = cameraFacing;
this.explicitSize = explicitSize;
}

Expand All @@ -50,7 +53,7 @@ public void init() throws IOException {
cameraExecutor = new HandlerExecutor(cameraHandler);

try {
String cameraId = selectCamera(explicitCameraId);
String cameraId = selectCamera(explicitCameraId, cameraFacing);
if (cameraId == null) {
throw new IOException("No matching camera found");
}
Expand All @@ -62,16 +65,30 @@ public void init() throws IOException {
}
}

private static String selectCamera(String explicitCameraId) throws CameraAccessException {
private static String selectCamera(String explicitCameraId, CameraFacing cameraFacing) throws CameraAccessException {
if (explicitCameraId != null) {
return explicitCameraId;
}

CameraManager cameraManager = ServiceManager.getCameraManager();

String[] cameraIds = cameraManager.getCameraIdList();
// Use the first one
return cameraIds.length > 0 ? cameraIds[0] : null;
if (cameraFacing == 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
Expand Down
33 changes: 33 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/CameraFacing.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
14 changes: 14 additions & 0 deletions server/src/main/java/com/genymobile/scrcpy/Options.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class Options {
private int displayId;
private String cameraId;
private Size cameraSize;
private CameraFacing cameraFacing;
private boolean showTouches;
private boolean stayAwake;
private List<CodecOption> videoCodecOptions;
Expand Down Expand Up @@ -126,6 +127,10 @@ public Size getCameraSize() {
return cameraSize;
}

public CameraFacing getCameraFacing() {
return cameraFacing;
}

public boolean getShowTouches() {
return showTouches;
}
Expand Down Expand Up @@ -360,6 +365,15 @@ public static Options parse(String... args) {
options.cameraSize = parseSize(value);
}
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":
options.sendDeviceMeta = Boolean.parseBoolean(value);
break;
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/java/com/genymobile/scrcpy/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc
if (options.getVideoSource() == VideoSource.DISPLAY) {
surfaceCapture = new ScreenCapture(device);
} 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(),
options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError());
Expand Down

0 comments on commit faebb7d

Please sign in to comment.