Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
QtCamera2.java
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3package org.qtproject.qt.android.multimedia.qffmpeg;
4
5import android.annotation.SuppressLint;
6import android.annotation.TargetApi;
7import android.content.Context;
8import android.graphics.ImageFormat;
9import android.graphics.Rect;
10import android.hardware.camera2.CameraAccessException;
11import android.hardware.camera2.CameraCaptureSession;
12import android.hardware.camera2.CameraDevice;
13import android.hardware.camera2.CameraMetadata;
14import android.hardware.camera2.CameraManager;
15import android.hardware.camera2.CaptureFailure;
16import android.hardware.camera2.CaptureResult;
17import android.hardware.camera2.CaptureRequest;
18import android.hardware.camera2.params.OutputConfiguration;
19import android.hardware.camera2.params.SessionConfiguration;
20import android.media.Image;
21import android.media.ImageReader;
22import android.os.Handler;
23import android.os.HandlerThread;
24import android.util.Log;
25import android.util.Range;
26import android.view.Surface;
27import java.lang.Thread;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.concurrent.CountDownLatch;
31import java.util.concurrent.TimeUnit;
32
33import org.qtproject.qt.android.UsedFromNativeCode;
34
35@TargetApi(23)
36class QtCamera2 {
37 static final String LOG_TAG = "QtCamera2";
38
39 // Should be called if an on-going still photo capture has failed to finish.
40 // This lets us submit an appropriate error and notify QImageCapture that there will
41 // be no photo emitted.
42 // TODO: In the future we should send a more descriptive error message to QImageCapture, and
43 // and pass it as a parameter here.
44 native void onStillPhotoCaptureFailed(String cameraId);
45
46 CameraDevice mCameraDevice = null;
47 // Counted down by CameraDeviceStateCallback.onClosed(), awaited in stopAndClose()
48 volatile CountDownLatch mDeviceClosedLatch = null;
49 QtVideoDeviceManager mVideoDeviceManager = null;
50 // Thread and handler to that allows us to receive callbacks and frames on a background thread.
51 HandlerThread mBackgroundThread;
52 Handler mBackgroundHandler;
53 // This object allows us to receive frames with associated callbacks. This ImageReader
54 // is used to emit preview/video frames to the C++ thread.
55 ImageReader mPreviewImageReader = null;
56 // Used to emit still photo images to the C++ thread.
57 ImageReader mStillPhotoImageReader = null;
58 CameraManager mCameraManager;
59 CameraCaptureSession mCaptureSession;
60 // Callback of the session being created or active. Events from callbacks of replaced
61 // sessions are ignored, see CameraCaptureSessionStateCallback.
62 volatile CameraCaptureSessionStateCallback mCurrentSessionCallback;
63 CaptureRequest.Builder mPreviewRequestBuilder;
64 CaptureRequest mPreviewRequest;
65 String mCameraId;
66 List<Surface> mTargetSurfaces = new ArrayList<>();
67
68 private static int MaxNumberFrames = 12;
69
70 // The purpose of this class is to gather variables that are accessed across
71 // the C++ QCamera's thread, and the background capture-processing thread.
72 // It also acts as the mutex for these variables.
73 // All access to these variables must happen after locking the instance.
74 class SyncedMembers {
75 // Idle -> Started: start(). Started <-> Capturing: still photo begins/ends.
76 // Idle/Started -> Reconfiguring: session rebuilt for a new still size, start() returns it
77 // to Started, a failure to Idle. Anything -> Idle: stopAndClose().
79 State mState = State.Idle;
80
81 // A still photo requested while reconfiguring runs once the session is configured.
82 boolean mStillCaptureDeferred = false;
83
84 // Preview is running, settings are applied to it directly
85 boolean isStarted() {
86 return mState == State.Started || mState == State.Capturing;
87 }
88
89 boolean isTakingStillPhoto() {
90 return mState == State.Capturing;
91 }
92
93 void endStillPhoto() {
94 if (mState == State.Capturing)
95 mState = State.Started;
96 }
97
98 private CameraSettings mCameraSettings = new CameraSettings();
99 }
100 final SyncedMembers mSyncedMembers = new SyncedMembers();
101
102 // Resets the control properties of this camera to their default values.
104 public void resetControlProperties() {
105 synchronized (mSyncedMembers) {
106 mSyncedMembers.mCameraSettings = new CameraSettings();
107 }
108 }
109
110 // Returns a deep copy of the CameraSettings instance. Thread-safe.
111 private CameraSettings atomicCameraSettingsCopy() {
112 synchronized (mSyncedMembers) {
113 return new CameraSettings(mSyncedMembers.mCameraSettings);
114 }
115 }
116
117 QtExifDataHandler mExifDataHandler = null;
118
119 native void onCameraOpened(String cameraId);
120 native void onCameraDisconnect(String cameraId);
121 native void onCameraError(String cameraId, int error);
122
123 CameraDeviceStateCallback mStateCallback = new CameraDeviceStateCallback(this);
124
125 native void onCaptureSessionConfigured(String cameraId);
126 native void onCaptureSessionConfigureFailed(String cameraId);
127 native void onStillReaderSwapFailed(String cameraId);
128
129
130 native void onSessionActive(String cameraId);
131 native void onSessionClosed(String cameraId);
132 native void onCaptureSessionFailed(String cameraId, int reason, long frameNumber);
133
134 // This callback is used when doing normal preview. The only purpose is to detect if something
135 // goes wrong, so we can report back to QCamera.
136 class PreviewCaptureSessionCallback extends CameraCaptureSession.CaptureCallback {
137 @Override
138 public void onCaptureFailed(
139 CameraCaptureSession session,
140 CaptureRequest request,
141 CaptureFailure failure)
142 {
143 super.onCaptureFailed(session, request, failure);
144 onCaptureSessionFailed(mCameraId, failure.getReason(), failure.getFrameNumber());
145 }
146 }
147
148 // Callback that is being used for error-handling when doing preview.
149 // TODO: The variable can be removed in the future, and instead just recreate the object
150 // every time we go into previewing.
151 PreviewCaptureSessionCallback mPreviewCaptureCallback = new PreviewCaptureSessionCallback();
152
153 QtCamera2(Context context) {
154 mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
155 mVideoDeviceManager = new QtVideoDeviceManager(context);
156 startBackgroundThread();
157 }
158
159 void startBackgroundThread() {
160 mBackgroundThread = new HandlerThread("CameraBackground");
161 mBackgroundThread.start();
162 mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
163 }
164
166 void stopBackgroundThread() {
167 mBackgroundThread.quitSafely();
168 try {
169 mBackgroundThread.join();
170 mBackgroundThread = null;
171 mBackgroundHandler = null;
172 } catch (Exception e) {
173 e.printStackTrace();
174 }
175 }
176
177 @SuppressLint("MissingPermission")
178 boolean open(String cameraId) {
179 try {
180 mCameraId = cameraId;
181 mCameraManager.openCamera(cameraId,mStateCallback,mBackgroundHandler);
182 return true;
183 } catch (Exception e){
184 Log.w(LOG_TAG, "Failed to open camera:" + e);
185 }
186
187 return false;
188 }
189
190 native void onStillPhotoAvailable(String cameraId, Image frame);
191
192 // Callback for when we receive a finalized still photo in mStillPhotoImageReader.
193 ImageReader.OnImageAvailableListener mOnStillPhotoAvailableListener = new ImageReader.OnImageAvailableListener() {
194 @Override
195 public void onImageAvailable(ImageReader reader) {
196 QtCamera2.this.onStillPhotoAvailable(mCameraId, reader.acquireLatestImage());
197 }
198 };
199
200 native void onPreviewFrameAvailable(String cameraId, Image frame);
201
202 // Callback for when we receive a preview/video frame in the associated mPreviewImageReader.
203 ImageReader.OnImageAvailableListener mOnPreviewImageAvailableListener = new ImageReader.OnImageAvailableListener() {
204 @Override
205 public void onImageAvailable(ImageReader reader) {
206 try {
207 Image img = reader.acquireLatestImage();
208 if (img != null)
209 QtCamera2.this.onPreviewFrameAvailable(mCameraId, img);
210 } catch (IllegalStateException e) {
211 // It seems that ffmpeg is processing images for too long (and does not close it)
212 // Give it a little more time. Restarting the camera session if it doesn't help
213 Log.e(LOG_TAG, "Image processing taking too long. Let's wait 0,5s more " + e);
214 try {
215 Thread.sleep(500);
216 QtCamera2.this.onPreviewFrameAvailable(mCameraId, reader.acquireLatestImage());
217 } catch (IllegalStateException | InterruptedException e2) {
218 Log.e(LOG_TAG, "Will not wait anymore. Restart camera session. " + e2);
219 // Remember current used camera ID, because stopAndClose will clear the value
220 String cameraId = mCameraId;
221 stopAndClose();
222 // Re-register surfaces
223 addPreviewImageReader(mPreviewImageReader.getWidth(),
224 mPreviewImageReader.getHeight());
225 if (mStillPhotoImageReader != null) {
226 addStillImageReader(mStillPhotoImageReader.getWidth(),
227 mStillPhotoImageReader.getHeight());
228 }
229 open(cameraId);
230 }
231 }
232 }
233 };
234
236 void prepareCamera(int width, int height, int minFps, int maxFps, int stillWidth, int stillHeight) {
237 addPreviewImageReader(width, height);
238 setFrameRate(minFps, maxFps);
239 addStillImageReader(stillWidth, stillHeight);
240 }
241
242 private void addPreviewImageReader(int width, int height) {
243 if (mPreviewImageReader != null)
244 removeSurface(mPreviewImageReader.getSurface());
245
246 mPreviewImageReader =
247 ImageReader.newInstance(width, height, ImageFormat.YUV_420_888, MaxNumberFrames);
248 mPreviewImageReader.setOnImageAvailableListener(mOnPreviewImageAvailableListener, mBackgroundHandler);
249 addSurface(mPreviewImageReader.getSurface());
250 }
251
252 void addStillImageReader(int width, int height) {
253 if (mStillPhotoImageReader != null)
254 removeSurface(mStillPhotoImageReader.getSurface());
255
256 mStillPhotoImageReader =
257 ImageReader.newInstance(width, height, ImageFormat.JPEG, MaxNumberFrames);
258 mStillPhotoImageReader.setOnImageAvailableListener(mOnStillPhotoAvailableListener, mBackgroundHandler);
259 addSurface(mStillPhotoImageReader.getSurface());
260 }
261
262 // Swaps the still ImageReader and rebuilds the session, keeping the camera open. Runs on the
263 // background handler so it can't interleave with session callbacks. Failure is reported
264 // through onStillReaderSwapFailed.
266 boolean updateStillImageReader(int width, int height) {
267 final Handler handler = mBackgroundHandler;
268 if (mCameraDevice == null || mStillPhotoImageReader == null || handler == null)
269 return false;
270
271 synchronized (mSyncedMembers) {
272 if (mSyncedMembers.isTakingStillPhoto())
273 return false;
274 mSyncedMembers.mState = SyncedMembers.State.Reconfiguring;
275 }
276
277 return handler.post(() -> {
278 if (mCameraDevice == null || mStillPhotoImageReader == null)
279 return; // Closed in the meantime
280
281 mCurrentSessionCallback = null;
282 if (mCaptureSession != null) {
283 mCaptureSession.close();
284 mCaptureSession = null;
285 }
286
287 // Not closed: native code may still hold its Images. GC releases it once they're gone.
288 mStillPhotoImageReader.setOnImageAvailableListener(null, null);
289 addStillImageReader(width, height);
290
291 if (!createSession())
292 onSessionConfigureFailed();
293 });
294 }
295
296 // Called on the background thread when configuring the session failed.
297 void onSessionConfigureFailed() {
298 final boolean isStillReaderSwap;
299 synchronized (mSyncedMembers) {
300 isStillReaderSwap = mSyncedMembers.mState == SyncedMembers.State.Reconfiguring;
301 }
302 if (isStillReaderSwap)
303 onStillReaderSwapFailed(mCameraId);
304 else
306 onSessionReconfigured(false);
307 }
308
309 // Called on the background thread. Runs the deferred still capture, if any.
310 void onSessionReconfigured(boolean success) {
311 final boolean captureDeferred;
312 synchronized (mSyncedMembers) {
313 if (mSyncedMembers.mState == SyncedMembers.State.Reconfiguring)
314 mSyncedMembers.mState = SyncedMembers.State.Idle;
315 captureDeferred = mSyncedMembers.mStillCaptureDeferred;
316 mSyncedMembers.mStillCaptureDeferred = false;
317 }
318 if (!captureDeferred)
319 return;
320 if (success)
321 beginStillPhotoCapture();
322 else
323 onStillPhotoCaptureFailed(mCameraId);
324 }
325
326 private void setFrameRate(int minFrameRate, int maxFrameRate) {
327 synchronized (mSyncedMembers) {
328 if (minFrameRate <= 0 || maxFrameRate <= 0)
329 mSyncedMembers.mCameraSettings.mFpsRange = null;
330 else
331 mSyncedMembers.mCameraSettings.mFpsRange = new Range<>(minFrameRate, maxFrameRate);
332 }
333 }
334
335 boolean addSurface(Surface surface) {
336 if (mTargetSurfaces.contains(surface))
337 return true;
338
339 return mTargetSurfaces.add(surface);
340 }
341
342 boolean removeSurface(Surface surface) {
343 return mTargetSurfaces.remove(surface);
344 }
345
347 void clearSurfaces() {
348 mTargetSurfaces.clear();
349 }
350
352 boolean createSession() {
353 if (mCameraDevice == null)
354 return false;
355
356 try {
357 List<OutputConfiguration> outputs = new ArrayList<>(mTargetSurfaces.size());
358 for (Surface surface : mTargetSurfaces)
359 outputs.add(new OutputConfiguration(surface));
360
361 mCurrentSessionCallback = new CameraCaptureSessionStateCallback(this);
362 SessionConfiguration config = new SessionConfiguration(
363 SessionConfiguration.SESSION_REGULAR, outputs,
364 mBackgroundHandler::post, mCurrentSessionCallback);
365 mCameraDevice.createCaptureSession(config);
366 return true;
367 } catch (Exception exception) {
368 Log.w(LOG_TAG, "Failed to create a capture session:" + exception);
369 }
370 return false;
371 }
372
374 boolean start() {
375 if (mCameraDevice == null)
376 return false;
377
378 if (mCaptureSession == null)
379 return false;
380
381 try {
382 synchronized (mSyncedMembers) {
383 setRepeatingRequestToPreview();
384 if (!mSyncedMembers.isTakingStillPhoto())
385 mSyncedMembers.mState = SyncedMembers.State.Started;
386 }
387 return true;
388 } catch (CameraAccessException exception) {
389 Log.w(LOG_TAG, "Failed to start preview:" + exception);
390 }
391 return false;
392 }
393
395 void stopAndClose() {
396 // Local closedLatch awaited outside the lock below, since mDeviceClosedLatch
397 // can be reassigned by a later open/close
398 CountDownLatch closedLatch = null;
399
400 // Report in-flight still capture as failed
401 String abortedStillPhotoCameraId = null;
402
403 synchronized (mSyncedMembers) {
404 try {
405 if (mSyncedMembers.isTakingStillPhoto() || mSyncedMembers.mStillCaptureDeferred)
406 abortedStillPhotoCameraId = mCameraId;
407 if (null != mCaptureSession) {
408 mCaptureSession.close();
409 mCaptureSession = null;
410 }
411 if (null != mCameraDevice) {
412 closedLatch = new CountDownLatch(1);
413 mDeviceClosedLatch = closedLatch;
414 mCameraDevice.close(); // async
415 mCameraDevice = null;
416 }
417 mCameraId = "";
418 mTargetSurfaces.clear();
419 } catch (Exception exception) {
420 Log.w(LOG_TAG, "Failed to stop and close:" + exception);
421 }
422 mSyncedMembers.mState = SyncedMembers.State.Idle;
423 mSyncedMembers.mStillCaptureDeferred = false;
424 }
425
426 if (abortedStillPhotoCameraId != null)
427 onStillPhotoCaptureFailed(abortedStillPhotoCameraId);
428
429 // Wait for onClosed() so background thread can be stopped
430 if (closedLatch != null) {
431 try {
432 if (!closedLatch.await(2, TimeUnit.SECONDS))
433 // NOTE: Fallthrough can race if onClosed never fires
434 Log.w(LOG_TAG, "Timed out waiting for camera device to close");
435 } catch (InterruptedException exception) {
436 Thread.currentThread().interrupt();
437 }
438 }
439 }
440
441 // Can by StillPhotoPrecaptureCallback on background thread in order to finalize a still photo
442 // capture.
443 void finalizeStillPhoto(CameraSettings cameraSettings) throws CameraAccessException
444 {
445 final CaptureRequest.Builder requestBuilder =
446 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
447 requestBuilder.addTarget(mStillPhotoImageReader.getSurface());
448 requestBuilder.set(
449 CaptureRequest.CONTROL_CAPTURE_INTENT,
450 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
451
452 applyStillPhotoSettingsToCaptureRequestBuilder(
453 requestBuilder,
454 cameraSettings);
455
456 mCaptureSession.capture(
457 requestBuilder.build(),
458 new CameraStillPhotoFinalizerCallback(this),
459 mBackgroundHandler);
460 }
461
462 // This function starts the process of taking a still photo. The final outputted image will
463 // be returned in the still photo image reader.
464 //
465 // Capturing a still photo requires the following steps:
466 // - We need to submit two requests: One repeating request and one single instant request.
467 // The single request triggers the calibration of auto-focus and/or auto-exposure,
468 // depending on what settings are set for the camera. This calibration takes some time,
469 // and we need to wait for these to settle. The logic for waiting for these to settle
470 // happens in the repeating request using the class StillPhotoPrecaptureCallback.
471 // - When the auto-focus and/or auto-exposure has settled, the StillPhotoPrecaptureCallback
472 // will submit a final request to finalize the photo. When the photo is finalized,
473 // we transition back into regular previewing.
474 // - Supporting auto flash is a corner case. When running auto-flash, we might get the result
475 // that no flash is needed, in which case we continue to finalizing the still photo
476 // as usual. If we receive the result that flash is required, we resubmit the still photo
477 // capture commands all over again as if we are using QCamera::FlashModeOn.
479 void beginStillPhotoCapture() {
480 boolean abortCapture = false;
481 synchronized (mSyncedMembers) {
482 if (mSyncedMembers.mState == SyncedMembers.State.Reconfiguring
483 || mSyncedMembers.mStillCaptureDeferred) {
484 mSyncedMembers.mStillCaptureDeferred = true;
485 return;
486 }
487 if (mSyncedMembers.isTakingStillPhoto()) {
488 // Queuing multiple still photos is not implemented.
489 // TODO: We might have to signal to QImageCapture here that capturing failed.
490 Log.w(
491 LOG_TAG,
492 "beginStillPhotoCapture() was called on camera backend while there " +
493 "is already a still photo in progress. This is not supported. Likely Qt " +
494 "developer bug.");
495 return;
496 }
497
498 if (!mSyncedMembers.isStarted()) {
499 Log.w(LOG_TAG, "Can't begin still photo capture, camera is not started");
500 abortCapture = true;
501 } else {
502 mSyncedMembers.mState = SyncedMembers.State.Capturing;
503 }
504 }
505
506 if (abortCapture) {
507 onStillPhotoCaptureFailed(mCameraId);
508 return;
509 }
510
511 final CameraSettings cameraSettings = atomicCameraSettingsCopy();
512 try {
513 submitNewStillPhotoCapture(cameraSettings);
514 } catch (Exception e) {
515 Log.w(LOG_TAG, "Failed to begin still photo capture: " + e);
516 e.printStackTrace();
517 synchronized (mSyncedMembers) {
518 mSyncedMembers.endStillPhoto();
519 }
520 onStillPhotoCaptureFailed(mCameraId);
521 // TODO: Try to go back to previewing if applicable. If that fails too, shut down
522 // camera session and report QCamera as inactive.
523 }
524 }
525
526 void submitNewStillPhotoCapture(CameraSettings cameraSettings) throws CameraAccessException
527 {
528 CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
529 CameraDevice.TEMPLATE_STILL_CAPTURE);
530 // Any in-between frames gathered while waiting for still photo, can be sent into
531 // the preview ImageReader.
532 requestBuilder.addTarget(mPreviewImageReader.getSurface());
533
534 applyStillPhotoSettingsToCaptureRequestBuilder(
535 requestBuilder,
536 cameraSettings);
537
538 // We need to trigger the auto-focus and auto-exposure mechanism in a single capture
539 // request, but waiting for it to settle happens in the repeating request.
540 // If configuration ended up with AF_MODE_AUTO, this implies we should trigger the
541 // auto focus to lock in.
542 final boolean triggerAutoFocus = requestBuilder.get(CaptureRequest.CONTROL_AF_MODE)
543 == CaptureResult.CONTROL_AF_MODE_AUTO;
544
545 final Integer aeMode = requestBuilder.get(CaptureRequest.CONTROL_AE_MODE);
546 boolean triggerAutoExposure = aeMode != null
547 && aeMode != CaptureResult.CONTROL_AE_MODE_OFF;
548
549 final CameraStillPhotoPrecaptureCallback precaptureCallback =
550 new CameraStillPhotoPrecaptureCallback(
551 this,
552 cameraSettings,
553 triggerAutoFocus,
554 triggerAutoExposure);
555
556 mCaptureSession.setRepeatingRequest(
557 requestBuilder.build(),
558 precaptureCallback,
559 mBackgroundHandler);
560
561 // Once we have prepared the repeating request that will wait, we re-use the
562 // request-builder and modify it to include the trigger commands, and then submit
563 // it as a one-time request.
564 if (triggerAutoFocus) {
565 requestBuilder.set(
566 CaptureRequest.CONTROL_AF_TRIGGER,
567 CaptureRequest.CONTROL_AF_TRIGGER_START);
568 }
569 if (triggerAutoExposure) {
570 requestBuilder.set(
571 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
572 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
573 }
574
575 // TODO: We should have a callback here that can track if still photo fails
576 mCaptureSession.capture(requestBuilder.build(), null, mBackgroundHandler);
577 }
578
580 void saveExifToFile(String path)
581 {
582 if (mExifDataHandler != null)
583 mExifDataHandler.save(path);
584 else
585 Log.e(LOG_TAG, "No Exif data that could be saved to " + path);
586 }
587
588 private Rect getScalerCropRegion(float zoomFactor)
589 {
590 Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
591 float zoomRatio = 1.0f;
592 if (zoomFactor != 0.0f)
593 zoomRatio = 1.0f / zoomFactor;
594
595 int croppedWidth = activePixels.width() - (int)(activePixels.width() * zoomRatio);
596 int croppedHeight = activePixels.height() - (int)(activePixels.height() * zoomRatio);
597 return new Rect(croppedWidth/2, croppedHeight/2, activePixels.width() - croppedWidth/2,
598 activePixels.height() - croppedHeight/2);
599 }
600
601 private void applyZoomSettingsToRequestBuilder(CaptureRequest.Builder requBuilder, float zoomFactor)
602 {
603 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) {
604 requBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion(zoomFactor));
605 } else {
606 requBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
607 }
608 }
609
611 void zoomTo(float factor)
612 {
613 synchronized (mSyncedMembers) {
614 mSyncedMembers.mCameraSettings.mZoomFactor = factor;
615
616 if (!mSyncedMembers.isStarted()) {
617 // Camera capture has not begun. Zoom will be applied during start().
618 return;
619 }
620
621 // TODO: In the future we can call .setRepeatingRequestToPreview() directly,
622 // which will recreate the request builder as necessary and apply it with all
623 // settings consistently.
624 applyZoomSettingsToRequestBuilder(mPreviewRequestBuilder, factor);
625 mPreviewRequest = mPreviewRequestBuilder.build();
626
627 if (mSyncedMembers.isTakingStillPhoto()) {
628 // Don't set any request if we are in the middle of taking a still photo.
629 // The setting will be applied to the preview after the still photo routine is done.
630 return;
631 }
632 try {
633 mCaptureSession.setRepeatingRequest(
634 mPreviewRequest,
635 mPreviewCaptureCallback,
636 mBackgroundHandler);
637 } catch (Exception exception) {
638 Log.w(LOG_TAG, "Failed to set zoom:" + exception);
639 }
640 }
641 }
642
643 // As described in QPlatformCamera::setFocusMode, this function must apply the focus-distance
644 // whenever the new QCamera::focusMode is set to Manual.
645 // For now, the QtCamera2 implementation only supports Auto and Manual FocusModes.
647 void setFocusMode(int newFocusMode)
648 {
649 // TODO: In the future, not all QCamera::FocusModes will have a 1:1 mapping to the
650 // CONTROL_AF_MODE values. We will need a general solution to translate between
651 // QCamera::FocusModes and the relevant Android Camera2 properties.
652
653 // Expand with more values in the future.
654 // Translate into the corresponding CONTROL_AF_MODE.
655 int newAfMode = 0;
656 if (newFocusMode == 0) // FocusModeAuto
657 newAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
658 else if (newFocusMode == 5) // FocusModeManual
659 newAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
660 else {
661 Log.d(
662 LOG_TAG,
663 "received a QCamera::FocusMode from native code that is not recognized. " +
664 "Likely Qt developer bug. Ignoring.");
665 return;
666 }
667
668 // TODO: Ideally this should check if newAfMode is supported through isAfModeAvailable()
669 // but in some situation, mCameraId will be null and therefore isAfModeAvailable will always
670 // return false. One example of this is during QCamera::setCameraDevice.
671 /*
672 if (!isAfModeAvailable(newAfMode)) {
673 Log.d(
674 LOG_TAG,
675 "received a QCamera::FocusMode from native code that is not reported as supported. " +
676 "Likely Qt developer bug. Ignoring.");
677 return;
678 }
679 */
680
681 synchronized (mSyncedMembers) {
682 mSyncedMembers.mCameraSettings.mAFMode = newAfMode;
683
684 // If the camera is not in the started state yet, we skip activating focus-mode here.
685 // Instead it will get applied when the camera is initialized.
686 if (!mSyncedMembers.isStarted())
687 return;
688
689 applyFocusSettingsToCaptureRequestBuilder(
690 mPreviewRequestBuilder,
691 mSyncedMembers.mCameraSettings,
692 false);
693 mPreviewRequest = mPreviewRequestBuilder.build();
694
695 if (mSyncedMembers.isTakingStillPhoto()) {
696 // Don't set any request if we are in the middle of taking a still photo.
697 // The setting will be applied to the preview after the still photo routine is done.
698 return;
699 }
700 try {
701 mCaptureSession.setRepeatingRequest(
702 mPreviewRequest,
703 mPreviewCaptureCallback,
704 mBackgroundHandler);
705 } catch (Exception exception) {
706 Log.w(LOG_TAG, "Failed to set focus mode:" + exception);
707 }
708 }
709 }
710
712 void setFlashMode(String flashMode)
713 {
714 synchronized (mSyncedMembers) {
715 int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
716 if (flashModeValue < 0) {
717 Log.w(LOG_TAG, "Unknown flash mode");
718 return;
719 }
720 mSyncedMembers.mCameraSettings.mStillPhotoFlashMode = flashModeValue;
721 }
722 }
723
724 // Sets the focus distance of the camera. Input is the same that accepted by the
725 // QCamera public API. Accepts a float in the range 0,1. Where 0 means as close as possible,
726 // and 1 means infinity.
727 //
728 // This should never be called if the device specifies focus-distance as unsupported.
730 public void setFocusDistance(float distanceInput)
731 {
732 if (distanceInput < 0.f || distanceInput > 1.f) {
733 Log.w(
734 LOG_TAG,
735 "received out-of-bounds value when setting camera focus-distance. " +
736 "Likely Qt developer bug. Ignoring.");
737 return;
738 }
739
740 // TODO: Add error handling to check if current mCameraId supports setting focus-distance.
741 // See setFocusMode relevant issue.
742
743 synchronized (mSyncedMembers) {
744 mSyncedMembers.mCameraSettings.mFocusDistance = distanceInput;
745
746 // If the camera is not in the started state yet, we skip applying any camera-controls
747 // here. It will get applied once the camera is ready.
748 if (!mSyncedMembers.isStarted())
749 return;
750
751 // If we are currently in QCamera::FocusModeManual, we apply the focus distance
752 // immediately. Otherwise, we store the value and apply it during setFocusMode(Manual).
753 if (mSyncedMembers.mCameraSettings.mAFMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
754 applyFocusSettingsToCaptureRequestBuilder(
755 mPreviewRequestBuilder,
756 mSyncedMembers.mCameraSettings,
757 false);
758
759 mPreviewRequest = mPreviewRequestBuilder.build();
760
761 if (mSyncedMembers.isTakingStillPhoto()) {
762 // Don't set any request if we are in the middle of taking a still photo.
763 // The setting will be applied to the preview after the still photo routine is done.
764 return;
765 }
766
767 try {
768 mCaptureSession.setRepeatingRequest(
769 mPreviewRequest,
770 mPreviewCaptureCallback,
771 mBackgroundHandler);
772 } catch (Exception exception) {
773 Log.w(LOG_TAG, "Failed to set focus distance:" + exception);
774 }
775 }
776 }
777 }
778
779 private int getTorchModeValue(boolean mode)
780 {
781 return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
782 }
783
785 void setTorchMode(boolean torchMode)
786 {
787 synchronized (mSyncedMembers) {
788 mSyncedMembers.mCameraSettings.mTorchMode = getTorchModeValue(torchMode);
789
790 if (mSyncedMembers.isStarted()) {
791 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mSyncedMembers.mCameraSettings.mTorchMode);
792 mPreviewRequest = mPreviewRequestBuilder.build();
793
794 if (mSyncedMembers.isTakingStillPhoto()) {
795 // Don't set any request if we are in the middle of taking a still photo.
796 // The setting will be applied to the preview after the still photo routine is done.
797 return;
798 }
799
800 try {
801 mCaptureSession.setRepeatingRequest(
802 mPreviewRequest,
803 mPreviewCaptureCallback,
804 mBackgroundHandler);
805 } catch (Exception exception) {
806 Log.w(LOG_TAG, "Failed to set flash mode:" + exception);
807 }
808 }
809 }
810 }
811
812 // Called indirectly from C++ when the QCamera goes active.
813 // Called again from Java camera background thread when a still photo is done.
815 void setRepeatingRequestToPreview() throws CameraAccessException {
816 synchronized (mSyncedMembers) {
817 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
818 mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());
819
820 applyPreviewSettingsToCaptureRequestBuilder(
821 mPreviewRequestBuilder,
822 mSyncedMembers.mCameraSettings);
823
824 mPreviewRequest = mPreviewRequestBuilder.build();
825 mCaptureSession.setRepeatingRequest(
826 mPreviewRequest,
827 mPreviewCaptureCallback,
828 mBackgroundHandler);
829 }
830 }
831
832 private void applyStillPhotoSettingsToCaptureRequestBuilder(
833 CaptureRequest.Builder requestBuilder,
834 CameraSettings cameraSettings)
835 {
836 requestBuilder.set(
837 CaptureRequest.CONTROL_CAPTURE_INTENT,
838 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
839 // Hint for the camera to use automatic modes for auto-focus, auto-exposure and
840 // white-balance where applicable.
841 requestBuilder.set(
842 CaptureRequest.CONTROL_MODE,
843 CaptureRequest.CONTROL_MODE_AUTO);
844 // TODO: We don't support any other exposure modes (such as manual control) yet. Will need
845 // to modify this in the future if we do.
846 requestBuilder.set(
847 CaptureRequest.CONTROL_AE_MODE,
848 CaptureRequest.CONTROL_AE_MODE_ON);
849
850 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
851
852 applyFocusSettingsToCaptureRequestBuilder(
853 requestBuilder,
854 cameraSettings,
855 true);
856
857 // Ideally we would pass AE_MODE_ON_ALWAYS_FLASH straight to the camera and let it
858 // control the flash unit. This has proven unreliable during testing. Instead we use
859 // regular CONTROL_AE_MODE_ON and force the flash on.
860 if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
861 requestBuilder.set(
862 CaptureRequest.CONTROL_AE_MODE,
863 CaptureRequest.CONTROL_AE_MODE_ON);
864 // Ideally, this should be set to SINGLE, only when we are finalizing the capture.
865 // However, this causes an issue on some Motorola devices where the flash will have
866 // wrong timing compared to the capture, and we will end up with no flash in the final
867 // photo.
868 requestBuilder.set(
869 CaptureRequest.FLASH_MODE,
870 CaptureRequest.FLASH_MODE_TORCH);
871 } else if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) {
872 requestBuilder.set(
873 CaptureRequest.CONTROL_AE_MODE,
874 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
875 }
876 }
877
878 private void applyPreviewSettingsToCaptureRequestBuilder(
879 CaptureRequest.Builder requestBuilder,
880 CameraSettings cameraSettings)
881 {
882 requestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
883
884 applyFocusSettingsToCaptureRequestBuilder(
885 requestBuilder,
886 cameraSettings,
887 false);
888
889 // TODO: Check if AE_MODE_ON is available
890 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
891 requestBuilder.set(CaptureRequest.FLASH_MODE, cameraSettings.mTorchMode);
892
893 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
894 if (cameraSettings.mFpsRange != null) {
895 requestBuilder.set(
896 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
897 cameraSettings.mFpsRange);
898 }
899
900 // TODO: This should likely not be set because trigger-events should only be submitted
901 // once. Meanwhile, this request will be used for preview which is repeating.
902 mPreviewRequestBuilder.set(
903 CaptureRequest.CONTROL_AF_TRIGGER,
904 CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
905 mPreviewRequestBuilder.set(
906 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
907 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
908 }
909
910 // If taking still photo, remember to trigger auto-focus calibration if CONTROL_AF_MODE is set
911 // to CONTROL_AF_MODE_AUTO.
912 void applyFocusSettingsToCaptureRequestBuilder(
913 CaptureRequest.Builder requestBuilder,
914 CameraSettings cameraSettings,
915 boolean stillPhoto)
916 {
917 int desiredAfMode = cameraSettings.mAFMode;
918 // During still photo, If the camera settings is set to CONTINUOUS_PICTURE, this is an
919 // indication that we are in QCamera::FocusModeAuto. In which case we should be using
920 // AF_MODE_AUTO, which lets us lock in focus once and keep it there until still photo
921 // is done.
922 if (stillPhoto && desiredAfMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
923 desiredAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
924 }
925
926 if (!isAfModeAvailable(desiredAfMode)) {
927 // If we don't support our desired AF_MODE, fallback to AF_MODE_OFF if that is
928 // available. Otherwise don't set any focus-mode, leave it as default and
929 // undefined state. Note: Setting CONTROL_AF_MODE to null is illegal and will cause an
930 // exception thrown.
931 if (isAfModeAvailable(CaptureRequest.CONTROL_AF_MODE_OFF)) {
932 requestBuilder.set(
933 CaptureRequest.CONTROL_AF_MODE,
934 CaptureRequest.CONTROL_AF_MODE_OFF);
935 }
936
937 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, null);
938 return;
939 }
940
941 requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, desiredAfMode);
942
943 // Set correct lens focus distance if we are in QCamera::FocusModeManual
944 if (desiredAfMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
945 final float lensFocusDistance = calcLensFocusDistanceFromQCameraFocusDistance(
946 cameraSettings.mFocusDistance);
947 if (lensFocusDistance < 0) {
948 Log.w(
949 LOG_TAG,
950 "Tried to apply FocusModeManual on a camera that doesn't support "
951 + "setting lens distance. Likely Qt developer bug. Ignoring.");
952 } else {
953 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, lensFocusDistance);
954 }
955 } else {
956 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, null);
957 }
958 }
959
960 // Calculates the CaptureRequest.LENS_FOCUS_DISTANCE equivalent given a QCamera::focusDistance
961 // value. Returns -1 on failure, such as if camera does not support setting manual focus
962 // distance.
963 private float calcLensFocusDistanceFromQCameraFocusDistance(float qCameraFocusDistance) {
964 float lensMinimumFocusDistance =
965 mVideoDeviceManager.getLensInfoMinimumFocusDistance(mCameraId);
966 if (lensMinimumFocusDistance <= 0)
967 return -1;
968
969 // Input is 0 to 1, with 0 meaning as close as possible.
970 // Android Camera2 expects it to be in the range [0, minimumFocusDistance]
971 // where higher values means closer to the camera and 0 means as far away as possible.
972 // We need to map to this range.
973 return (1.f - qCameraFocusDistance) * lensMinimumFocusDistance;
974 }
975
976 // Helper function to check if a given CaptureRequest.CONTROL_AF_MODE is supported on this
977 // device
978 private boolean isAfModeAvailable(int afMode) {
979 if (mVideoDeviceManager == null || mCameraId == null || mCameraId.isEmpty())
980 return false;
981 return mVideoDeviceManager.isAfModeAvailable(mCameraId, afMode);
982 }
983
984 // AF_STATE_NOT_FOCUSED_LOCKED implies we tried to calibrate the auto-focus, but failed
985 // to establish focus and the hardware has now given up and locked the focus.
986 static boolean afStateIsReadyForCapture(Integer afState) {
987 return afState == null
988 || afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
989 || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
990 }
991
992 static boolean aeStateIsReadyForCapture(Integer aeState) {
993 return aeState == null
994 || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
995 || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
996 }
997}
\inmodule QtGui
QPainter Context
#define LOG_TAG
Definition extract.cpp:14
static void onPreviewFrameAvailable(JNIEnv *env, jobject obj, jstring cameraId, QtJniTypes::Image image)
static void onCaptureSessionFailed(JNIEnv *env, jobject obj, jstring cameraId, jint reason, jlong framenumber)
static void onSessionClosed(JNIEnv *env, jobject obj, jstring cameraId)
static void onStillPhotoCaptureFailed(JNIEnv *env, jobject obj, jstring cameraId)
static void onStillReaderSwapFailed(JNIEnv *env, jobject obj, jstring cameraId)
static void onCameraError(JNIEnv *env, jobject obj, jstring cameraId, jint error)
static void onCaptureSessionConfigureFailed(JNIEnv *env, jobject obj, jstring cameraId)
static void onCameraOpened(JNIEnv *env, jobject obj, jstring cameraId)
static void onSessionActive(JNIEnv *env, jobject obj, jstring cameraId)
static void onCameraDisconnect(JNIEnv *env, jobject obj, jstring cameraId)
static void onCaptureSessionConfigured(JNIEnv *env, jobject obj, jstring cameraId)
static void onStillPhotoAvailable(JNIEnv *env, jobject obj, jstring cameraId, QtJniTypes::Image image)
static const QString context()
Definition java.cpp:396
Catch::Generators::GeneratorWrapper< T > handler(Catch::Generators::GeneratorWrapper< T > &&generator)
Returns a generator wrapping generator that ensures that changes its semantics so that the first call...
QImageReader reader("image.png")
[1]
DBusConnection const char DBusError * error
EGLConfig config
[3]
OutputConfiguration
GLenum mode
GLuint start
GLfloat GLfloat f
[26]
GLint GLsizei width
GLint void * img
Definition qopenglext.h:233
GLsizei const GLchar *const * path
@ Handler
QFrame frame
[0]
file open(QIODevice::ReadOnly)
QNetworkRequest request(url)
[0]