Simplify camera startup code

Avoid multiple back-and-forths between the caller thread and the camera
thread.
This commit is contained in:
Romain Vimont
2025-11-28 22:31:52 +01:00
parent dc628cc433
commit 8cb601f602

View File

@@ -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<OutputConfiguration> 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<CameraCaptureSession> future = new CompletableFuture<>();
OutputConfiguration outputConfig = new OutputConfiguration(surface);
List<OutputConfiguration> 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;
}
}