mirror of
https://github.com/Genymobile/scrcpy
synced 2025-02-16 12:36:51 +00:00
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>
This commit is contained in:
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…
Reference in New Issue
Block a user