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