From 5bba202f014224b221e73567eae91dcdc278d02c Mon Sep 17 00:00:00 2001 From: Simon Chan <1330321+yume-chan@users.noreply.github.com> Date: Sun, 16 Jul 2023 17:07:19 +0800 Subject: [PATCH] Add --list-cameras Add an option to list the device cameras. Co-authored-by: Romain Vimont --- app/data/bash-completion/scrcpy | 1 + app/data/zsh-completion/_scrcpy | 1 + app/scrcpy.1 | 3 ++ app/src/cli.c | 9 ++++ app/src/options.c | 1 + app/src/options.h | 1 + app/src/scrcpy.c | 4 +- app/src/server.c | 6 ++- app/src/server.h | 1 + .../java/com/genymobile/scrcpy/LogUtils.java | 50 +++++++++++++++++++ .../java/com/genymobile/scrcpy/Options.java | 8 +++ .../java/com/genymobile/scrcpy/Server.java | 10 +++- .../com/genymobile/scrcpy/Workarounds.java | 7 ++- .../scrcpy/wrappers/ServiceManager.java | 19 +++++++ 14 files changed, 115 insertions(+), 6 deletions(-) diff --git a/app/data/bash-completion/scrcpy b/app/data/bash-completion/scrcpy index 003f9d73..cfa04847 100644 --- a/app/data/bash-completion/scrcpy +++ b/app/data/bash-completion/scrcpy @@ -23,6 +23,7 @@ _scrcpy() { --kill-adb-on-close -K --hid-keyboard --legacy-paste + --list-cameras --list-displays --list-encoders --lock-video-orientation diff --git a/app/data/zsh-completion/_scrcpy b/app/data/zsh-completion/_scrcpy index 81142851..06872524 100644 --- a/app/data/zsh-completion/_scrcpy +++ b/app/data/zsh-completion/_scrcpy @@ -30,6 +30,7 @@ arguments=( '--kill-adb-on-close[Kill adb when scrcpy terminates]' {-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]' + '--list-cameras[List cameras available on the device]' '--list-displays[List displays 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)' diff --git a/app/scrcpy.1 b/app/scrcpy.1 index c1378d8b..43d94daa 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -155,6 +155,9 @@ 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. +.B \-\-list\-cameras +List cameras available on the device. + .TP .B \-\-list\-encoders List video and audio encoders available on the device. diff --git a/app/src/cli.c b/app/src/cli.c index 09f853f5..78e8833e 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -79,6 +79,7 @@ enum { OPT_AUDIO_SOURCE, OPT_KILL_ADB_ON_CLOSE, OPT_TIME_LIMIT, + OPT_LIST_CAMERAS, }; struct sc_option { @@ -312,6 +313,11 @@ static const struct sc_option options[] = { "This is a workaround for some devices not behaving as " "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 = "list-displays", @@ -1944,6 +1950,9 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], "platform)."); return false; #endif + case OPT_LIST_CAMERAS: + opts->list_cameras = true; + break; case OPT_LIST_ENCODERS: opts->list_encoders = true; break; diff --git a/app/src/options.c b/app/src/options.c index 530e003b..99285cab 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -81,5 +81,6 @@ const struct scrcpy_options scrcpy_options_default = { .require_audio = false, .list_encoders = false, .list_displays = false, + .list_cameras = false, .kill_adb_on_close = false, }; diff --git a/app/src/options.h b/app/src/options.h index 1f36ad7f..0087f0a7 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -181,6 +181,7 @@ struct scrcpy_options { bool require_audio; bool list_encoders; bool list_displays; + bool list_cameras; bool kill_adb_on_close; }; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index aabb7c5a..e428cf09 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -381,6 +381,7 @@ scrcpy(struct scrcpy_options *options) { .power_on = options->power_on, .list_encoders = options->list_encoders, .list_displays = options->list_displays, + .list_cameras = options->list_cameras, .kill_adb_on_close = options->kill_adb_on_close, }; @@ -399,7 +400,8 @@ scrcpy(struct scrcpy_options *options) { server_started = true; - if (options->list_encoders || options->list_displays) { + if (options->list_encoders || options->list_displays + || options->list_cameras) { bool ok = await_for_server(NULL); ret = ok ? SCRCPY_EXIT_SUCCESS : SCRCPY_EXIT_FAILURE; goto end; diff --git a/app/src/server.c b/app/src/server.c index 4d787ea9..87977811 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -316,6 +316,9 @@ execute_server(struct sc_server *server, if (params->list_displays) { ADD_PARAM("list_displays=true"); } + if (params->list_cameras) { + ADD_PARAM("list_cameras=true"); + } #undef ADD_PARAM @@ -895,7 +898,8 @@ run_server(void *data) { // If --list-* is passed, then the server just prints the requested data // then exits. - if (params->list_encoders || params->list_displays) { + if (params->list_encoders || params->list_displays + || params->list_cameras) { sc_pid pid = execute_server(server, params); if (pid == SC_PROCESS_NONE) { goto error_connection_failed; diff --git a/app/src/server.h b/app/src/server.h index adba2652..b2b81ee5 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -58,6 +58,7 @@ struct sc_server_params { bool power_on; bool list_encoders; bool list_displays; + bool list_cameras; bool kill_adb_on_close; }; diff --git a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java index 243a156b..1501cab2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/LogUtils.java +++ b/server/src/main/java/com/genymobile/scrcpy/LogUtils.java @@ -3,6 +3,12 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraCharacteristics; +import android.hardware.camera2.CameraManager; +import android.hardware.camera2.params.StreamConfigurationMap; +import android.media.MediaCodec; + import java.util.List; public final class LogUtils { @@ -60,4 +66,48 @@ public final class LogUtils { } 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=").append(id); + CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id); + Integer facingInteger = characteristics.get(CameraCharacteristics.LENS_FACING); + if (facingInteger != null) { + int facing = facingInteger; + builder.append(" (").append(getCameraFacingName(facing)).append(')'); + } + + StreamConfigurationMap configs = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); + android.util.Size[] sizes = configs.getOutputSizes(MediaCodec.class); + for (android.util.Size size : sizes) { + // TODO remove (just for testing) + builder.append("\n - " + size.getWidth() + "x" + size.getHeight()); + } + } + } + } catch (CameraAccessException e) { + builder.append("\n (access denied)"); + } + return builder.toString(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index aab6fce8..4163147a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -38,6 +38,7 @@ public class Options { private boolean listEncoders; private boolean listDisplays; + private boolean listCameras; // Options not used by the scrcpy client, but useful to use scrcpy-server directly private boolean sendDeviceMeta = true; // send device name and size @@ -161,6 +162,10 @@ public class Options { return listDisplays; } + public boolean getListCameras() { + return listCameras; + } + public boolean getSendDeviceMeta() { return sendDeviceMeta; } @@ -306,6 +311,9 @@ public class Options { case "list_displays": options.listDisplays = Boolean.parseBoolean(value); break; + case "list_cameras": + options.listCameras = Boolean.parseBoolean(value); + break; case "send_device_meta": options.sendDeviceMeta = Boolean.parseBoolean(value); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 2ff41e89..74b0dbbb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -2,6 +2,7 @@ package com.genymobile.scrcpy; import android.os.BatteryManager; import android.os.Build; +import android.os.Looper; import java.io.IOException; import java.util.ArrayList; @@ -99,7 +100,8 @@ public final class Server { boolean audio = options.getAudio(); boolean sendDummyByte = options.getSendDummyByte(); - Workarounds.apply(audio); + boolean camera = true; + Workarounds.apply(audio, camera); List asyncProcessors = new ArrayList<>(); @@ -180,7 +182,7 @@ public final class Server { Ln.initLogLevel(options.getLogLevel()); - if (options.getListEncoders() || options.getListDisplays()) { + if (options.getListEncoders() || options.getListDisplays() || options.getListCameras()) { if (options.getCleanup()) { CleanUp.unlinkSelf(); } @@ -192,6 +194,10 @@ public final class Server { if (options.getListDisplays()) { Ln.i(LogUtils.buildDisplayListMessage()); } + if (options.getListCameras()) { + Workarounds.apply(false, true); + Ln.i(LogUtils.buildCameraListMessage()); + } // Just print the requested data, do not mirror return; } diff --git a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java index 74c0202a..b8aa2b7e 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Workarounds.java +++ b/server/src/main/java/com/genymobile/scrcpy/Workarounds.java @@ -28,14 +28,13 @@ public final class Workarounds { // not instantiable } - public static void apply(boolean audio) { + public static void apply(boolean audio, boolean camera) { Workarounds.prepareMainLooper(); boolean mustFillAppInfo = false; boolean mustFillBaseContext = false; boolean mustFillAppContext = false; - if (Build.BRAND.equalsIgnoreCase("meizu")) { // Workarounds must be applied for Meizu phones: // - @@ -65,6 +64,10 @@ public final class Workarounds { mustFillAppContext = true; } + if (camera) { + mustFillAppInfo = true; + } + if (mustFillAppInfo) { Workarounds.fillAppInfo(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java index 69803971..d4056b4b 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/ServiceManager.java @@ -1,9 +1,15 @@ package com.genymobile.scrcpy.wrappers; +import com.genymobile.scrcpy.FakeContext; +import com.genymobile.scrcpy.Workarounds; + import android.annotation.SuppressLint; +import android.content.Context; +import android.hardware.camera2.CameraManager; import android.os.IBinder; import android.os.IInterface; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -26,6 +32,7 @@ public final class ServiceManager { private static StatusBarManager statusBarManager; private static ClipboardManager clipboardManager; private static ActivityManager activityManager; + private static CameraManager cameraManager; private ServiceManager() { /* not instantiable */ @@ -129,4 +136,16 @@ public final class ServiceManager { return activityManager; } + + public static CameraManager getCameraManager() { + if (cameraManager == null) { + try { + Constructor ctor = CameraManager.class.getDeclaredConstructor(Context.class); + cameraManager = ctor.newInstance(FakeContext.get()); + } catch (Exception e) { + throw new AssertionError(e); + } + } + return cameraManager; + } }