Provide reason for capture reset

Add flags to indicate why a capture was reset.

This paves the way for properly handling virtual display resize
requests.
This commit is contained in:
Romain Vimont 2026-04-11 16:30:40 +02:00
parent b4ab0f62ab
commit 4f7e3d165e
6 changed files with 30 additions and 17 deletions

View file

@ -13,6 +13,7 @@ import com.genymobile.scrcpy.model.Size;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.video.CameraCapture;
import com.genymobile.scrcpy.video.CaptureControl;
import com.genymobile.scrcpy.video.SurfaceCapture;
import com.genymobile.scrcpy.video.VideoSource;
import com.genymobile.scrcpy.video.VirtualDisplayListener;
@ -815,7 +816,7 @@ public class Controller implements AsyncProcessor, VirtualDisplayListener {
private void resetVideo() {
if (surfaceCapture != null) {
Ln.i("Video capture reset");
surfaceCapture.getCaptureControl().reset();
surfaceCapture.getCaptureControl().reset(CaptureControl.RESET_REASON_CLIENT_RESET);
}
}
}

View file

@ -323,7 +323,7 @@ public class CameraCapture extends SurfaceCapture {
} catch (CameraAccessException e) {
Ln.e("Camera error", e);
disconnected.set(true);
getCaptureControl().reset();
getCaptureControl().reset(CaptureControl.RESET_REASON_TERMINATE);
}
}
@ -331,7 +331,7 @@ public class CameraCapture extends SurfaceCapture {
public void onConfigureFailed(CameraCaptureSession session) {
Ln.e("Camera configuration error");
disconnected.set(true);
getCaptureControl().reset();
getCaptureControl().reset(CaptureControl.RESET_REASON_TERMINATE);
}
});
@ -388,7 +388,7 @@ public class CameraCapture extends SurfaceCapture {
public void onDisconnected(CameraDevice camera) {
Ln.w("Camera disconnected");
disconnected.set(true);
getCaptureControl().reset();
getCaptureControl().reset(CaptureControl.RESET_REASON_TERMINATE);
}
@Override

View file

@ -2,21 +2,30 @@ package com.genymobile.scrcpy.video;
import android.media.MediaCodec;
import java.util.concurrent.atomic.AtomicBoolean;
public class CaptureControl {
private final AtomicBoolean reset = new AtomicBoolean();
public static final int RESET_REASON_TERMINATE = 1;
public static final int RESET_REASON_DISPLAY_PROPERTIES_CHANGED = 1 << 1;
public static final int RESET_REASON_CLIENT_RESET = 1 << 2;
private int reset = 0;
// Current instance of MediaCodec to "interrupt" on reset
private MediaCodec runningMediaCodec;
public boolean consumeReset() {
return reset.getAndSet(false);
public synchronized boolean isResetRequested() {
return reset != 0;
}
public synchronized void reset() {
reset.set(true);
public synchronized int consumeReset() {
int value = reset;
reset = 0;
return value;
}
public synchronized void reset(int reason) {
assert reason != 0;
reset |= reason;
if (runningMediaCodec != null) {
try {
runningMediaCodec.signalEndOfInputStream();

View file

@ -201,7 +201,7 @@ public class NewDisplayCapture extends SurfaceCapture {
ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy);
}
displayMonitor.start(virtualDisplayId, () -> getCaptureControl().reset());
displayMonitor.start(virtualDisplayId, () -> getCaptureControl().reset(CaptureControl.RESET_REASON_DISPLAY_PROPERTIES_CHANGED));
} catch (Exception e) {
Ln.e("Could not create display", e);
throw new AssertionError("Could not create display");

View file

@ -61,7 +61,7 @@ public class ScreenCapture extends SurfaceCapture {
@Override
public void init() {
displayMonitor.start(displayId, () -> getCaptureControl().reset());
displayMonitor.start(displayId, () -> getCaptureControl().reset(CaptureControl.RESET_REASON_DISPLAY_PROPERTIES_CHANGED));
}
@Override

View file

@ -97,7 +97,10 @@ public class SurfaceEncoder implements AsyncProcessor {
streamer.writeVideoHeader();
do {
captureControl.consumeReset(); // If a capture reset was requested, it is implicitly fulfilled
int resetReasons = captureControl.consumeReset();
if ((resetReasons & CaptureControl.RESET_REASON_TERMINATE) != 0) {
break;
}
capture.prepare();
Size size = capture.getSize();
@ -123,12 +126,12 @@ public class SurfaceEncoder implements AsyncProcessor {
if (stopped.get()) {
alive = false;
} else {
boolean resetRequested = captureControl.consumeReset();
if (!resetRequested) {
if (!captureControl.isResetRequested()) {
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
streamer.writeSessionMeta(size.getWidth(), size.getHeight());
encode(mediaCodec, streamer);
}
// The capture might have been closed internally (for example if the camera is disconnected)
alive = !stopped.get() && !capture.isClosed();
}
@ -275,7 +278,7 @@ public class SurfaceEncoder implements AsyncProcessor {
public void stop() {
if (thread != null) {
stopped.set(true);
captureControl.reset();
captureControl.reset(CaptureControl.RESET_REASON_TERMINATE);
}
}