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