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;
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 =
224 ImageReader.newInstance(width, height, ImageFormat.JPEG, MaxNumberFrames);
225 mStillPhotoImageReader.setOnImageAvailableListener(mOnStillPhotoAvailableListener, mBackgroundHandler);
226 addSurface(mStillPhotoImageReader.getSurface());
227 }
228
229 private void setFrameRate(int minFrameRate, int maxFrameRate) {
230 synchronized (mSyncedMembers) {
231 if (minFrameRate <= 0 || maxFrameRate <= 0)
232 mSyncedMembers.mCameraSettings.mFpsRange = null;
233 else
234 mSyncedMembers.mCameraSettings.mFpsRange = new Range<>(minFrameRate, maxFrameRate);
235 }
236 }
237
238 boolean addSurface(Surface surface) {
239 if (mTargetSurfaces.contains(surface))
240 return true;
241
242 return mTargetSurfaces.add(surface);
243 }
244
245 boolean removeSurface(Surface surface) {
246 return mTargetSurfaces.remove(surface);
247 }
248
250 void clearSurfaces() {
251 mTargetSurfaces.clear();
252 }
253
255 boolean createSession() {
256 if (mCameraDevice == null)
257 return false;
258
259 try {
260 // TODO: This API is deprecated and we should transition to the more modern method
261 // overload. See QTBUG-134750.
262 mCameraDevice.createCaptureSession(mTargetSurfaces, mCaptureStateCallback, mBackgroundHandler);
263 return true;
264 } catch (Exception exception) {
265 Log.w(LOG_TAG, "Failed to create a capture session:" + exception);
266 }
267 return false;
268 }
269
271 boolean start() {
272 if (mCameraDevice == null)
273 return false;
274
275 if (mCaptureSession == null)
276 return false;
277
278 try {
279 synchronized (mSyncedMembers) {
280 setRepeatingRequestToPreview();
281 mSyncedMembers.mIsStarted = true;
282 }
283 return true;
284 } catch (CameraAccessException exception) {
285 Log.w(LOG_TAG, "Failed to start preview:" + exception);
286 }
287 return false;
288 }
289
291 void stopAndClose() {
292 synchronized (mSyncedMembers) {
293 try {
294 if (null != mCaptureSession) {
295 mCaptureSession.close();
296 mCaptureSession = null;
297 }
298 if (null != mCameraDevice) {
299 mCameraDevice.close();
300 mCameraDevice = null;
301 }
302 mCameraId = "";
303 mTargetSurfaces.clear();
304 } catch (Exception exception) {
305 Log.w(LOG_TAG, "Failed to stop and close:" + exception);
306 }
307 mSyncedMembers.mIsStarted = false;
308 mSyncedMembers.mIsTakingStillPhoto = false;
309 }
310 }
311
312 // Can by StillPhotoPrecaptureCallback on background thread in order to finalize a still photo
313 // capture.
314 void finalizeStillPhoto(CameraSettings cameraSettings) throws CameraAccessException
315 {
316 final CaptureRequest.Builder requestBuilder =
317 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
318 requestBuilder.addTarget(mStillPhotoImageReader.getSurface());
319 requestBuilder.set(
320 CaptureRequest.CONTROL_CAPTURE_INTENT,
321 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
322
323 applyStillPhotoSettingsToCaptureRequestBuilder(
324 requestBuilder,
325 cameraSettings);
326
327 mCaptureSession.capture(
328 requestBuilder.build(),
329 new CameraStillPhotoFinalizerCallback(this),
330 mBackgroundHandler);
331 }
332
333 // This function starts the process of taking a still photo. The final outputted image will
334 // be returned in the still photo image reader.
335 //
336 // Capturing a still photo requires the following steps:
337 // - We need to submit two requests: One repeating request and one single instant request.
338 // The single request triggers the calibration of auto-focus and/or auto-exposure,
339 // depending on what settings are set for the camera. This calibration takes some time,
340 // and we need to wait for these to settle. The logic for waiting for these to settle
341 // happens in the repeating request using the class StillPhotoPrecaptureCallback.
342 // - When the auto-focus and/or auto-exposure has settled, the StillPhotoPrecaptureCallback
343 // will submit a final request to finalize the photo. When the photo is finalized,
344 // we transition back into regular previewing.
345 // - Supporting auto flash is a corner case. When running auto-flash, we might get the result
346 // that no flash is needed, in which case we continue to finalizing the still photo
347 // as usual. If we receive the result that flash is required, we resubmit the still photo
348 // capture commands all over again as if we are using QCamera::FlashModeOn.
350 void beginStillPhotoCapture() {
351 synchronized (mSyncedMembers) {
352 if (mSyncedMembers.mIsTakingStillPhoto) {
353 // Queuing multiple still photos is not implemented.
354 // TODO: We might have to signal to QImageCapture here that capturing failed.
355 Log.w(
356 LOG_TAG,
357 "beginStillPhotoCapture() was called on camera backend while there " +
358 "is already a still photo in progress. This is not supported. Likely Qt " +
359 "developer bug.");
360 return;
361 }
362 }
363
364 final CameraSettings cameraSettings = atomicCameraSettingsCopy();
365 try {
366 submitNewStillPhotoCapture(cameraSettings);
367 } catch (CameraAccessException e) {
368 Log.w(LOG_TAG, "Cannot get access to the camera: " + e);
369 e.printStackTrace();
370 onStillPhotoCaptureFailed(mCameraId);
371 // TODO: Try to go back to previewing if applicable. If that fails too, shut down
372 // camera session and report QCamera as inactive.
373 }
374 }
375
376 void submitNewStillPhotoCapture(CameraSettings cameraSettings) throws CameraAccessException
377 {
378 CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
379 CameraDevice.TEMPLATE_STILL_CAPTURE);
380 // Any in-between frames gathered while waiting for still photo, can be sent into
381 // the preview ImageReader.
382 requestBuilder.addTarget(mPreviewImageReader.getSurface());
383
384 applyStillPhotoSettingsToCaptureRequestBuilder(
385 requestBuilder,
386 cameraSettings);
387
388 // We need to trigger the auto-focus and auto-exposure mechanism in a single capture
389 // request, but waiting for it to settle happens in the repeating request.
390 // If configuration ended up with AF_MODE_AUTO, this implies we should trigger the
391 // auto focus to lock in.
392 final boolean triggerAutoFocus = requestBuilder.get(CaptureRequest.CONTROL_AF_MODE)
393 == CaptureResult.CONTROL_AF_MODE_AUTO;
394
395 final Integer aeMode = requestBuilder.get(CaptureRequest.CONTROL_AE_MODE);
396 boolean triggerAutoExposure = aeMode != null
397 && aeMode != CaptureResult.CONTROL_AE_MODE_OFF;
398
399 final CameraStillPhotoPrecaptureCallback precaptureCallback =
400 new CameraStillPhotoPrecaptureCallback(
401 this,
402 cameraSettings,
403 triggerAutoFocus,
404 triggerAutoExposure);
405
406 mCaptureSession.setRepeatingRequest(
407 requestBuilder.build(),
408 precaptureCallback,
409 mBackgroundHandler);
410
411 // Once we have prepared the repeating request that will wait, we re-use the
412 // request-builder and modify it to include the trigger commands, and then submit
413 // it as a one-time request.
414 if (triggerAutoFocus) {
415 requestBuilder.set(
416 CaptureRequest.CONTROL_AF_TRIGGER,
417 CaptureRequest.CONTROL_AF_TRIGGER_START);
418 }
419 if (triggerAutoExposure) {
420 requestBuilder.set(
421 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
422 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
423 }
424
425 // TODO: We should have a callback here that can track if still photo fails
426 mCaptureSession.capture(
427 requestBuilder.build(),
428 null,
429 mBackgroundHandler);
430
431 synchronized (mSyncedMembers) {
432 mSyncedMembers.mIsTakingStillPhoto = true;
433 }
434 }
435
437 void saveExifToFile(String path)
438 {
439 if (mExifDataHandler != null)
440 mExifDataHandler.save(path);
441 else
442 Log.e(LOG_TAG, "No Exif data that could be saved to " + path);
443 }
444
445 private Rect getScalerCropRegion(float zoomFactor)
446 {
447 Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
448 float zoomRatio = 1.0f;
449 if (zoomFactor != 0.0f)
450 zoomRatio = 1.0f / zoomFactor;
451
452 int croppedWidth = activePixels.width() - (int)(activePixels.width() * zoomRatio);
453 int croppedHeight = activePixels.height() - (int)(activePixels.height() * zoomRatio);
454 return new Rect(croppedWidth/2, croppedHeight/2, activePixels.width() - croppedWidth/2,
455 activePixels.height() - croppedHeight/2);
456 }
457
458 private void applyZoomSettingsToRequestBuilder(CaptureRequest.Builder requBuilder, float zoomFactor)
459 {
460 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) {
461 requBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion(zoomFactor));
462 } else {
463 requBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
464 }
465 }
466
468 void zoomTo(float factor)
469 {
470 synchronized (mSyncedMembers) {
471 mSyncedMembers.mCameraSettings.mZoomFactor = factor;
472
473 if (!mSyncedMembers.mIsStarted) {
474 // Camera capture has not begun. Zoom will be applied during start().
475 return;
476 }
477
478 // TODO: In the future we can call .setRepeatingRequestToPreview() directly,
479 // which will recreate the request builder as necessary and apply it with all
480 // settings consistently.
481 applyZoomSettingsToRequestBuilder(mPreviewRequestBuilder, factor);
482 mPreviewRequest = mPreviewRequestBuilder.build();
483
484 if (mSyncedMembers.mIsTakingStillPhoto) {
485 // Don't set any request if we are in the middle of taking a still photo.
486 // The setting will be applied to the preview after the still photo routine is done.
487 return;
488 }
489 try {
490 mCaptureSession.setRepeatingRequest(
491 mPreviewRequest,
492 mPreviewCaptureCallback,
493 mBackgroundHandler);
494 } catch (Exception exception) {
495 Log.w(LOG_TAG, "Failed to set zoom:" + exception);
496 }
497 }
498 }
499
500 // As described in QPlatformCamera::setFocusMode, this function must apply the focus-distance
501 // whenever the new QCamera::focusMode is set to Manual.
502 // For now, the QtCamera2 implementation only supports Auto and Manual FocusModes.
504 void setFocusMode(int newFocusMode)
505 {
506 // TODO: In the future, not all QCamera::FocusModes will have a 1:1 mapping to the
507 // CONTROL_AF_MODE values. We will need a general solution to translate between
508 // QCamera::FocusModes and the relevant Android Camera2 properties.
509
510 // Expand with more values in the future.
511 // Translate into the corresponding CONTROL_AF_MODE.
512 int newAfMode = 0;
513 if (newFocusMode == 0) // FocusModeAuto
514 newAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
515 else if (newFocusMode == 5) // FocusModeManual
516 newAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
517 else {
518 Log.d(
519 LOG_TAG,
520 "received a QCamera::FocusMode from native code that is not recognized. " +
521 "Likely Qt developer bug. Ignoring.");
522 return;
523 }
524
525 // TODO: Ideally this should check if newAfMode is supported through isAfModeAvailable()
526 // but in some situation, mCameraId will be null and therefore isAfModeAvailable will always
527 // return false. One example of this is during QCamera::setCameraDevice.
528 /*
529 if (!isAfModeAvailable(newAfMode)) {
530 Log.d(
531 LOG_TAG,
532 "received a QCamera::FocusMode from native code that is not reported as supported. " +
533 "Likely Qt developer bug. Ignoring.");
534 return;
535 }
536 */
537
538 synchronized (mSyncedMembers) {
539 mSyncedMembers.mCameraSettings.mAFMode = newAfMode;
540
541 // If the camera is not in the started state yet, we skip activating focus-mode here.
542 // Instead it will get applied when the camera is initialized.
543 if (!mSyncedMembers.mIsStarted)
544 return;
545
546 applyFocusSettingsToCaptureRequestBuilder(
547 mPreviewRequestBuilder,
548 mSyncedMembers.mCameraSettings,
549 false);
550 mPreviewRequest = mPreviewRequestBuilder.build();
551
552 if (mSyncedMembers.mIsTakingStillPhoto) {
553 // Don't set any request if we are in the middle of taking a still photo.
554 // The setting will be applied to the preview after the still photo routine is done.
555 return;
556 }
557 try {
558 mCaptureSession.setRepeatingRequest(
559 mPreviewRequest,
560 mPreviewCaptureCallback,
561 mBackgroundHandler);
562 } catch (Exception exception) {
563 Log.w(LOG_TAG, "Failed to set focus mode:" + exception);
564 }
565 }
566 }
567
569 void setFlashMode(String flashMode)
570 {
571 synchronized (mSyncedMembers) {
572 int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
573 if (flashModeValue < 0) {
574 Log.w(LOG_TAG, "Unknown flash mode");
575 return;
576 }
577 mSyncedMembers.mCameraSettings.mStillPhotoFlashMode = flashModeValue;
578 }
579 }
580
581 // Sets the focus distance of the camera. Input is the same that accepted by the
582 // QCamera public API. Accepts a float in the range 0,1. Where 0 means as close as possible,
583 // and 1 means infinity.
584 //
585 // This should never be called if the device specifies focus-distance as unsupported.
587 public void setFocusDistance(float distanceInput)
588 {
589 if (distanceInput < 0.f || distanceInput > 1.f) {
590 Log.w(
591 LOG_TAG,
592 "received out-of-bounds value when setting camera focus-distance. " +
593 "Likely Qt developer bug. Ignoring.");
594 return;
595 }
596
597 // TODO: Add error handling to check if current mCameraId supports setting focus-distance.
598 // See setFocusMode relevant issue.
599
600 synchronized (mSyncedMembers) {
601 mSyncedMembers.mCameraSettings.mFocusDistance = distanceInput;
602
603 // If the camera is not in the started state yet, we skip applying any camera-controls
604 // here. It will get applied once the camera is ready.
605 if (!mSyncedMembers.mIsStarted)
606 return;
607
608 // If we are currently in QCamera::FocusModeManual, we apply the focus distance
609 // immediately. Otherwise, we store the value and apply it during setFocusMode(Manual).
610 if (mSyncedMembers.mCameraSettings.mAFMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
611 applyFocusSettingsToCaptureRequestBuilder(
612 mPreviewRequestBuilder,
613 mSyncedMembers.mCameraSettings,
614 false);
615
616 mPreviewRequest = mPreviewRequestBuilder.build();
617
618 if (mSyncedMembers.mIsTakingStillPhoto) {
619 // Don't set any request if we are in the middle of taking a still photo.
620 // The setting will be applied to the preview after the still photo routine is done.
621 return;
622 }
623
624 try {
625 mCaptureSession.setRepeatingRequest(
626 mPreviewRequest,
627 mPreviewCaptureCallback,
628 mBackgroundHandler);
629 } catch (Exception exception) {
630 Log.w(LOG_TAG, "Failed to set focus distance:" + exception);
631 }
632 }
633 }
634 }
635
636 private int getTorchModeValue(boolean mode)
637 {
638 return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
639 }
640
642 void setTorchMode(boolean torchMode)
643 {
644 synchronized (mSyncedMembers) {
645 mSyncedMembers.mCameraSettings.mTorchMode = getTorchModeValue(torchMode);
646
647 if (mSyncedMembers.mIsStarted) {
648 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mSyncedMembers.mCameraSettings.mTorchMode);
649 mPreviewRequest = mPreviewRequestBuilder.build();
650
651 if (mSyncedMembers.mIsTakingStillPhoto) {
652 // Don't set any request if we are in the middle of taking a still photo.
653 // The setting will be applied to the preview after the still photo routine is done.
654 return;
655 }
656
657 try {
658 mCaptureSession.setRepeatingRequest(
659 mPreviewRequest,
660 mPreviewCaptureCallback,
661 mBackgroundHandler);
662 } catch (Exception exception) {
663 Log.w(LOG_TAG, "Failed to set flash mode:" + exception);
664 }
665 }
666 }
667 }
668
669 // Called indirectly from C++ when the QCamera goes active.
670 // Called again from Java camera background thread when a still photo is done.
672 void setRepeatingRequestToPreview() throws CameraAccessException {
673 synchronized (mSyncedMembers) {
674 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
675 mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());
676
677 applyPreviewSettingsToCaptureRequestBuilder(
678 mPreviewRequestBuilder,
679 mSyncedMembers.mCameraSettings);
680
681 mPreviewRequest = mPreviewRequestBuilder.build();
682 mCaptureSession.setRepeatingRequest(
683 mPreviewRequest,
684 mPreviewCaptureCallback,
685 mBackgroundHandler);
686 }
687 }
688
689 private void applyStillPhotoSettingsToCaptureRequestBuilder(
690 CaptureRequest.Builder requestBuilder,
691 CameraSettings cameraSettings)
692 {
693 requestBuilder.set(
694 CaptureRequest.CONTROL_CAPTURE_INTENT,
695 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
696 // Hint for the camera to use automatic modes for auto-focus, auto-exposure and
697 // white-balance where applicable.
698 requestBuilder.set(
699 CaptureRequest.CONTROL_MODE,
700 CaptureRequest.CONTROL_MODE_AUTO);
701 // TODO: We don't support any other exposure modes (such as manual control) yet. Will need
702 // to modify this in the future if we do.
703 requestBuilder.set(
704 CaptureRequest.CONTROL_AE_MODE,
705 CaptureRequest.CONTROL_AE_MODE_ON);
706
707 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
708
709 applyFocusSettingsToCaptureRequestBuilder(
710 requestBuilder,
711 cameraSettings,
712 true);
713
714 // Ideally we would pass AE_MODE_ON_ALWAYS_FLASH straight to the camera and let it
715 // control the flash unit. This has proven unreliable during testing. Instead we use
716 // regular CONTROL_AE_MODE_ON and force the flash on.
717 if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
718 requestBuilder.set(
719 CaptureRequest.CONTROL_AE_MODE,
720 CaptureRequest.CONTROL_AE_MODE_ON);
721 // Ideally, this should be set to SINGLE, only when we are finalizing the capture.
722 // However, this causes an issue on some Motorola devices where the flash will have
723 // wrong timing compared to the capture, and we will end up with no flash in the final
724 // photo.
725 requestBuilder.set(
726 CaptureRequest.FLASH_MODE,
727 CaptureRequest.FLASH_MODE_TORCH);
728 } else if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) {
729 requestBuilder.set(
730 CaptureRequest.CONTROL_AE_MODE,
731 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
732 }
733 }
734
735 private void applyPreviewSettingsToCaptureRequestBuilder(
736 CaptureRequest.Builder requestBuilder,
737 CameraSettings cameraSettings)
738 {
739 requestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
740
741 applyFocusSettingsToCaptureRequestBuilder(
742 requestBuilder,
743 cameraSettings,
744 false);
745
746 // TODO: Check if AE_MODE_ON is available
747 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
748 requestBuilder.set(CaptureRequest.FLASH_MODE, cameraSettings.mTorchMode);
749
750 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
751 if (cameraSettings.mFpsRange != null) {
752 requestBuilder.set(
753 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
754 cameraSettings.mFpsRange);
755 }
756
757 // TODO: This should likely not be set because trigger-events should only be submitted
758 // once. Meanwhile, this request will be used for preview which is repeating.
759 mPreviewRequestBuilder.set(
760 CaptureRequest.CONTROL_AF_TRIGGER,
761 CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
762 mPreviewRequestBuilder.set(
763 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
764 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
765 }
766
767 // If taking still photo, remember to trigger auto-focus calibration if CONTROL_AF_MODE is set
768 // to CONTROL_AF_MODE_AUTO.
769 void applyFocusSettingsToCaptureRequestBuilder(
770 CaptureRequest.Builder requestBuilder,
771 CameraSettings cameraSettings,
772 boolean stillPhoto)
773 {
774 int desiredAfMode = cameraSettings.mAFMode;
775 // During still photo, If the camera settings is set to CONTINUOUS_PICTURE, this is an
776 // indication that we are in QCamera::FocusModeAuto. In which case we should be using
777 // AF_MODE_AUTO, which lets us lock in focus once and keep it there until still photo
778 // is done.
779 if (stillPhoto && desiredAfMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
780 desiredAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
781 }
782
783 if (!isAfModeAvailable(desiredAfMode)) {
784 // If we don't support our desired AF_MODE, fallback to AF_MODE_OFF if that is
785 // available. Otherwise don't set any focus-mode, leave it as default and
786 // undefined state. Note: Setting CONTROL_AF_MODE to null is illegal and will cause an
787 // exception thrown.
788 if (isAfModeAvailable(CaptureRequest.CONTROL_AF_MODE_OFF)) {
789 requestBuilder.set(
790 CaptureRequest.CONTROL_AF_MODE,
791 CaptureRequest.CONTROL_AF_MODE_OFF);
792 }
793
794 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, null);
795 return;
796 }
797
798 requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, desiredAfMode);
799
800 // Set correct lens focus distance if we are in QCamera::FocusModeManual
801 if (desiredAfMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
802 final float lensFocusDistance = calcLensFocusDistanceFromQCameraFocusDistance(
803 cameraSettings.mFocusDistance);
804 if (lensFocusDistance < 0) {
805 Log.w(
806 LOG_TAG,
807 "Tried to apply FocusModeManual on a camera that doesn't support "
808 + "setting lens distance. Likely Qt developer bug. Ignoring.");
809 } else {
810 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, lensFocusDistance);
811 }
812 } else {
813 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, null);
814 }
815 }
816
817 // Calculates the CaptureRequest.LENS_FOCUS_DISTANCE equivalent given a QCamera::focusDistance
818 // value. Returns -1 on failure, such as if camera does not support setting manual focus
819 // distance.
820 private float calcLensFocusDistanceFromQCameraFocusDistance(float qCameraFocusDistance) {
821 float lensMinimumFocusDistance =
822 mVideoDeviceManager.getLensInfoMinimumFocusDistance(mCameraId);
823 if (lensMinimumFocusDistance <= 0)
824 return -1;
825
826 // Input is 0 to 1, with 0 meaning as close as possible.
827 // Android Camera2 expects it to be in the range [0, minimumFocusDistance]
828 // where higher values means closer to the camera and 0 means as far away as possible.
829 // We need to map to this range.
830 return (1.f - qCameraFocusDistance) * lensMinimumFocusDistance;
831 }
832
833 // Helper function to check if a given CaptureRequest.CONTROL_AF_MODE is supported on this
834 // device
835 private boolean isAfModeAvailable(int afMode) {
836 if (mVideoDeviceManager == null || mCameraId == null || mCameraId.isEmpty())
837 return false;
838 return mVideoDeviceManager.isAfModeAvailable(mCameraId, afMode);
839 }
840
841 // AF_STATE_NOT_FOCUSED_LOCKED implies we tried to calibrate the auto-focus, but failed
842 // to establish focus and the hardware has now given up and locked the focus.
843 static boolean afStateIsReadyForCapture(Integer afState) {
844 return afState == null
845 || afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
846 || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
847 }
848
849 static boolean aeStateIsReadyForCapture(Integer aeState) {
850 return aeState == null
851 || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
852 || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
853 }
854}
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]