From 3a39d1ba50974e97360c4cc53981aadb03172e19 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 31 Oct 2024 22:47:35 +0100 Subject: [PATCH] Add shortcut to reset video capture/encoding Reset video capture/encoding on MOD+Shift+r. Like on device rotation, this starts a new encoding session which produces a video stream starting by a key frame. PR #5432 --- app/scrcpy.1 | 4 ++++ app/src/cli.c | 4 ++++ app/src/control_msg.c | 4 ++++ app/src/control_msg.h | 1 + app/src/input_manager.c | 20 ++++++++++++++-- app/tests/test_control_msg_serialize.c | 16 +++++++++++++ doc/shortcuts.md | 1 + .../java/com/genymobile/scrcpy/Server.java | 4 ++++ .../scrcpy/control/ControlMessage.java | 1 + .../scrcpy/control/ControlMessageReader.java | 1 + .../genymobile/scrcpy/control/Controller.java | 18 +++++++++++++++ .../scrcpy/video/CameraCapture.java | 5 ++++ .../scrcpy/video/NewDisplayCapture.java | 23 ++++++++++++++----- .../scrcpy/video/ScreenCapture.java | 5 ++++ .../scrcpy/video/SurfaceCapture.java | 7 ++++++ 15 files changed, 106 insertions(+), 8 deletions(-) diff --git a/app/scrcpy.1 b/app/scrcpy.1 index f517ad4c93..76e36dcb12 100644 --- a/app/scrcpy.1 +++ b/app/scrcpy.1 @@ -671,6 +671,10 @@ Pause or re-pause display .B MOD+Shift+z Unpause display +.TP +.B MOD+Shift+r +Reset video capture/encoding + .TP .B MOD+g Resize window to 1:1 (pixel\-perfect) diff --git a/app/src/cli.c b/app/src/cli.c index 77747e93f1..7cc680859b 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -1022,6 +1022,10 @@ static const struct sc_shortcut shortcuts[] = { .shortcuts = { "MOD+Shift+z" }, .text = "Unpause display", }, + { + .shortcuts = { "MOD+Shift+r" }, + .text = "Reset video capture/encoding", + }, { .shortcuts = { "MOD+g" }, .text = "Resize window to 1:1 (pixel-perfect)", diff --git a/app/src/control_msg.c b/app/src/control_msg.c index e04fbd3cf6..0defda9257 100644 --- a/app/src/control_msg.c +++ b/app/src/control_msg.c @@ -181,6 +181,7 @@ sc_control_msg_serialize(const struct sc_control_msg *msg, uint8_t *buf) { case SC_CONTROL_MSG_TYPE_COLLAPSE_PANELS: case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: + case SC_CONTROL_MSG_TYPE_RESET_VIDEO: // no additional data return 1; default: @@ -304,6 +305,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) { case SC_CONTROL_MSG_TYPE_START_APP: LOG_CMSG("start app \"%s\"", msg->start_app.name); break; + case SC_CONTROL_MSG_TYPE_RESET_VIDEO: + LOG_CMSG("reset video"); + break; default: LOG_CMSG("unknown type: %u", (unsigned) msg->type); break; diff --git a/app/src/control_msg.h b/app/src/control_msg.h index 9eef7e827c..f0a2e37346 100644 --- a/app/src/control_msg.h +++ b/app/src/control_msg.h @@ -42,6 +42,7 @@ enum sc_control_msg_type { SC_CONTROL_MSG_TYPE_UHID_DESTROY, SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, SC_CONTROL_MSG_TYPE_START_APP, + SC_CONTROL_MSG_TYPE_RESET_VIDEO, }; enum sc_copy_key { diff --git a/app/src/input_manager.c b/app/src/input_manager.c index 140b50ac6e..3955c211d7 100644 --- a/app/src/input_manager.c +++ b/app/src/input_manager.c @@ -284,6 +284,18 @@ open_hard_keyboard_settings(struct sc_input_manager *im) { } } +static void +reset_video(struct sc_input_manager *im) { + assert(im->controller); + + struct sc_control_msg msg; + msg.type = SC_CONTROL_MSG_TYPE_RESET_VIDEO; + + if (!sc_controller_push_msg(im->controller, &msg)) { + LOGW("Could not request reset video"); + } +} + static void apply_orientation_transform(struct sc_input_manager *im, enum sc_orientation transform) { @@ -521,8 +533,12 @@ sc_input_manager_process_key(struct sc_input_manager *im, } return; case SDLK_r: - if (control && !shift && !repeat && down && !paused) { - rotate_device(im); + if (control && !repeat && down && !paused) { + if (shift) { + reset_video(im); + } else { + rotate_device(im); + } } return; case SDLK_k: diff --git a/app/tests/test_control_msg_serialize.c b/app/tests/test_control_msg_serialize.c index 73bca901f9..9adf2a3d7f 100644 --- a/app/tests/test_control_msg_serialize.c +++ b/app/tests/test_control_msg_serialize.c @@ -407,6 +407,21 @@ static void test_serialize_open_hard_keyboard(void) { assert(!memcmp(buf, expected, sizeof(expected))); } +static void test_serialize_reset_video(void) { + struct sc_control_msg msg = { + .type = SC_CONTROL_MSG_TYPE_RESET_VIDEO, + }; + + uint8_t buf[SC_CONTROL_MSG_MAX_SIZE]; + size_t size = sc_control_msg_serialize(&msg, buf); + assert(size == 1); + + const uint8_t expected[] = { + SC_CONTROL_MSG_TYPE_RESET_VIDEO, + }; + assert(!memcmp(buf, expected, sizeof(expected))); +} + int main(int argc, char *argv[]) { (void) argc; (void) argv; @@ -429,5 +444,6 @@ int main(int argc, char *argv[]) { test_serialize_uhid_input(); test_serialize_uhid_destroy(); test_serialize_open_hard_keyboard(); + test_serialize_reset_video(); return 0; } diff --git a/doc/shortcuts.md b/doc/shortcuts.md index 4ea37257a8..bfaf1c4ef6 100644 --- a/doc/shortcuts.md +++ b/doc/shortcuts.md @@ -30,6 +30,7 @@ _[Super] is typically the Windows or Cmd key._ | Flip display vertically | MOD+Shift+ _(up)_ \| MOD+Shift+ _(down)_ | Pause or re-pause display | MOD+z | Unpause display | MOD+Shift+z + | Reset video capture/encoding | MOD+Shift+r | Resize window to 1:1 (pixel-perfect) | MOD+g | Resize window to remove black borders | MOD+w \| _Double-left-click¹_ | Click on `HOME` | MOD+h \| _Middle-click_ diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index a0a48806c5..e0adeea0be 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -225,6 +225,10 @@ private static void scrcpy(Options options) throws IOException, ConfigurationExc SurfaceEncoder surfaceEncoder = new SurfaceEncoder(surfaceCapture, videoStreamer, options.getVideoBitRate(), options.getMaxFps(), options.getVideoCodecOptions(), options.getVideoEncoder(), options.getDownsizeOnError()); asyncProcessors.add(surfaceEncoder); + + if (controller != null) { + controller.setSurfaceCapture(surfaceCapture); + } } Completion completion = new Completion(asyncProcessors.size()); diff --git a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java index eec5f67fb1..7455cdf836 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessage.java @@ -24,6 +24,7 @@ public final class ControlMessage { public static final int TYPE_UHID_DESTROY = 14; public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15; public static final int TYPE_START_APP = 16; + public static final int TYPE_RESET_VIDEO = 17; public static final long SEQUENCE_INVALID = 0; diff --git a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java index ae1676906e..b82615ede8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/ControlMessageReader.java @@ -46,6 +46,7 @@ public ControlMessage read() throws IOException { case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_ROTATE_DEVICE: case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: + case ControlMessage.TYPE_RESET_VIDEO: return ControlMessage.createEmpty(type); case ControlMessage.TYPE_UHID_CREATE: return parseUhidCreate(); diff --git a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java index 67a0115de1..b4ae07b6fa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/control/Controller.java +++ b/server/src/main/java/com/genymobile/scrcpy/control/Controller.java @@ -9,6 +9,7 @@ import com.genymobile.scrcpy.device.Position; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.LogUtils; +import com.genymobile.scrcpy.video.SurfaceCapture; import com.genymobile.scrcpy.video.VirtualDisplayListener; import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.InputManager; @@ -93,6 +94,9 @@ private DisplayData(int virtualDisplayId, PositionMapper positionMapper) { private boolean keepDisplayPowerOff; + // Used for resetting video encoding on RESET_VIDEO message + private SurfaceCapture surfaceCapture; + public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) { this.displayId = displayId; this.controlChannel = controlChannel; @@ -143,6 +147,10 @@ public void onNewVirtualDisplay(int virtualDisplayId, PositionMapper positionMap } } + public void setSurfaceCapture(SurfaceCapture surfaceCapture) { + this.surfaceCapture = surfaceCapture; + } + private UhidManager getUhidManager() { if (uhidManager == null) { uhidManager = new UhidManager(sender); @@ -293,6 +301,9 @@ private boolean handleEvent() throws IOException { case ControlMessage.TYPE_START_APP: startAppAsync(msg.getText()); break; + case ControlMessage.TYPE_RESET_VIDEO: + resetVideo(); + break; default: // do nothing } @@ -680,4 +691,11 @@ private void setDisplayPower(boolean on) { } } } + + private void resetVideo() { + if (surfaceCapture != null) { + Ln.i("Video capture reset"); + surfaceCapture.requestInvalidate(); + } + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java index 92663f79b7..7385283e34 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java @@ -355,4 +355,9 @@ public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request public boolean isClosed() { return disconnected.get(); } + + @Override + public void requestInvalidate() { + // do nothing (the user could not request a reset anyway for now, since there is no controller for camera mirroring) + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java index 5d61c4bd9f..f6561a5f1a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/NewDisplayCapture.java @@ -14,6 +14,8 @@ import android.os.Build; import android.view.Surface; +import java.io.IOException; + public class NewDisplayCapture extends SurfaceCapture { // Internal fields copied from android.hardware.display.DisplayManager @@ -72,13 +74,8 @@ public void prepare() { } } - @Override - public void start(Surface surface) { - if (virtualDisplay != null) { - virtualDisplay.release(); - virtualDisplay = null; - } + public void startNew(Surface surface) { int virtualDisplayId; try { int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC @@ -113,6 +110,15 @@ public void start(Surface surface) { } } + @Override + public void start(Surface surface) throws IOException { + if (virtualDisplay == null) { + startNew(surface); + } else { + virtualDisplay.setSurface(surface); + } + } + @Override public void release() { if (virtualDisplay != null) { @@ -142,4 +148,9 @@ private static int scaleDpi(Size initialSize, int initialDpi, Size size) { int num = size.getMax(); return initialDpi * num / den; } + + @Override + public void requestInvalidate() { + invalidate(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index c0d49f60dd..48e594b777 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -291,4 +291,9 @@ private void unregisterDisplayListenerFallbacks() { } } } + + @Override + public void requestInvalidate() { + invalidate(); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java index 172bd78f03..de9e1b275d 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java @@ -79,4 +79,11 @@ public void prepare() throws ConfigurationException { public boolean isClosed() { return false; } + + /** + * Manually request to invalidate (typically a user request). + *

+ * The capture implementation is free to ignore the request and do nothing. + */ + public abstract void requestInvalidate(); }