mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-08-10 10:08:52 +00:00
torch-wip
There is no input manager in camera mode (so the control msg is never sent), so it does not work. We need a specific access to the controller in camera mode.
This commit is contained in:
parent
5a9e8ec7ba
commit
e68e865d65
8 changed files with 99 additions and 6 deletions
|
@ -157,6 +157,9 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) {
|
||||||
sc_write16be(&buf[3], msg->uhid_input.size);
|
sc_write16be(&buf[3], msg->uhid_input.size);
|
||||||
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
memcpy(&buf[5], msg->uhid_input.data, msg->uhid_input.size);
|
||||||
return 5 + msg->uhid_input.size;
|
return 5 + msg->uhid_input.size;
|
||||||
|
case SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH:
|
||||||
|
buf[1] = msg->set_camera_torch.enabled ? 1 : 0;
|
||||||
|
return 2;
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
case SC_CONTROL_MSG_TYPE_EXPAND_NOTIFICATION_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
case SC_CONTROL_MSG_TYPE_EXPAND_SETTINGS_PANEL:
|
||||||
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS:
|
||||||
|
@ -274,6 +277,10 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
|
||||||
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
LOG_CMSG("open hard keyboard settings");
|
LOG_CMSG("open hard keyboard settings");
|
||||||
break;
|
break;
|
||||||
|
case SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH:
|
||||||
|
LOG_CMSG("%s camera torch",
|
||||||
|
msg->set_camera_torch.enabled ? "enable" : "disable");
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
LOG_CMSG("unknown type: %u", (unsigned) msg->type);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -41,6 +41,7 @@ enum sc_control_msg_type {
|
||||||
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
SC_CONTROL_MSG_TYPE_UHID_CREATE,
|
||||||
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
SC_CONTROL_MSG_TYPE_UHID_INPUT,
|
||||||
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
|
||||||
|
SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum sc_screen_power_mode {
|
enum sc_screen_power_mode {
|
||||||
|
@ -106,6 +107,9 @@ struct sc_control_msg {
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
uint8_t data[SC_HID_MAX_SIZE];
|
uint8_t data[SC_HID_MAX_SIZE];
|
||||||
} uhid_input;
|
} uhid_input;
|
||||||
|
struct {
|
||||||
|
bool enabled;
|
||||||
|
} set_camera_torch;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -330,6 +330,19 @@ open_hard_keyboard_settings(struct sc_input_manager *im) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
set_camera_torch(struct sc_input_manager *im, bool enabled) {
|
||||||
|
assert(im->controller);
|
||||||
|
|
||||||
|
struct sc_control_msg msg;
|
||||||
|
msg.type = SC_CONTROL_MSG_TYPE_SET_CAMERA_TORCH;
|
||||||
|
msg.set_camera_torch.enabled = enabled;
|
||||||
|
|
||||||
|
if (!sc_controller_push_msg(im->controller, &msg)) {
|
||||||
|
LOGW("Could not request setting camera torch");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
apply_orientation_transform(struct sc_input_manager *im,
|
apply_orientation_transform(struct sc_input_manager *im,
|
||||||
enum sc_orientation transform) {
|
enum sc_orientation transform) {
|
||||||
|
@ -569,6 +582,11 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||||
open_hard_keyboard_settings(im);
|
open_hard_keyboard_settings(im);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
case SDLK_l:
|
||||||
|
if (control && !repeat && down) {
|
||||||
|
set_camera_torch(im, !shift);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -53,6 +53,8 @@ public class CameraCapture extends SurfaceCapture {
|
||||||
|
|
||||||
private final AtomicBoolean disconnected = new AtomicBoolean();
|
private final AtomicBoolean disconnected = new AtomicBoolean();
|
||||||
|
|
||||||
|
private CameraCaptureSession currentSession;
|
||||||
|
|
||||||
public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps,
|
public CameraCapture(String explicitCameraId, CameraFacing cameraFacing, Size explicitSize, int maxSize, CameraAspectRatio aspectRatio, int fps,
|
||||||
boolean highSpeed) {
|
boolean highSpeed) {
|
||||||
this.explicitCameraId = explicitCameraId;
|
this.explicitCameraId = explicitCameraId;
|
||||||
|
@ -196,7 +198,22 @@ public class CameraCapture extends SurfaceCapture {
|
||||||
public void start(Surface surface) throws IOException {
|
public void start(Surface surface) throws IOException {
|
||||||
try {
|
try {
|
||||||
CameraCaptureSession session = createCaptureSession(cameraDevice, surface);
|
CameraCaptureSession session = createCaptureSession(cameraDevice, surface);
|
||||||
CaptureRequest request = createCaptureRequest(surface);
|
CaptureRequest request = createCaptureRequest(surface, false);
|
||||||
|
setRepeatingRequest(session, request);
|
||||||
|
setCurrentSession(session);
|
||||||
|
} catch (CameraAccessException | InterruptedException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
public void setTorchEnabled(boolean enabled) throws IOException {
|
||||||
|
CameraCaptureSession session = getCurrentSession();
|
||||||
|
if (session == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
CaptureRequest request = createCaptureRequest(session.getInputSurface(), enabled);
|
||||||
setRepeatingRequest(session, request);
|
setRepeatingRequest(session, request);
|
||||||
} catch (CameraAccessException | InterruptedException e) {
|
} catch (CameraAccessException | InterruptedException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
|
@ -310,9 +327,12 @@ public class CameraCapture extends SurfaceCapture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException {
|
private CaptureRequest createCaptureRequest(Surface surface, boolean torch) throws CameraAccessException {
|
||||||
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
|
||||||
requestBuilder.addTarget(surface);
|
requestBuilder.addTarget(surface);
|
||||||
|
if (torch) {
|
||||||
|
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
|
||||||
|
}
|
||||||
|
|
||||||
if (fps > 0) {
|
if (fps > 0) {
|
||||||
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps));
|
requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps));
|
||||||
|
@ -348,4 +368,12 @@ public class CameraCapture extends SurfaceCapture {
|
||||||
public boolean isClosed() {
|
public boolean isClosed() {
|
||||||
return disconnected.get();
|
return disconnected.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private synchronized void setCurrentSession(CameraCaptureSession session) {
|
||||||
|
currentSession = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized CameraCaptureSession getCurrentSession() {
|
||||||
|
return currentSession;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ public final class ControlMessage {
|
||||||
public static final int TYPE_UHID_CREATE = 12;
|
public static final int TYPE_UHID_CREATE = 12;
|
||||||
public static final int TYPE_UHID_INPUT = 13;
|
public static final int TYPE_UHID_INPUT = 13;
|
||||||
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14;
|
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 14;
|
||||||
|
public static final int TYPE_SET_CAMERA_TORCH = 15;
|
||||||
|
|
||||||
public static final long SEQUENCE_INVALID = 0;
|
public static final long SEQUENCE_INVALID = 0;
|
||||||
|
|
||||||
|
@ -45,6 +46,7 @@ public final class ControlMessage {
|
||||||
private long sequence;
|
private long sequence;
|
||||||
private int id;
|
private int id;
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
|
private boolean enabled;
|
||||||
|
|
||||||
private ControlMessage() {
|
private ControlMessage() {
|
||||||
}
|
}
|
||||||
|
@ -144,6 +146,13 @@ public final class ControlMessage {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ControlMessage createSetCameraTorch(boolean enabled) {
|
||||||
|
ControlMessage msg = new ControlMessage();
|
||||||
|
msg.type = TYPE_SET_CAMERA_TORCH;
|
||||||
|
msg.enabled = enabled;
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
@ -215,4 +224,8 @@ public final class ControlMessage {
|
||||||
public byte[] getData() {
|
public byte[] getData() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class ControlMessageReader {
|
||||||
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
static final int SET_CLIPBOARD_FIXED_PAYLOAD_LENGTH = 9;
|
||||||
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
static final int UHID_CREATE_FIXED_PAYLOAD_LENGTH = 4;
|
||||||
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
static final int UHID_INPUT_FIXED_PAYLOAD_LENGTH = 4;
|
||||||
|
static final int CAMERA_TORCH_PAYLOAD_LENGTH = 1;
|
||||||
|
|
||||||
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
private static final int MESSAGE_MAX_SIZE = 1 << 18; // 256k
|
||||||
|
|
||||||
|
@ -95,6 +96,9 @@ public class ControlMessageReader {
|
||||||
case ControlMessage.TYPE_UHID_INPUT:
|
case ControlMessage.TYPE_UHID_INPUT:
|
||||||
msg = parseUhidInput();
|
msg = parseUhidInput();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_SET_CAMERA_TORCH:
|
||||||
|
msg = parseCameraTorch();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown event type: " + type);
|
Ln.w("Unknown event type: " + type);
|
||||||
msg = null;
|
msg = null;
|
||||||
|
@ -245,6 +249,14 @@ public class ControlMessageReader {
|
||||||
return ControlMessage.createUhidInput(id, data);
|
return ControlMessage.createUhidInput(id, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ControlMessage parseCameraTorch() {
|
||||||
|
if (buffer.remaining() < CAMERA_TORCH_PAYLOAD_LENGTH) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
boolean enabled = buffer.get() != 0;
|
||||||
|
return ControlMessage.createSetCameraTorch(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
private static Position readPosition(ByteBuffer buffer) {
|
private static Position readPosition(ByteBuffer buffer) {
|
||||||
int x = buffer.getInt();
|
int x = buffer.getInt();
|
||||||
int y = buffer.getInt();
|
int y = buffer.getInt();
|
||||||
|
|
|
@ -32,6 +32,7 @@ public class Controller implements AsyncProcessor {
|
||||||
|
|
||||||
private final Device device;
|
private final Device device;
|
||||||
private final ControlChannel controlChannel;
|
private final ControlChannel controlChannel;
|
||||||
|
private final CameraCapture cameraCapture;
|
||||||
private final CleanUp cleanUp;
|
private final CleanUp cleanUp;
|
||||||
private final DeviceMessageSender sender;
|
private final DeviceMessageSender sender;
|
||||||
private final boolean clipboardAutosync;
|
private final boolean clipboardAutosync;
|
||||||
|
@ -46,9 +47,11 @@ public class Controller implements AsyncProcessor {
|
||||||
|
|
||||||
private boolean keepPowerModeOff;
|
private boolean keepPowerModeOff;
|
||||||
|
|
||||||
public Controller(Device device, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
|
public Controller(Device device, ControlChannel controlChannel, CameraCapture cameraCapture, CleanUp cleanUp, boolean clipboardAutosync,
|
||||||
|
boolean powerOn) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
this.controlChannel = controlChannel;
|
this.controlChannel = controlChannel;
|
||||||
|
this.cameraCapture = cameraCapture;
|
||||||
this.cleanUp = cleanUp;
|
this.cleanUp = cleanUp;
|
||||||
this.clipboardAutosync = clipboardAutosync;
|
this.clipboardAutosync = clipboardAutosync;
|
||||||
this.powerOn = powerOn;
|
this.powerOn = powerOn;
|
||||||
|
@ -145,7 +148,7 @@ public class Controller implements AsyncProcessor {
|
||||||
// this is expected on close
|
// this is expected on close
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
Ln.i("==== msg = " + msg.getType());
|
||||||
switch (msg.getType()) {
|
switch (msg.getType()) {
|
||||||
case ControlMessage.TYPE_INJECT_KEYCODE:
|
case ControlMessage.TYPE_INJECT_KEYCODE:
|
||||||
if (device.supportsInputEvents()) {
|
if (device.supportsInputEvents()) {
|
||||||
|
@ -213,6 +216,10 @@ public class Controller implements AsyncProcessor {
|
||||||
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
|
||||||
openHardKeyboardSettings();
|
openHardKeyboardSettings();
|
||||||
break;
|
break;
|
||||||
|
case ControlMessage.TYPE_SET_CAMERA_TORCH:
|
||||||
|
Ln.i("===");
|
||||||
|
cameraCapture.setTorchEnabled(msg.getEnabled());
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.genymobile.scrcpy;
|
||||||
|
|
||||||
import android.os.BatteryManager;
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -144,6 +145,7 @@ public final class Server {
|
||||||
asyncProcessors.add(audioRecorder);
|
asyncProcessors.add(audioRecorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CameraCapture cameraCapture = null;
|
||||||
if (video) {
|
if (video) {
|
||||||
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
|
Streamer videoStreamer = new Streamer(connection.getVideoFd(), options.getVideoCodec(), options.getSendCodecMeta(),
|
||||||
options.getSendFrameMeta());
|
options.getSendFrameMeta());
|
||||||
|
@ -151,8 +153,9 @@ 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.getCameraFacing(), options.getCameraSize(),
|
cameraCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
|
||||||
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());
|
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());
|
||||||
|
surfaceCapture = cameraCapture;
|
||||||
}
|
}
|
||||||
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());
|
||||||
|
@ -161,7 +164,8 @@ public final class Server {
|
||||||
|
|
||||||
if (control) {
|
if (control) {
|
||||||
ControlChannel controlChannel = connection.getControlChannel();
|
ControlChannel controlChannel = connection.getControlChannel();
|
||||||
Controller controller = new Controller(device, controlChannel, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
Controller controller = new Controller(
|
||||||
|
device, controlChannel, cameraCapture, cleanUp, options.getClipboardAutosync(), options.getPowerOn());
|
||||||
device.setClipboardListener(text -> {
|
device.setClipboardListener(text -> {
|
||||||
DeviceMessage msg = DeviceMessage.createClipboard(text);
|
DeviceMessage msg = DeviceMessage.createClipboard(text);
|
||||||
controller.getSender().send(msg);
|
controller.getSender().send(msg);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue