Add --list-cameras

Add an option to list the device cameras.

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>
camera.37
Simon Chan 11 months ago committed by Romain Vimont
parent f085765e04
commit cd63896d63

@ -23,6 +23,7 @@ _scrcpy() {
--kill-adb-on-close --kill-adb-on-close
-K --hid-keyboard -K --hid-keyboard
--legacy-paste --legacy-paste
--list-cameras
--list-displays --list-displays
--list-encoders --list-encoders
--lock-video-orientation --lock-video-orientation

@ -30,6 +30,7 @@ arguments=(
'--kill-adb-on-close[Kill adb when scrcpy terminates]' '--kill-adb-on-close[Kill adb when scrcpy terminates]'
{-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]' {-K,--hid-keyboard}'[Simulate a physical keyboard by using HID over AOAv2]'
'--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]' '--legacy-paste[Inject computer clipboard text as a sequence of key events on Ctrl+v]'
'--list-cameras[List cameras available on the device]'
'--list-displays[List displays available on the device]' '--list-displays[List displays available on the device]'
'--list-encoders[List video and audio encoders available on the device]' '--list-encoders[List video and audio encoders available on the device]'
'--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)' '--lock-video-orientation=[Lock video orientation]:orientation:(unlocked initial 0 1 2 3)'

@ -155,6 +155,10 @@ Inject computer clipboard text as a sequence of key events on Ctrl+v (like MOD+S
This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically. This is a workaround for some devices not behaving as expected when setting the device clipboard programmatically.
.TP
.B \-\-list\-cameras
List cameras available on the device.
.TP .TP
.B \-\-list\-encoders .B \-\-list\-encoders
List video and audio encoders available on the device. List video and audio encoders available on the device.

@ -81,6 +81,7 @@ enum {
OPT_KILL_ADB_ON_CLOSE, OPT_KILL_ADB_ON_CLOSE,
OPT_TIME_LIMIT, OPT_TIME_LIMIT,
OPT_PAUSE_ON_EXIT, OPT_PAUSE_ON_EXIT,
OPT_LIST_CAMERAS,
}; };
struct sc_option { struct sc_option {
@ -320,6 +321,11 @@ static const struct sc_option options[] = {
"This is a workaround for some devices not behaving as " "This is a workaround for some devices not behaving as "
"expected when setting the device clipboard programmatically.", "expected when setting the device clipboard programmatically.",
}, },
{
.longopt_id = OPT_LIST_CAMERAS,
.longopt = "list-cameras",
.text = "List device cameras.",
},
{ {
.longopt_id = OPT_LIST_DISPLAYS, .longopt_id = OPT_LIST_DISPLAYS,
.longopt = "list-displays", .longopt = "list-displays",
@ -1998,6 +2004,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
case OPT_LIST_DISPLAYS: case OPT_LIST_DISPLAYS:
opts->list |= SC_OPTION_LIST_DISPLAYS; opts->list |= SC_OPTION_LIST_DISPLAYS;
break; break;
case OPT_LIST_CAMERAS:
opts->list |= SC_OPTION_LIST_CAMERAS;
break;
case OPT_REQUIRE_AUDIO: case OPT_REQUIRE_AUDIO:
opts->require_audio = true; opts->require_audio = true;
break; break;

@ -182,6 +182,7 @@ struct scrcpy_options {
bool kill_adb_on_close; bool kill_adb_on_close;
#define SC_OPTION_LIST_ENCODERS 0x1 #define SC_OPTION_LIST_ENCODERS 0x1
#define SC_OPTION_LIST_DISPLAYS 0x2 #define SC_OPTION_LIST_DISPLAYS 0x2
#define SC_OPTION_LIST_CAMERAS 0x4
uint8_t list; uint8_t list;
}; };

@ -317,6 +317,9 @@ execute_server(struct sc_server *server,
if (params->list & SC_OPTION_LIST_DISPLAYS) { if (params->list & SC_OPTION_LIST_DISPLAYS) {
ADD_PARAM("list_displays=true"); ADD_PARAM("list_displays=true");
} }
if (params->list & SC_OPTION_LIST_CAMERAS) {
ADD_PARAM("list_cameras=true");
}
#undef ADD_PARAM #undef ADD_PARAM

@ -3,6 +3,11 @@ package com.genymobile.scrcpy;
import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.graphics.Rect;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import java.util.List; import java.util.List;
public final class LogUtils { public final class LogUtils {
@ -60,4 +65,42 @@ public final class LogUtils {
} }
return builder.toString(); return builder.toString();
} }
private static String getCameraFacingName(int facing) {
switch (facing) {
case CameraCharacteristics.LENS_FACING_FRONT:
return "front";
case CameraCharacteristics.LENS_FACING_BACK:
return "back";
case CameraCharacteristics.LENS_FACING_EXTERNAL:
return "external";
default:
return "unknown";
}
}
public static String buildCameraListMessage() {
StringBuilder builder = new StringBuilder("List of cameras:");
CameraManager cameraManager = ServiceManager.getCameraManager();
try {
String[] cameraIds = cameraManager.getCameraIdList();
if (cameraIds == null || cameraIds.length == 0) {
builder.append("\n (none)");
} else {
for (String id : cameraIds) {
builder.append("\n --video-source=camera --camera-id=").append(id);
CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
int facing = characteristics.get(CameraCharacteristics.LENS_FACING);
builder.append(" (").append(getCameraFacingName(facing)).append(", ");
Rect activeSize = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
builder.append(activeSize.width()).append("x").append(activeSize.height()).append(')');
}
}
} catch (CameraAccessException e) {
builder.append("\n (access denied)");
}
return builder.toString();
}
} }

@ -38,6 +38,7 @@ public class Options {
private boolean listEncoders; private boolean listEncoders;
private boolean listDisplays; private boolean listDisplays;
private boolean listCameras;
// Options not used by the scrcpy client, but useful to use scrcpy-server directly // Options not used by the scrcpy client, but useful to use scrcpy-server directly
private boolean sendDeviceMeta = true; // send device name and size private boolean sendDeviceMeta = true; // send device name and size
@ -154,7 +155,7 @@ public class Options {
} }
public boolean getList() { public boolean getList() {
return listEncoders || listDisplays; return listEncoders || listDisplays || listCameras;
} }
public boolean getListEncoders() { public boolean getListEncoders() {
@ -165,6 +166,10 @@ public class Options {
return listDisplays; return listDisplays;
} }
public boolean getListCameras() {
return listCameras;
}
public boolean getSendDeviceMeta() { public boolean getSendDeviceMeta() {
return sendDeviceMeta; return sendDeviceMeta;
} }
@ -312,6 +317,9 @@ public class Options {
case "list_displays": case "list_displays":
options.listDisplays = Boolean.parseBoolean(value); options.listDisplays = Boolean.parseBoolean(value);
break; break;
case "list_cameras":
options.listCameras = Boolean.parseBoolean(value);
break;
case "send_device_meta": case "send_device_meta":
options.sendDeviceMeta = Boolean.parseBoolean(value); options.sendDeviceMeta = Boolean.parseBoolean(value);
break; break;

@ -98,8 +98,9 @@ public final class Server {
boolean video = options.getVideo(); boolean video = options.getVideo();
boolean audio = options.getAudio(); boolean audio = options.getAudio();
boolean sendDummyByte = options.getSendDummyByte(); boolean sendDummyByte = options.getSendDummyByte();
boolean camera = false;
Workarounds.apply(audio); Workarounds.apply(audio, camera);
List<AsyncProcessor> asyncProcessors = new ArrayList<>(); List<AsyncProcessor> asyncProcessors = new ArrayList<>();
@ -207,6 +208,10 @@ public final class Server {
if (options.getListDisplays()) { if (options.getListDisplays()) {
Ln.i(LogUtils.buildDisplayListMessage()); Ln.i(LogUtils.buildDisplayListMessage());
} }
if (options.getListCameras()) {
Workarounds.apply(false, true);
Ln.i(LogUtils.buildCameraListMessage());
}
// Just print the requested data, do not mirror // Just print the requested data, do not mirror
return; return;
} }

@ -28,14 +28,13 @@ public final class Workarounds {
// not instantiable // not instantiable
} }
public static void apply(boolean audio) { public static void apply(boolean audio, boolean camera) {
Workarounds.prepareMainLooper(); Workarounds.prepareMainLooper();
boolean mustFillAppInfo = false; boolean mustFillAppInfo = false;
boolean mustFillBaseContext = false; boolean mustFillBaseContext = false;
boolean mustFillAppContext = false; boolean mustFillAppContext = false;
if (Build.BRAND.equalsIgnoreCase("meizu")) { if (Build.BRAND.equalsIgnoreCase("meizu")) {
// Workarounds must be applied for Meizu phones: // Workarounds must be applied for Meizu phones:
// - <https://github.com/Genymobile/scrcpy/issues/240> // - <https://github.com/Genymobile/scrcpy/issues/240>
@ -65,6 +64,11 @@ public final class Workarounds {
mustFillAppContext = true; mustFillAppContext = true;
} }
if (camera) {
mustFillAppInfo = true;
mustFillBaseContext = true;
}
if (mustFillAppInfo) { if (mustFillAppInfo) {
Workarounds.fillAppInfo(); Workarounds.fillAppInfo();
} }

@ -1,9 +1,14 @@
package com.genymobile.scrcpy.wrappers; package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.FakeContext;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.camera2.CameraManager;
import android.os.IBinder; import android.os.IBinder;
import android.os.IInterface; import android.os.IInterface;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -26,6 +31,7 @@ public final class ServiceManager {
private static StatusBarManager statusBarManager; private static StatusBarManager statusBarManager;
private static ClipboardManager clipboardManager; private static ClipboardManager clipboardManager;
private static ActivityManager activityManager; private static ActivityManager activityManager;
private static CameraManager cameraManager;
private ServiceManager() { private ServiceManager() {
/* not instantiable */ /* not instantiable */
@ -129,4 +135,16 @@ public final class ServiceManager {
return activityManager; return activityManager;
} }
public static CameraManager getCameraManager() {
if (cameraManager == null) {
try {
Constructor<CameraManager> ctor = CameraManager.class.getDeclaredConstructor(Context.class);
cameraManager = ctor.newInstance(FakeContext.get());
} catch (Exception e) {
throw new AssertionError(e);
}
}
return cameraManager;
}
} }

Loading…
Cancel
Save