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