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:
Romain Vimont 2024-02-27 21:22:39 +01:00
commit e68e865d65
8 changed files with 99 additions and 6 deletions

View file

@ -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;

View file

@ -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;
}; };
}; };

View file

@ -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;

View file

@ -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;
}
} }

View file

@ -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;
}
} }

View file

@ -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();

View file

@ -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
} }

View file

@ -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);