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 <https://github.com/Genymobile/scrcpy/pull/5432>
This commit is contained in:
Romain Vimont 2024-10-31 22:47:35 +01:00
parent 9958302e6f
commit 104195fc3b
15 changed files with 106 additions and 8 deletions

View File

@ -671,6 +671,10 @@ Pause or re-pause display
.B MOD+Shift+z .B MOD+Shift+z
Unpause display Unpause display
.TP
.B MOD+Shift+r
Reset video capture/encoding
.TP .TP
.B MOD+g .B MOD+g
Resize window to 1:1 (pixel\-perfect) Resize window to 1:1 (pixel\-perfect)

View File

@ -1022,6 +1022,10 @@ static const struct sc_shortcut shortcuts[] = {
.shortcuts = { "MOD+Shift+z" }, .shortcuts = { "MOD+Shift+z" },
.text = "Unpause display", .text = "Unpause display",
}, },
{
.shortcuts = { "MOD+Shift+r" },
.text = "Reset video capture/encoding",
},
{ {
.shortcuts = { "MOD+g" }, .shortcuts = { "MOD+g" },
.text = "Resize window to 1:1 (pixel-perfect)", .text = "Resize window to 1:1 (pixel-perfect)",

View File

@ -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_COLLAPSE_PANELS:
case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE: case SC_CONTROL_MSG_TYPE_ROTATE_DEVICE:
case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS: case SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
// no additional data // no additional data
return 1; return 1;
default: default:
@ -304,6 +305,9 @@ sc_control_msg_log(const struct sc_control_msg *msg) {
case SC_CONTROL_MSG_TYPE_START_APP: case SC_CONTROL_MSG_TYPE_START_APP:
LOG_CMSG("start app \"%s\"", msg->start_app.name); LOG_CMSG("start app \"%s\"", msg->start_app.name);
break; break;
case SC_CONTROL_MSG_TYPE_RESET_VIDEO:
LOG_CMSG("reset video");
break;
default: default:
LOG_CMSG("unknown type: %u", (unsigned) msg->type); LOG_CMSG("unknown type: %u", (unsigned) msg->type);
break; break;

View File

@ -42,6 +42,7 @@ enum sc_control_msg_type {
SC_CONTROL_MSG_TYPE_UHID_DESTROY, SC_CONTROL_MSG_TYPE_UHID_DESTROY,
SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS, SC_CONTROL_MSG_TYPE_OPEN_HARD_KEYBOARD_SETTINGS,
SC_CONTROL_MSG_TYPE_START_APP, SC_CONTROL_MSG_TYPE_START_APP,
SC_CONTROL_MSG_TYPE_RESET_VIDEO,
}; };
enum sc_copy_key { enum sc_copy_key {

View File

@ -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 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) {
@ -521,9 +533,13 @@ sc_input_manager_process_key(struct sc_input_manager *im,
} }
return; return;
case SDLK_r: case SDLK_r:
if (control && !shift && !repeat && down && !paused) { if (control && !repeat && down && !paused) {
if (shift) {
reset_video(im);
} else {
rotate_device(im); rotate_device(im);
} }
}
return; return;
case SDLK_k: case SDLK_k:
if (control && !shift && !repeat && down && !paused if (control && !shift && !repeat && down && !paused

View File

@ -407,6 +407,21 @@ static void test_serialize_open_hard_keyboard(void) {
assert(!memcmp(buf, expected, sizeof(expected))); 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[]) { int main(int argc, char *argv[]) {
(void) argc; (void) argc;
(void) argv; (void) argv;
@ -429,5 +444,6 @@ int main(int argc, char *argv[]) {
test_serialize_uhid_input(); test_serialize_uhid_input();
test_serialize_uhid_destroy(); test_serialize_uhid_destroy();
test_serialize_open_hard_keyboard(); test_serialize_open_hard_keyboard();
test_serialize_reset_video();
return 0; return 0;
} }

View File

@ -30,6 +30,7 @@ _<kbd>[Super]</kbd> is typically the <kbd>Windows</kbd> or <kbd>Cmd</kbd> key._
| Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(down)_ | Flip display vertically | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(up)_ \| <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd></kbd> _(down)_
| Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd> | Pause or re-pause display | <kbd>MOD</kbd>+<kbd>z</kbd>
| Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd> | Unpause display | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>z</kbd>
| Reset video capture/encoding | <kbd>MOD</kbd>+<kbd>Shift</kbd>+<kbd>r</kbd>
| Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd> | Resize window to 1:1 (pixel-perfect) | <kbd>MOD</kbd>+<kbd>g</kbd>
| Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_ | Resize window to remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_ | Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_

View File

@ -225,6 +225,10 @@ public final class Server {
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());
asyncProcessors.add(surfaceEncoder); asyncProcessors.add(surfaceEncoder);
if (controller != null) {
controller.setSurfaceCapture(surfaceCapture);
}
} }
Completion completion = new Completion(asyncProcessors.size()); Completion completion = new Completion(asyncProcessors.size());

View File

@ -24,6 +24,7 @@ public final class ControlMessage {
public static final int TYPE_UHID_DESTROY = 14; public static final int TYPE_UHID_DESTROY = 14;
public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15; public static final int TYPE_OPEN_HARD_KEYBOARD_SETTINGS = 15;
public static final int TYPE_START_APP = 16; public static final int TYPE_START_APP = 16;
public static final int TYPE_RESET_VIDEO = 17;
public static final long SEQUENCE_INVALID = 0; public static final long SEQUENCE_INVALID = 0;

View File

@ -46,6 +46,7 @@ public class ControlMessageReader {
case ControlMessage.TYPE_COLLAPSE_PANELS: case ControlMessage.TYPE_COLLAPSE_PANELS:
case ControlMessage.TYPE_ROTATE_DEVICE: case ControlMessage.TYPE_ROTATE_DEVICE:
case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS: case ControlMessage.TYPE_OPEN_HARD_KEYBOARD_SETTINGS:
case ControlMessage.TYPE_RESET_VIDEO:
return ControlMessage.createEmpty(type); return ControlMessage.createEmpty(type);
case ControlMessage.TYPE_UHID_CREATE: case ControlMessage.TYPE_UHID_CREATE:
return parseUhidCreate(); return parseUhidCreate();

View File

@ -9,6 +9,7 @@ import com.genymobile.scrcpy.device.Point;
import com.genymobile.scrcpy.device.Position; import com.genymobile.scrcpy.device.Position;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils; import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.video.SurfaceCapture;
import com.genymobile.scrcpy.video.VirtualDisplayListener; import com.genymobile.scrcpy.video.VirtualDisplayListener;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.InputManager;
@ -93,6 +94,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
private boolean keepDisplayPowerOff; 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) { public Controller(int displayId, ControlChannel controlChannel, CleanUp cleanUp, boolean clipboardAutosync, boolean powerOn) {
this.displayId = displayId; this.displayId = displayId;
this.controlChannel = controlChannel; this.controlChannel = controlChannel;
@ -143,6 +147,10 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
} }
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
this.surfaceCapture = surfaceCapture;
}
private UhidManager getUhidManager() { private UhidManager getUhidManager() {
if (uhidManager == null) { if (uhidManager == null) {
uhidManager = new UhidManager(sender); uhidManager = new UhidManager(sender);
@ -293,6 +301,9 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
case ControlMessage.TYPE_START_APP: case ControlMessage.TYPE_START_APP:
startAppAsync(msg.getText()); startAppAsync(msg.getText());
break; break;
case ControlMessage.TYPE_RESET_VIDEO:
resetVideo();
break;
default: default:
// do nothing // do nothing
} }
@ -680,4 +691,11 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
} }
} }
} }
private void resetVideo() {
if (surfaceCapture != null) {
Ln.i("Video capture reset");
surfaceCapture.requestInvalidate();
}
}
} }

View File

@ -355,4 +355,9 @@ public class CameraCapture extends SurfaceCapture {
public boolean isClosed() { public boolean isClosed() {
return disconnected.get(); 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)
}
} }

View File

@ -14,6 +14,8 @@ import android.hardware.display.VirtualDisplay;
import android.os.Build; import android.os.Build;
import android.view.Surface; import android.view.Surface;
import java.io.IOException;
public class NewDisplayCapture extends SurfaceCapture { public class NewDisplayCapture extends SurfaceCapture {
// Internal fields copied from android.hardware.display.DisplayManager // Internal fields copied from android.hardware.display.DisplayManager
@ -72,13 +74,8 @@ public class NewDisplayCapture extends SurfaceCapture {
} }
} }
@Override
public void start(Surface surface) {
if (virtualDisplay != null) {
virtualDisplay.release();
virtualDisplay = null;
}
public void startNew(Surface surface) {
int virtualDisplayId; int virtualDisplayId;
try { try {
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
@ -113,6 +110,15 @@ public class NewDisplayCapture extends SurfaceCapture {
} }
} }
@Override
public void start(Surface surface) throws IOException {
if (virtualDisplay == null) {
startNew(surface);
} else {
virtualDisplay.setSurface(surface);
}
}
@Override @Override
public void release() { public void release() {
if (virtualDisplay != null) { if (virtualDisplay != null) {
@ -142,4 +148,9 @@ public class NewDisplayCapture extends SurfaceCapture {
int num = size.getMax(); int num = size.getMax();
return initialDpi * num / den; return initialDpi * num / den;
} }
@Override
public void requestInvalidate() {
invalidate();
}
} }

View File

@ -291,4 +291,9 @@ public class ScreenCapture extends SurfaceCapture {
} }
} }
} }
@Override
public void requestInvalidate() {
invalidate();
}
} }

View File

@ -79,4 +79,11 @@ public abstract class SurfaceCapture {
public boolean isClosed() { public boolean isClosed() {
return false; return false;
} }
/**
* Manually request to invalidate (typically a user request).
* <p>
* The capture implementation is free to ignore the request and do nothing.
*/
public abstract void requestInvalidate();
} }