Compare commits
1 Commits
reset_vide
...
opengl_fil
Author | SHA1 | Date | |
---|---|---|---|
4397dfba89 |
@ -671,10 +671,6 @@ 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)
|
||||
|
@ -1022,10 +1022,6 @@ 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)",
|
||||
|
@ -181,7 +181,6 @@ 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:
|
||||
@ -305,9 +304,6 @@ 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;
|
||||
|
@ -42,7 +42,6 @@ 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 {
|
||||
|
@ -284,18 +284,6 @@ 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) {
|
||||
@ -533,12 +521,8 @@ sc_input_manager_process_key(struct sc_input_manager *im,
|
||||
}
|
||||
return;
|
||||
case SDLK_r:
|
||||
if (control && !repeat && down && !paused) {
|
||||
if (shift) {
|
||||
reset_video(im);
|
||||
} else {
|
||||
rotate_device(im);
|
||||
}
|
||||
if (control && !shift && !repeat && down && !paused) {
|
||||
rotate_device(im);
|
||||
}
|
||||
return;
|
||||
case SDLK_k:
|
||||
|
@ -407,21 +407,6 @@ 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;
|
||||
@ -444,6 +429,5 @@ 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;
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ _<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)_
|
||||
| Pause or re-pause display | <kbd>MOD</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 remove black borders | <kbd>MOD</kbd>+<kbd>w</kbd> \| _Double-left-click¹_
|
||||
| Click on `HOME` | <kbd>MOD</kbd>+<kbd>h</kbd> \| _Middle-click_
|
||||
|
@ -137,7 +137,7 @@ public final class CleanUp {
|
||||
}
|
||||
}
|
||||
|
||||
if (displayId != Device.DISPLAY_ID_NONE && Device.isScreenOn(displayId)) {
|
||||
if (Device.isScreenOn() && displayId != Device.DISPLAY_ID_NONE) {
|
||||
if (powerOffScreen) {
|
||||
Ln.i("Power off screen");
|
||||
Device.powerOffScreen(displayId);
|
||||
|
@ -225,10 +225,6 @@ public final class Server {
|
||||
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());
|
||||
|
@ -24,7 +24,6 @@ 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;
|
||||
|
||||
|
@ -46,7 +46,6 @@ public class ControlMessageReader {
|
||||
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();
|
||||
|
@ -9,7 +9,6 @@ import com.genymobile.scrcpy.device.Point;
|
||||
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;
|
||||
@ -94,9 +93,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
|
||||
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;
|
||||
@ -147,10 +143,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void setSurfaceCapture(SurfaceCapture surfaceCapture) {
|
||||
this.surfaceCapture = surfaceCapture;
|
||||
}
|
||||
|
||||
private UhidManager getUhidManager() {
|
||||
if (uhidManager == null) {
|
||||
uhidManager = new UhidManager(sender);
|
||||
@ -174,7 +166,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
|
||||
private void control() throws IOException {
|
||||
// on start, power on the device
|
||||
if (powerOn && displayId == 0 && !Device.isScreenOn(displayId)) {
|
||||
if (powerOn && displayId == 0 && !Device.isScreenOn()) {
|
||||
Device.pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||
|
||||
// dirty hack
|
||||
@ -301,9 +293,6 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
case ControlMessage.TYPE_START_APP:
|
||||
startAppAsync(msg.getText());
|
||||
break;
|
||||
case ControlMessage.TYPE_RESET_VIDEO:
|
||||
resetVideo();
|
||||
break;
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
@ -501,7 +490,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
|
||||
private boolean pressBackOrTurnScreenOn(int action) {
|
||||
if (displayId == Device.DISPLAY_ID_NONE || Device.isScreenOn(displayId)) {
|
||||
if (Device.isScreenOn()) {
|
||||
return injectKeyEvent(action, KeyEvent.KEYCODE_BACK, 0, 0, Device.INJECT_MODE_ASYNC);
|
||||
}
|
||||
|
||||
@ -691,11 +680,4 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetVideo() {
|
||||
if (surfaceCapture != null) {
|
||||
Ln.i("Video capture reset");
|
||||
surfaceCapture.requestInvalidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,9 +82,8 @@ public final class Device {
|
||||
&& injectKeyEvent(KeyEvent.ACTION_UP, keyCode, 0, 0, displayId, injectMode);
|
||||
}
|
||||
|
||||
public static boolean isScreenOn(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
return ServiceManager.getPowerManager().isScreenOn(displayId);
|
||||
public static boolean isScreenOn() {
|
||||
return ServiceManager.getPowerManager().isScreenOn();
|
||||
}
|
||||
|
||||
public static void expandNotificationPanel() {
|
||||
@ -182,7 +181,7 @@ public final class Device {
|
||||
public static boolean powerOffScreen(int displayId) {
|
||||
assert displayId != DISPLAY_ID_NONE;
|
||||
|
||||
if (!isScreenOn(displayId)) {
|
||||
if (!isScreenOn()) {
|
||||
return true;
|
||||
}
|
||||
return pressReleaseKeycode(KeyEvent.KEYCODE_POWER, displayId, Device.INJECT_MODE_ASYNC);
|
||||
|
@ -68,7 +68,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() throws IOException {
|
||||
public void init() throws IOException {
|
||||
cameraThread = new HandlerThread("camera");
|
||||
cameraThread.start();
|
||||
cameraHandler = new Handler(cameraThread.getLooper());
|
||||
@ -256,7 +256,7 @@ public class CameraCapture extends SurfaceCapture {
|
||||
public void onDisconnected(CameraDevice camera) {
|
||||
Ln.w("Camera disconnected");
|
||||
disconnected.set(true);
|
||||
invalidate();
|
||||
requestReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -355,9 +355,4 @@ public class CameraCapture extends SurfaceCapture {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import android.media.MediaCodec;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class CaptureReset implements SurfaceCapture.CaptureListener {
|
||||
|
||||
private final AtomicBoolean reset = new AtomicBoolean();
|
||||
|
||||
// Current instance of MediaCodec to "interrupt" on reset
|
||||
private MediaCodec runningMediaCodec;
|
||||
|
||||
public boolean consumeReset() {
|
||||
return reset.getAndSet(false);
|
||||
}
|
||||
|
||||
public synchronized void reset() {
|
||||
reset.set(true);
|
||||
if (runningMediaCodec != null) {
|
||||
runningMediaCodec.signalEndOfInputStream();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void setRunningMediaCodec(MediaCodec runningMediaCodec) {
|
||||
this.runningMediaCodec = runningMediaCodec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInvalidated() {
|
||||
reset();
|
||||
}
|
||||
}
|
@ -14,8 +14,6 @@ import android.hardware.display.VirtualDisplay;
|
||||
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
|
||||
@ -48,7 +46,7 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init() {
|
||||
public void init() {
|
||||
size = newDisplay.getSize();
|
||||
dpi = newDisplay.getDpi();
|
||||
if (size == null || dpi == 0) {
|
||||
@ -74,8 +72,13 @@ 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;
|
||||
try {
|
||||
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
|
||||
@ -90,8 +93,8 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
| VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED
|
||||
| VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
||||
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
||||
flags |= VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
|
||||
| VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP;
|
||||
}
|
||||
}
|
||||
virtualDisplay = ServiceManager.getDisplayManager()
|
||||
@ -104,21 +107,13 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
}
|
||||
|
||||
if (vdListener != null) {
|
||||
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
|
||||
Rect contentRect = new Rect(0, 0, size.getWidth(), size.getHeight());
|
||||
PositionMapper positionMapper = new PositionMapper(size, contentRect, 0);
|
||||
vdListener.onNewVirtualDisplay(virtualDisplayId, positionMapper);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Surface surface) throws IOException {
|
||||
if (virtualDisplay == null) {
|
||||
startNew(surface);
|
||||
} else {
|
||||
virtualDisplay.setSurface(surface);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
if (virtualDisplay != null) {
|
||||
@ -148,9 +143,4 @@ public class NewDisplayCapture extends SurfaceCapture {
|
||||
int num = size.getMax();
|
||||
return initialDpi * num / den;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInvalidate() {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
Ln.v("ScreenCapture: requestReset(): " + getSessionDisplaySize() + " -> (unknown)");
|
||||
}
|
||||
setSessionDisplaySize(null);
|
||||
invalidate();
|
||||
requestReset();
|
||||
} else {
|
||||
Size size = di.getSize();
|
||||
|
||||
@ -102,7 +102,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
// Set the new size immediately, so that a future onDisplayChanged() event called before the asynchronous prepare()
|
||||
// considers that the current size is the requested size (to avoid a duplicate requestReset())
|
||||
setSessionDisplaySize(size);
|
||||
invalidate();
|
||||
requestReset();
|
||||
} else if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v("ScreenCapture: Size not changed (" + size + "): do not requestReset()");
|
||||
}
|
||||
@ -246,7 +246,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
if (Ln.isEnabled(Ln.Level.VERBOSE)) {
|
||||
Ln.v("ScreenCapture: onRotationChanged(" + rotation + ")");
|
||||
}
|
||||
invalidate();
|
||||
requestReset();
|
||||
}
|
||||
};
|
||||
ServiceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
|
||||
@ -272,7 +272,7 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
// Ignore events related to other display ids
|
||||
return;
|
||||
}
|
||||
invalidate();
|
||||
requestReset();
|
||||
}
|
||||
};
|
||||
ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener);
|
||||
@ -291,9 +291,4 @@ public class ScreenCapture extends SurfaceCapture {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestInvalidate() {
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
@ -6,37 +6,36 @@ import com.genymobile.scrcpy.device.Size;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* A video source which can be rendered on a Surface for encoding.
|
||||
*/
|
||||
public abstract class SurfaceCapture {
|
||||
|
||||
public interface CaptureListener {
|
||||
void onInvalidated();
|
||||
}
|
||||
|
||||
private CaptureListener listener;
|
||||
private final AtomicBoolean resetCapture = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Notify the listener that the capture has been invalidated (for example, because its size changed).
|
||||
* Request the encoding session to be restarted, for example if the capture implementation detects that the video source size has changed (on
|
||||
* device rotation for example).
|
||||
*/
|
||||
protected void invalidate() {
|
||||
listener.onInvalidated();
|
||||
protected void requestReset() {
|
||||
resetCapture.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the reset request (intended to be called by the encoder).
|
||||
*
|
||||
* @return {@code true} if a reset request was pending, {@code false} otherwise.
|
||||
*/
|
||||
public boolean consumeReset() {
|
||||
return resetCapture.getAndSet(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once before the first capture starts.
|
||||
*/
|
||||
public final void init(CaptureListener listener) throws ConfigurationException, IOException {
|
||||
this.listener = listener;
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called once before the first capture starts.
|
||||
*/
|
||||
protected abstract void init() throws ConfigurationException, IOException;
|
||||
public abstract void init() throws ConfigurationException, IOException;
|
||||
|
||||
/**
|
||||
* Called after the last capture ends (if and only if {@link #init()} has been called).
|
||||
@ -79,11 +78,4 @@ public abstract class SurfaceCapture {
|
||||
public boolean isClosed() {
|
||||
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();
|
||||
}
|
||||
|
@ -49,8 +49,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
private Thread thread;
|
||||
private final AtomicBoolean stopped = new AtomicBoolean();
|
||||
|
||||
private final CaptureReset reset = new CaptureReset();
|
||||
|
||||
public SurfaceEncoder(SurfaceCapture capture, Streamer streamer, int videoBitRate, float maxFps, List<CodecOption> codecOptions,
|
||||
String encoderName, boolean downsizeOnError) {
|
||||
this.capture = capture;
|
||||
@ -67,14 +65,14 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
MediaCodec mediaCodec = createMediaCodec(codec, encoderName);
|
||||
MediaFormat format = createFormat(codec.getMimeType(), videoBitRate, maxFps, codecOptions);
|
||||
|
||||
capture.init(reset);
|
||||
capture.init();
|
||||
|
||||
try {
|
||||
boolean alive;
|
||||
boolean headerWritten = false;
|
||||
|
||||
do {
|
||||
reset.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||
capture.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
|
||||
capture.prepare();
|
||||
Size size = capture.getSize();
|
||||
if (!headerWritten) {
|
||||
@ -90,27 +88,17 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
|
||||
surface = mediaCodec.createInputSurface();
|
||||
|
||||
VideoFilter filter = new VideoFilter(surface);
|
||||
surface = filter.getInputSurface();
|
||||
|
||||
capture.start(surface);
|
||||
|
||||
mediaCodec.start();
|
||||
|
||||
// Set the MediaCodec instance to "interrupt" (by signaling an EOS) on reset
|
||||
reset.setRunningMediaCodec(mediaCodec);
|
||||
|
||||
if (stopped.get()) {
|
||||
alive = false;
|
||||
} else {
|
||||
boolean resetRequested = reset.consumeReset();
|
||||
if (!resetRequested) {
|
||||
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
|
||||
encode(mediaCodec, streamer);
|
||||
}
|
||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||
alive = !stopped.get() && !capture.isClosed();
|
||||
}
|
||||
|
||||
alive = encode(mediaCodec, streamer);
|
||||
// do not call stop() on exception, it would trigger an IllegalStateException
|
||||
mediaCodec.stop();
|
||||
filter.release();
|
||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||
Ln.e("Encoding error: " + e.getClass().getName() + ": " + e.getMessage());
|
||||
if (!prepareRetry(size)) {
|
||||
@ -119,7 +107,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
Ln.i("Retrying...");
|
||||
alive = true;
|
||||
} finally {
|
||||
reset.setRunningMediaCodec(null);
|
||||
mediaCodec.reset();
|
||||
if (surface != null) {
|
||||
surface.release();
|
||||
@ -180,16 +167,25 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void encode(MediaCodec codec, Streamer streamer) throws IOException {
|
||||
private boolean encode(MediaCodec codec, Streamer streamer) throws IOException {
|
||||
boolean eof = false;
|
||||
boolean alive = true;
|
||||
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
|
||||
|
||||
boolean eos;
|
||||
do {
|
||||
while (!capture.consumeReset() && !eof) {
|
||||
if (stopped.get()) {
|
||||
alive = false;
|
||||
break;
|
||||
}
|
||||
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
|
||||
try {
|
||||
eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
||||
// On EOS, there might be data or not, depending on bufferInfo.size
|
||||
if (outputBufferId >= 0 && bufferInfo.size > 0) {
|
||||
if (capture.consumeReset()) {
|
||||
// must restart encoding with new size
|
||||
break;
|
||||
}
|
||||
|
||||
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
|
||||
if (outputBufferId >= 0) {
|
||||
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
|
||||
|
||||
boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
|
||||
@ -206,7 +202,14 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
codec.releaseOutputBuffer(outputBufferId, false);
|
||||
}
|
||||
}
|
||||
} while (!eos);
|
||||
}
|
||||
|
||||
if (capture.isClosed()) {
|
||||
// The capture might have been closed internally (for example if the camera is disconnected)
|
||||
alive = false;
|
||||
}
|
||||
|
||||
return !eof && alive;
|
||||
}
|
||||
|
||||
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
|
||||
@ -299,7 +302,6 @@ public class SurfaceEncoder implements AsyncProcessor {
|
||||
public void stop() {
|
||||
if (thread != null) {
|
||||
stopped.set(true);
|
||||
reset.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,116 @@
|
||||
package com.genymobile.scrcpy.video;
|
||||
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.opengl.EGL14;
|
||||
import android.opengl.EGLConfig;
|
||||
import android.opengl.EGLContext;
|
||||
import android.opengl.EGLDisplay;
|
||||
import android.opengl.EGLSurface;
|
||||
import android.opengl.GLES11Ext;
|
||||
import android.opengl.GLES20;
|
||||
import android.view.Surface;
|
||||
|
||||
public class VideoFilter {
|
||||
private EGLDisplay eglDisplay;
|
||||
private EGLContext eglContext;
|
||||
private EGLSurface eglSurface;
|
||||
private SurfaceTexture surfaceTexture;
|
||||
private Surface inputSurface;
|
||||
private int textureId;
|
||||
|
||||
public VideoFilter(Surface outputSurface) {
|
||||
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||
if (eglDisplay == EGL14.EGL_NO_DISPLAY) {
|
||||
throw new RuntimeException("Unable to get EGL14 display");
|
||||
}
|
||||
|
||||
int[] version = new int[2];
|
||||
if (!EGL14.eglInitialize(eglDisplay, version, 0, version, 1)) {
|
||||
throw new RuntimeException("Unable to initialize EGL14");
|
||||
}
|
||||
|
||||
int[] attribList = {
|
||||
EGL14.EGL_RED_SIZE, 8,
|
||||
EGL14.EGL_GREEN_SIZE, 8,
|
||||
EGL14.EGL_BLUE_SIZE, 8,
|
||||
EGL14.EGL_ALPHA_SIZE, 8,
|
||||
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
EGLConfig[] configs = new EGLConfig[1];
|
||||
int[] numConfigs = new int[1];
|
||||
EGL14.eglChooseConfig(eglDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0);
|
||||
if (numConfigs[0] <= 0) {
|
||||
throw new RuntimeException("Unable to find ES2 EGL config");
|
||||
}
|
||||
EGLConfig eglConfig = configs[0];
|
||||
|
||||
int[] contextAttribList = {
|
||||
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, contextAttribList, 0);
|
||||
if (eglContext == null) {
|
||||
throw new RuntimeException("Failed to create EGL context");
|
||||
}
|
||||
|
||||
int[] surfaceAttribList = {
|
||||
EGL14.EGL_NONE
|
||||
};
|
||||
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, outputSurface, surfaceAttribList, 0);
|
||||
if (eglSurface == null) {
|
||||
throw new RuntimeException("Failed to create EGL window surface");
|
||||
}
|
||||
|
||||
if (!EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
|
||||
throw new RuntimeException("Failed to make EGL context current");
|
||||
}
|
||||
|
||||
int[] textures = new int[1];
|
||||
GLES20.glGenTextures(1, textures, 0);
|
||||
textureId = textures[0];
|
||||
|
||||
surfaceTexture = new SurfaceTexture(textureId);
|
||||
inputSurface = new Surface(surfaceTexture);
|
||||
|
||||
surfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
|
||||
@Override
|
||||
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||
// XXX This should be called when the VirtualDisplay has rendered a new frame
|
||||
Ln.i("==== render");
|
||||
render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Surface getInputSurface() {
|
||||
return inputSurface;
|
||||
}
|
||||
|
||||
public void render() {
|
||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
|
||||
|
||||
// For now, just paint with a color
|
||||
GLES20.glClearColor(0.0f, 0.5f, 0.5f, 1.0f);
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLES20.glViewport(0, 0, 1920, 1080);
|
||||
|
||||
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (eglDisplay != EGL14.EGL_NO_DISPLAY) {
|
||||
EGL14.eglDestroySurface(eglDisplay, eglSurface);
|
||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||
EGL14.eglTerminate(eglDisplay);
|
||||
}
|
||||
eglDisplay = EGL14.EGL_NO_DISPLAY;
|
||||
eglContext = EGL14.EGL_NO_CONTEXT;
|
||||
eglSurface = EGL14.EGL_NO_SURFACE;
|
||||
surfaceTexture.release();
|
||||
inputSurface.release();
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
package com.genymobile.scrcpy.wrappers;
|
||||
|
||||
import com.genymobile.scrcpy.AndroidVersions;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.IInterface;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@ -23,22 +21,14 @@ public final class PowerManager {
|
||||
|
||||
private Method getIsScreenOnMethod() throws NoSuchMethodException {
|
||||
if (isScreenOnMethod == null) {
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
isScreenOnMethod = manager.getClass().getMethod("isDisplayInteractive", int.class);
|
||||
} else {
|
||||
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
|
||||
}
|
||||
isScreenOnMethod = manager.getClass().getMethod("isInteractive");
|
||||
}
|
||||
return isScreenOnMethod;
|
||||
}
|
||||
|
||||
public boolean isScreenOn(int displayId) {
|
||||
|
||||
public boolean isScreenOn() {
|
||||
try {
|
||||
Method method = getIsScreenOnMethod();
|
||||
if (Build.VERSION.SDK_INT >= AndroidVersions.API_34_ANDROID_14) {
|
||||
return (boolean) method.invoke(manager, displayId);
|
||||
}
|
||||
return (boolean) method.invoke(manager);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
Ln.e("Could not invoke method", e);
|
||||
|
Reference in New Issue
Block a user