From cb7a8b18af077baa796f48646c381504b2824e4d Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Fri, 28 Nov 2025 22:31:52 +0100 Subject: [PATCH] Simplify camera startup code Avoid multiple back-and-forths between the caller thread and the camera thread. --- .../scrcpy/video/CameraCapture.java | 102 ++++++++++-------- 1 file changed, 58 insertions(+), 44 deletions(-) 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 2b7c8099..ad0348ab 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/CameraCapture.java @@ -36,6 +36,7 @@ import android.view.Surface; import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; @@ -78,6 +79,9 @@ public class CameraCapture extends SurfaceCapture { private final AtomicBoolean disconnected = new AtomicBoolean(); + // Must be accessed only from the camera thread + private boolean started; + public CameraCapture(Options options) { this.explicitCameraId = options.getCameraId(); this.cameraFacing = options.getCameraFacing(); @@ -250,6 +254,7 @@ public class CameraCapture extends SurfaceCapture { return ratio.getAspectRatio(); } + @TargetApi(AndroidVersions.API_28_ANDROID_9) @Override public void start(Surface surface) throws IOException { if (transform != null) { @@ -261,11 +266,50 @@ public class CameraCapture extends SurfaceCapture { surface = glRunner.start(captureSize, videoSize, surface); } + cameraHandler.post(() -> { + assertCameraThread(); + started = true; + }); + + Surface captureSurface = surface; + OutputConfiguration outputConfig = new OutputConfiguration(captureSurface); + List outputs = Collections.singletonList(outputConfig); + int sessionType = highSpeed ? SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR; + SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, new CameraCaptureSession.StateCallback() { + @Override + public void onConfigured(CameraCaptureSession session) { + assertCameraThread(); + if (!started) { + // Stopped on the encoder thread between the call to start() and this callback + return; + } + + try { + CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); + requestBuilder.addTarget(captureSurface); + + if (fps > 0) { + requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps)); + } + + CaptureRequest request = requestBuilder.build(); + setRepeatingRequest(session, request); + } catch (CameraAccessException e) { + Ln.e("Camera error", e); + invalidate(); + } + } + + @Override + public void onConfigureFailed(CameraCaptureSession session) { + Ln.e("Camera configuration error"); + invalidate(); + } + }); + try { - CameraCaptureSession session = createCaptureSession(cameraDevice, surface); - CaptureRequest request = createCaptureRequest(surface); - setRepeatingRequest(session, request); - } catch (CameraAccessException | InterruptedException e) { + cameraDevice.createCaptureSession(sessionConfig); + } catch (CameraAccessException e) { stop(); throw new IOException(e); } @@ -273,6 +317,11 @@ public class CameraCapture extends SurfaceCapture { @Override public void stop() { + cameraHandler.post(() -> { + assertCameraThread(); + started = false; + }); + if (glRunner != null) { glRunner.stopAndRelease(); glRunner = null; @@ -353,46 +402,7 @@ public class CameraCapture extends SurfaceCapture { } @TargetApi(AndroidVersions.API_31_ANDROID_12) - private CameraCaptureSession createCaptureSession(CameraDevice camera, Surface surface) throws CameraAccessException, InterruptedException { - CompletableFuture future = new CompletableFuture<>(); - OutputConfiguration outputConfig = new OutputConfiguration(surface); - List outputs = Arrays.asList(outputConfig); - - int sessionType = highSpeed ? SessionConfiguration.SESSION_HIGH_SPEED : SessionConfiguration.SESSION_REGULAR; - SessionConfiguration sessionConfig = new SessionConfiguration(sessionType, outputs, cameraExecutor, new CameraCaptureSession.StateCallback() { - @Override - public void onConfigured(CameraCaptureSession session) { - future.complete(session); - } - - @Override - public void onConfigureFailed(CameraCaptureSession session) { - future.completeExceptionally(new CameraAccessException(CameraAccessException.CAMERA_ERROR)); - } - }); - - camera.createCaptureSession(sessionConfig); - - try { - return future.get(); - } catch (ExecutionException e) { - throw (CameraAccessException) e.getCause(); - } - } - - private CaptureRequest createCaptureRequest(Surface surface) throws CameraAccessException { - CaptureRequest.Builder requestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); - requestBuilder.addTarget(surface); - - if (fps > 0) { - requestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, new Range<>(fps, fps)); - } - - return requestBuilder.build(); - } - - @TargetApi(AndroidVersions.API_31_ANDROID_12) - private void setRepeatingRequest(CameraCaptureSession session, CaptureRequest request) throws CameraAccessException, InterruptedException { + private void setRepeatingRequest(CameraCaptureSession session, CaptureRequest request) throws CameraAccessException { CameraCaptureSession.CaptureCallback callback = new CameraCaptureSession.CaptureCallback() { @Override public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request, long timestamp, long frameNumber) { @@ -418,4 +428,8 @@ public class CameraCapture extends SurfaceCapture { public boolean isClosed() { return disconnected.get(); } + + private void assertCameraThread() { + assert Thread.currentThread() == cameraThread; + } }