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:
parent
9958302e6f
commit
104195fc3b
@ -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)
|
||||||
|
@ -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)",
|
||||||
|
@ -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;
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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_
|
||||||
|
@ -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());
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,4 +291,9 @@ public class ScreenCapture extends SurfaceCapture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestInvalidate() {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user