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;
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 // Should be called if an on-going still photo capture has failed to finish.
34 // This lets us submit an appropriate error and notify QImageCapture that there will
35 // be no photo emitted.
36 // TODO: In the future we should send a more descriptive error message to QImageCapture, and
37 // and pass it as a parameter here.
38 native void onStillPhotoCaptureFailed(String cameraId);
39
40 CameraDevice mCameraDevice = null;
41 QtVideoDeviceManager mVideoDeviceManager = null;
42 // Thread and handler to that allows us to receive callbacks and frames on a background thread.
43 HandlerThread mBackgroundThread;
44 Handler mBackgroundHandler;
45 // This object allows us to receive frames with associated callbacks. This ImageReader
46 // is used to emit preview/video frames to the C++ thread.
47 ImageReader mPreviewImageReader = null;
48 // Used to emit still photo images to the C++ thread.
49 ImageReader mStillPhotoImageReader = null;
50 CameraManager mCameraManager;
51 CameraCaptureSession mCaptureSession;
52 CaptureRequest.Builder mPreviewRequestBuilder;
53 CaptureRequest mPreviewRequest;
54 String mCameraId;
55 List<Surface> mTargetSurfaces = new ArrayList<>();
56
57 private static int MaxNumberFrames = 12;
58
59 private static final int DEFAULT_FLASH_MODE = CaptureRequest.CONTROL_AE_MODE_ON;
60 private static final int DEFAULT_TORCH_MODE = CameraMetadata.FLASH_MODE_OFF;
61 // Default value in QPlatformCamera is FocusModeAuto, which maps to CONTINUOUS_PICTURE.
62 private static final int DEFAULT_AF_MODE = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
63 private static final float DEFAULT_FOCUS_DISTANCE = 1.f;
64 private static final float DEFAULT_ZOOM_FACTOR = 1.0f;
65
66 class CameraSettings {
67 // Not to be confused with QCamera::FlashMode.
68 // This controls the currently desired CaptureRequest.CONTROL_AE_MODE,
69 // but only for still photos.
70 //
71 // QCamera::FlashMode::FlashOff maps to CaptureRequest.CONTROL_AE_MODE_ON. This implies
72 // regular automatic exposure.
73 // QCamera::FlashMode::FlashAuto maps to CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH.
74 // QCamera::FlashMode::FlashOn should ideally map to
75 // CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH, but testing has shown that this is
76 // unreliable. Instead we force flash on using CaptureRequest.FLASH_MODE_ON and
77 // trigger auto-exposure using CaptureRequest.CONTROL_AE_MODE_ON.
78 public int mStillPhotoFlashMode = DEFAULT_FLASH_MODE;
79
80 // Not to be confused with QCamera::TorchMode.
81 // This controls the currently desired CaptureRequest.FLASH_MODE
82 // QCamera::TorchMode::TorchOff maps to CaptureRequest.FLASH_MODE_OFF
83 // QCamera::TorchMode::TorchAuto is not supported.
84 // QCamera::TorhcMode::TorchOn maps to CaptureRequest.FLASH_MODE_TORCH.
85 public int mTorchMode = DEFAULT_TORCH_MODE;
86
87 // Not to be confused with QCamera::FocusMode
88 // This controls the currently desired CaptureRequest.CONTROL_AF_MODE
89 // QCamera::FocusMode::FocusModeAuto maps to CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
90 //
91 // This variable only controls the AF_MODE we desire to apply. If the device
92 // does not support this AF_MODE this will not reflect what the camera is currently doing.
93 public int mAFMode = DEFAULT_AF_MODE;
94
95 // Not to be confused with CaptureRequest.LENS_FOCUS_DISTANCE. This variable stores the
96 // current QCamera::focusDistance. Must be applied whenever the focus-mode is set to
97 // Manual.
98 // Must have the same default value as the one set in QPlatformCamera.
99 public float mFocusDistance = DEFAULT_FOCUS_DISTANCE;
100
101 // Not to be confused with CaptureRequest.CONTROL_ZOOM_RATIO
102 // This matches the current QCamera::zoomFactor of the C++ QCamera object.
103 public float mZoomFactor = DEFAULT_ZOOM_FACTOR;
104
105 Range<Integer> mFpsRange = null;
106
107 public CameraSettings() { }
108
109 public CameraSettings(CameraSettings other) {
110 this.mStillPhotoFlashMode = other.mStillPhotoFlashMode;
111 this.mTorchMode = other.mTorchMode;
112 this.mAFMode = other.mAFMode;
113 this.mFocusDistance = other.mFocusDistance;
114 this.mZoomFactor = other.mZoomFactor;
115 if (other.mFpsRange != null) {
116 this.mFpsRange = new Range<Integer>(other.mFpsRange.getLower(), other.mFpsRange.getUpper());
117 }
118
119 }
120 }
121
122 // The purpose of this class is to gather variables that are accessed across
123 // the C++ QCamera's thread, and the background capture-processing thread.
124 // It also acts as the mutex for these variables.
125 // All access to these variables must happen after locking the instance.
126 class SyncedMembers {
127 private boolean mIsStarted = false;
128
129 private boolean mIsTakingStillPhoto = false;
130
131 private CameraSettings mCameraSettings = new CameraSettings();
132 }
133 private final SyncedMembers mSyncedMembers = new SyncedMembers();
134
135 // Resets the control properties of this camera to their default values.
137 public void resetControlProperties() {
138 synchronized (mSyncedMembers) {
139 mSyncedMembers.mCameraSettings = new CameraSettings();
140 }
141 }
142
143 // Returns a deep copy of the CameraSettings instance. Thread-safe.
144 private CameraSettings atomicCameraSettingsCopy() {
145 synchronized (mSyncedMembers) {
146 return new CameraSettings(mSyncedMembers.mCameraSettings);
147 }
148 }
149
150 private QtExifDataHandler mExifDataHandler = null;
151
152 native void onCameraOpened(String cameraId);
153 native void onCameraDisconnect(String cameraId);
154 native void onCameraError(String cameraId, int error);
155 CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
156 @Override
157 public void onOpened(CameraDevice cameraDevice) {
158 if (mCameraDevice != null)
159 mCameraDevice.close();
160 mCameraDevice = cameraDevice;
161 onCameraOpened(mCameraId);
162 }
163 @Override
164 public void onDisconnected(CameraDevice cameraDevice) {
165 cameraDevice.close();
166 if (mCameraDevice == cameraDevice)
167 mCameraDevice = null;
168 onCameraDisconnect(mCameraId);
169 }
170 @Override
171 public void onError(CameraDevice cameraDevice, int error) {
172 cameraDevice.close();
173 if (mCameraDevice == cameraDevice)
174 mCameraDevice = null;
175 onCameraError(mCameraId, error);
176 }
177 };
178
179 native void onCaptureSessionConfigured(String cameraId);
180 native void onCaptureSessionConfigureFailed(String cameraId);
181 CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {
182 @Override
183 public void onConfigured(CameraCaptureSession cameraCaptureSession) {
184 mCaptureSession = cameraCaptureSession;
186 }
187
188 @Override
189 public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
191 }
192
193 @Override
194 public void onActive(CameraCaptureSession cameraCaptureSession) {
195 super.onActive(cameraCaptureSession);
196 onSessionActive(mCameraId);
197 }
198
199 @Override
200 public void onClosed(CameraCaptureSession cameraCaptureSession) {
201 super.onClosed(cameraCaptureSession);
202 onSessionClosed(mCameraId);
203 }
204 };
205
206 native void onSessionActive(String cameraId);
207 native void onSessionClosed(String cameraId);
208 native void onCaptureSessionFailed(String cameraId, int reason, long frameNumber);
209
210 // This callback is used when doing normal preview. The only purpose is to detect if something
211 // goes wrong, so we can report back to QCamera.
212 class PreviewCaptureSessionCallback extends CameraCaptureSession.CaptureCallback {
213 @Override
214 public void onCaptureFailed(
215 CameraCaptureSession session,
216 CaptureRequest request,
217 CaptureFailure failure)
218 {
219 super.onCaptureFailed(session, request, failure);
220 onCaptureSessionFailed(mCameraId, failure.getReason(), failure.getFrameNumber());
221 }
222 }
223
224 // Callback that is being used for error-handling when doing preview.
225 // TODO: The variable can be removed in the future, and instead just recreate the object
226 // every time we go into previewing.
227 PreviewCaptureSessionCallback mPreviewCaptureCallback = new PreviewCaptureSessionCallback();
228
229 QtCamera2(Context context) {
230 mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
231 mVideoDeviceManager = new QtVideoDeviceManager(context);
232 startBackgroundThread();
233 }
234
235 void startBackgroundThread() {
236 mBackgroundThread = new HandlerThread("CameraBackground");
237 mBackgroundThread.start();
238 mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
239 }
240
242 void stopBackgroundThread() {
243 mBackgroundThread.quitSafely();
244 try {
245 mBackgroundThread.join();
246 mBackgroundThread = null;
247 mBackgroundHandler = null;
248 } catch (Exception e) {
249 e.printStackTrace();
250 }
251 }
252
253 @SuppressLint("MissingPermission")
254 boolean open(String cameraId) {
255 try {
256 mCameraId = cameraId;
257 mCameraManager.openCamera(cameraId,mStateCallback,mBackgroundHandler);
258 return true;
259 } catch (Exception e){
260 Log.w("QtCamera2", "Failed to open camera:" + e);
261 }
262
263 return false;
264 }
265
266 native void onStillPhotoAvailable(String cameraId, Image frame);
267
268 // Callback for when we receive a finalized still photo in mStillPhotoImageReader.
269 ImageReader.OnImageAvailableListener mOnStillPhotoAvailableListener = new ImageReader.OnImageAvailableListener() {
270 @Override
271 public void onImageAvailable(ImageReader reader) {
272 QtCamera2.this.onStillPhotoAvailable(mCameraId, reader.acquireLatestImage());
273 }
274 };
275
276 native void onPreviewFrameAvailable(String cameraId, Image frame);
277
278 // Callback for when we receive a preview/video frame in the associated mPreviewImageReader.
279 ImageReader.OnImageAvailableListener mOnPreviewImageAvailableListener = new ImageReader.OnImageAvailableListener() {
280 @Override
281 public void onImageAvailable(ImageReader reader) {
282 try {
283 Image img = reader.acquireLatestImage();
284 if (img != null)
285 QtCamera2.this.onPreviewFrameAvailable(mCameraId, img);
286 } catch (IllegalStateException e) {
287 // It seems that ffmpeg is processing images for too long (and does not close it)
288 // Give it a little more time. Restarting the camera session if it doesn't help
289 Log.e("QtCamera2", "Image processing taking too long. Let's wait 0,5s more " + e);
290 try {
291 Thread.sleep(500);
292 QtCamera2.this.onPreviewFrameAvailable(mCameraId, reader.acquireLatestImage());
293 } catch (IllegalStateException | InterruptedException e2) {
294 Log.e("QtCamera2", "Will not wait anymore. Restart camera session. " + e2);
295 // Remember current used camera ID, because stopAndClose will clear the value
296 String cameraId = mCameraId;
297 stopAndClose();
298 addImageReader(
299 mPreviewImageReader.getWidth(),
300 mPreviewImageReader.getHeight(),
301 mPreviewImageReader.getImageFormat());
302 open(cameraId);
303 }
304 }
305 }
306 };
307
309 void prepareCamera(int width, int height, int format, int minFps, int maxFps) {
310
311 addImageReader(width, height, format);
312 setFrameRate(minFps, maxFps);
313 }
314
315 private void addImageReader(int width, int height, int format) {
316
317 if (mPreviewImageReader != null)
318 removeSurface(mPreviewImageReader.getSurface());
319
320 if (mStillPhotoImageReader != null)
321 removeSurface(mStillPhotoImageReader.getSurface());
322
323 mPreviewImageReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
324 mPreviewImageReader.setOnImageAvailableListener(mOnPreviewImageAvailableListener, mBackgroundHandler);
325 addSurface(mPreviewImageReader.getSurface());
326
327 mStillPhotoImageReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
328 mStillPhotoImageReader.setOnImageAvailableListener(mOnStillPhotoAvailableListener, mBackgroundHandler);
329 addSurface(mStillPhotoImageReader.getSurface());
330 }
331
332 private void setFrameRate(int minFrameRate, int maxFrameRate) {
333 synchronized (mSyncedMembers) {
334 if (minFrameRate <= 0 || maxFrameRate <= 0)
335 mSyncedMembers.mCameraSettings.mFpsRange = null;
336 else
337 mSyncedMembers.mCameraSettings.mFpsRange = new Range<>(minFrameRate, maxFrameRate);
338 }
339 }
340
341 boolean addSurface(Surface surface) {
342 if (mTargetSurfaces.contains(surface))
343 return true;
344
345 return mTargetSurfaces.add(surface);
346 }
347
348 boolean removeSurface(Surface surface) {
349 return mTargetSurfaces.remove(surface);
350 }
351
353 void clearSurfaces() {
354 mTargetSurfaces.clear();
355 }
356
358 boolean createSession() {
359 if (mCameraDevice == null)
360 return false;
361
362 try {
363 // TODO: This API is deprecated and we should transition to the more modern method
364 // overload. See QTBUG-134750.
365 mCameraDevice.createCaptureSession(mTargetSurfaces, mCaptureStateCallback, mBackgroundHandler);
366 return true;
367 } catch (Exception exception) {
368 Log.w("QtCamera2", "Failed to create a capture session:" + exception);
369 }
370 return false;
371 }
372
374 boolean start() {
375 if (mCameraDevice == null)
376 return false;
377
378 if (mCaptureSession == null)
379 return false;
380
381 try {
382 synchronized (mSyncedMembers) {
383 setRepeatingRequestToPreview();
384 mSyncedMembers.mIsStarted = true;
385 }
386 return true;
387 } catch (CameraAccessException exception) {
388 Log.w("QtCamera2", "Failed to start preview:" + exception);
389 }
390 return false;
391 }
392
394 void stopAndClose() {
395 synchronized (mSyncedMembers) {
396 try {
397 if (null != mCaptureSession) {
398 mCaptureSession.close();
399 mCaptureSession = null;
400 }
401 if (null != mCameraDevice) {
402 mCameraDevice.close();
403 mCameraDevice = null;
404 }
405 mCameraId = "";
406 mTargetSurfaces.clear();
407 } catch (Exception exception) {
408 Log.w("QtCamera2", "Failed to stop and close:" + exception);
409 }
410 mSyncedMembers.mIsStarted = false;
411 mSyncedMembers.mIsTakingStillPhoto = false;
412 }
413 }
414
420
421 // This callback class is meant to be used a repeating request during still photo capture, when
422 // waiting for the auto-focus and auto-exposure calibration to lock in. Once done,
423 // it will finalize the still photo and then return the camera to previewing.
424 class StillPhotoPrecaptureCallback extends CameraCaptureSession.CaptureCallback {
425 StillPhotoPrecaptureCallback(
426 CameraSettings cameraSettings,
427 boolean waitForAutoFocus,
428 boolean waitForAutoExposure)
429 {
430 this.mCameraSettings = cameraSettings;
431 this.mWaitForAutoFocus = waitForAutoFocus;
432 this.mWaitForAutoExposure = waitForAutoExposure;
433 }
434
435 // Holds a copy of the camera settings that were to be used when the still photo
436 // was started.
437 CameraSettings mCameraSettings = null;
438 boolean mWaitForAutoFocus = false;
439 boolean mWaitForAutoExposure = false;
440 // Testing has showed that this repeating request will keep on invoking methods on this
441 // callback object, even after we have submitted a new request. This can make it hard
442 // to differentiate between callbacks to this object, or new instances that have been
443 // resubmitted for capture. This boolean tracks whether we should be processing incoming
444 // events.
445 boolean mShouldProcessIncomingEvents = true;
446
447 boolean capturingWithAutoFlash() {
448 return mWaitForAutoExposure
449 && mCameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH;
450 }
451
452 private void onCaptureFailureEvent() {
453 mShouldProcessIncomingEvents = false;
454
455 onStillPhotoCaptureFailed(mCameraId);
456
457 synchronized (mSyncedMembers) {
458 mSyncedMembers.mIsTakingStillPhoto = false;
459 }
460
461 // Try to reset our camera to regular preview
462 try {
463 setRepeatingRequestToPreview();
464 } catch (CameraAccessException e) {
465 // TODO: If we fail to go back into preview, we can clean up the camera session and
466 // set the QCamera to inactive.
467 }
468 }
469
470 @Override
471 public void onCaptureFailed(
472 CameraCaptureSession session,
473 CaptureRequest request,
474 CaptureFailure failure)
475 {
476 onCaptureFailureEvent();
477 }
478
479 // Returns the operation we should do as a result of processing the result.
480 private StillPhotoPrecaptureOperation determinePrecaptureOperation(CaptureResult result) {
481 Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
482 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
483
484 // If we are calibrating with auto flash and we receive FLASH_REQUIRED,
485 // we don't have to care about auto focus. Just transition straight to resubmitting
486 // the still photo request with flash forced on.
487 if (capturingWithAutoFlash() && aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) {
488 return StillPhotoPrecaptureOperation.RESUBMIT_WITH_FORCED_FLASH;
489 }
490
491 // If we're not waiting for anything, finalize still photo immediately
492 if (!mWaitForAutoFocus && !mWaitForAutoExposure) {
493 return StillPhotoPrecaptureOperation.FINALIZE_CAPTURE;
494 }
495
496 // Wait for focus only
497 if (mWaitForAutoFocus && QtCamera2.afStateIsReadyForCapture(afState)
498 && !mWaitForAutoExposure)
499 {
500 return StillPhotoPrecaptureOperation.FINALIZE_CAPTURE;
501 }
502
503 // Wait for exposure only
504 if (!mWaitForAutoFocus
505 && mWaitForAutoExposure && QtCamera2.aeStateIsReadyForCapture(aeState))
506 {
507 return StillPhotoPrecaptureOperation.FINALIZE_CAPTURE;
508 }
509
510 // Wait for focus and exposure
511 if (mWaitForAutoFocus && QtCamera2.afStateIsReadyForCapture(afState)
512 && mWaitForAutoExposure && QtCamera2.aeStateIsReadyForCapture(aeState))
513 {
514 return StillPhotoPrecaptureOperation.FINALIZE_CAPTURE;
515 }
516
517 return StillPhotoPrecaptureOperation.WAIT;
518 }
519
520 @Override
521 public void onCaptureCompleted(
522 CameraCaptureSession s,
523 CaptureRequest r,
524 TotalCaptureResult result)
525 {
526 if (!mShouldProcessIncomingEvents)
527 return;
528
529 final StillPhotoPrecaptureOperation operation = determinePrecaptureOperation(result);
530 try {
531 switch (operation) {
532 case FINALIZE_CAPTURE:
533 finalizeStillPhoto(mCameraSettings);
534 break;
535 case RESUBMIT_WITH_FORCED_FLASH:
536 // Submit a new still photo capture as if we were forcing flash on.
537 CameraSettings newCameraSettings = new CameraSettings(mCameraSettings);
538 newCameraSettings.mStillPhotoFlashMode = CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH;
539 submitNewStillPhotoCapture(newCameraSettings);
540 break;
541 default:
542 // Do nothing; wait for next result
543 break;
544 }
545 } catch (CameraAccessException e) {
546 onCaptureFailureEvent();
547 }
548
549 if (operation != StillPhotoPrecaptureOperation.WAIT) {
550 mShouldProcessIncomingEvents = false;
551 }
552 }
553 }
554
555 // Used for finalizing a still photo capture. Will reset mState and preview-request back to
556 // default when capture is done. This should be used for a singular capture-call, not a
557 // repeating request.
558 class StillPhotoFinalizerCallback extends CameraCaptureSession.CaptureCallback {
559 // TODO: Implement failure case where we tell QImageCapture that cancel this pending
560 // image and then try to reset our camera to preview if applicable.
561
562 // If capture fails, try to return to previewing.
563 @Override
564 public void onCaptureFailed(
565 CameraCaptureSession session,
566 CaptureRequest request,
567 CaptureFailure failure)
568 {
569 onStillPhotoCaptureFailed(mCameraId);
570 synchronized (mSyncedMembers) {
571 mSyncedMembers.mIsTakingStillPhoto = false;
572 }
573 try {
574 setRepeatingRequestToPreview();
575 } catch (CameraAccessException e) {
576 // TODO: If we fail here, we can clean up the camera session and set the QCamera
577 // to unactive.
578 }
579 }
580
581 @Override
582 public void onCaptureCompleted(
583 CameraCaptureSession session,
584 CaptureRequest request,
585 TotalCaptureResult result)
586 {
587 try {
588 mExifDataHandler = new QtExifDataHandler(result);
589 synchronized (mSyncedMembers) {
590 // If mIsStarted is true, it's an indication the QCamera is active and wants
591 // to keep receiving preview frames.
592 if (mSyncedMembers.mIsStarted) {
593 setRepeatingRequestToPreview();
594 }
595
596 // TODO: If we implement queueing of multiple photos, we should start the
597 // process of capturing the next photo here.
598 mSyncedMembers.mIsTakingStillPhoto = false;
599 }
600 } catch (CameraAccessException e) {
601 e.printStackTrace();
602 } catch (NullPointerException e) {
603 // See QTBUG-130901:
604 // It should not be possible for mCaptureSession to be null here
605 // because we always call .close() on mCaptureSession and then set it to null.
606 // Calling .close() should flush all pending callbacks, including this one.
607 // Either way, user has evidence this is happening, and catching this exception
608 // stops us from crashing the program.
609 Log.e(
610 "QtCamera2",
611 "Null-pointer access exception thrown when finalizing still photo capture. " +
612 "This should not be possible.");
613 e.printStackTrace();
614 } catch (IllegalStateException e) {
615 // See QTBUG-136227:
616 // According to the Bug description, it may happen that we are trying to call
617 // setRepeatingRequest on not active session
618 Log.w("QtCamera2", "Session is no longer active.");
619 e.printStackTrace();
620 }
621 }
622 }
623
624 // Can by StillPhotoPrecaptureCallback on background thread in order to finalize a still photo
625 // capture.
626 private void finalizeStillPhoto(CameraSettings cameraSettings) throws CameraAccessException
627 {
628 final CaptureRequest.Builder requestBuilder =
629 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
630 requestBuilder.addTarget(mStillPhotoImageReader.getSurface());
631 requestBuilder.set(
632 CaptureRequest.CONTROL_CAPTURE_INTENT,
633 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
634
635 applyStillPhotoSettingsToCaptureRequestBuilder(
636 requestBuilder,
637 cameraSettings);
638
639 mCaptureSession.capture(
640 requestBuilder.build(),
641 new StillPhotoFinalizerCallback(),
642 mBackgroundHandler);
643 }
644
645 // This function starts the process of taking a still photo. The final outputted image will
646 // be returned in the still photo image reader.
647 //
648 // Capturing a still photo requires the following steps:
649 // - We need to submit two requests: One repeating request and one single instant request.
650 // The single request triggers the calibration of auto-focus and/or auto-exposure,
651 // depending on what settings are set for the camera. This calibration takes some time,
652 // and we need to wait for these to settle. The logic for waiting for these to settle
653 // happens in the repeating request using the class StillPhotoPrecaptureCallback.
654 // - When the auto-focus and/or auto-exposure has settled, the StillPhotoPrecaptureCallback
655 // will submit a final request to finalize the photo. When the photo is finalized,
656 // we transition back into regular previewing.
657 // - Supporting auto flash is a corner case. When running auto-flash, we might get the result
658 // that no flash is needed, in which case we continue to finalizing the still photo
659 // as usual. If we receive the result that flash is required, we resubmit the still photo
660 // capture commands all over again as if we are using QCamera::FlashModeOn.
662 void beginStillPhotoCapture() {
663 synchronized (mSyncedMembers) {
664 if (mSyncedMembers.mIsTakingStillPhoto) {
665 // Queuing multiple still photos is not implemented.
666 // TODO: We might have to signal to QImageCapture here that capturing failed.
667 Log.w(
668 "QtCamera2",
669 "beginStillPhotoCapture() was called on camera backend while there " +
670 "is already a still photo in progress. This is not supported. Likely Qt " +
671 "developer bug.");
672 return;
673 }
674 }
675
676 final CameraSettings cameraSettings = atomicCameraSettingsCopy();
677 try {
678 submitNewStillPhotoCapture(cameraSettings);
679 } catch (CameraAccessException e) {
680 Log.w("QtCamera2", "Cannot get access to the camera: " + e);
681 e.printStackTrace();
682 onStillPhotoCaptureFailed(mCameraId);
683 // TODO: Try to go back to previewing if applicable. If that fails too, shut down
684 // camera session and report QCamera as inactive.
685 }
686 }
687
688 void submitNewStillPhotoCapture(CameraSettings cameraSettings) throws CameraAccessException
689 {
690 CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
691 CameraDevice.TEMPLATE_STILL_CAPTURE);
692 // Any in-between frames gathered while waiting for still photo, can be sent into
693 // the preview ImageReader.
694 requestBuilder.addTarget(mPreviewImageReader.getSurface());
695
696 applyStillPhotoSettingsToCaptureRequestBuilder(
697 requestBuilder,
698 cameraSettings);
699
700 // We need to trigger the auto-focus and auto-exposure mechanism in a single capture
701 // request, but waiting for it to settle happens in the repeating request.
702 // If configuration ended up with AF_MODE_AUTO, this implies we should trigger the
703 // auto focus to lock in.
704 final boolean triggerAutoFocus = requestBuilder.get(CaptureRequest.CONTROL_AF_MODE)
705 == CaptureResult.CONTROL_AF_MODE_AUTO;
706
707 final Integer aeMode = requestBuilder.get(CaptureRequest.CONTROL_AE_MODE);
708 boolean triggerAutoExposure = aeMode != null
709 && aeMode != CaptureResult.CONTROL_AE_MODE_OFF;
710
711 final StillPhotoPrecaptureCallback precaptureCallback = new StillPhotoPrecaptureCallback(
712 cameraSettings,
713 triggerAutoFocus,
714 triggerAutoExposure);
715
716 mCaptureSession.setRepeatingRequest(
717 requestBuilder.build(),
718 precaptureCallback,
719 mBackgroundHandler);
720
721 // Once we have prepared the repeating request that will wait, we re-use the
722 // request-builder and modify it to include the trigger commands, and then submit
723 // it as a one-time request.
724 if (triggerAutoFocus) {
725 requestBuilder.set(
726 CaptureRequest.CONTROL_AF_TRIGGER,
727 CaptureRequest.CONTROL_AF_TRIGGER_START);
728 }
729 if (triggerAutoExposure) {
730 requestBuilder.set(
731 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
732 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
733 }
734
735 // TODO: We should have a callback here that can track if still photo fails
736 mCaptureSession.capture(
737 requestBuilder.build(),
738 null,
739 mBackgroundHandler);
740
741 synchronized (mSyncedMembers) {
742 mSyncedMembers.mIsTakingStillPhoto = true;
743 }
744 }
745
747 void saveExifToFile(String path)
748 {
749 if (mExifDataHandler != null)
750 mExifDataHandler.save(path);
751 else
752 Log.e("QtCamera2", "No Exif data that could be saved to " + path);
753 }
754
755 private Rect getScalerCropRegion(float zoomFactor)
756 {
757 Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
758 float zoomRatio = 1.0f;
759 if (zoomFactor != 0.0f)
760 zoomRatio = 1.0f / zoomFactor;
761
762 int croppedWidth = activePixels.width() - (int)(activePixels.width() * zoomRatio);
763 int croppedHeight = activePixels.height() - (int)(activePixels.height() * zoomRatio);
764 return new Rect(croppedWidth/2, croppedHeight/2, activePixels.width() - croppedWidth/2,
765 activePixels.height() - croppedHeight/2);
766 }
767
768 private void applyZoomSettingsToRequestBuilder(CaptureRequest.Builder requBuilder, float zoomFactor)
769 {
770 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) {
771 requBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion(zoomFactor));
772 } else {
773 requBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
774 }
775 }
776
778 void zoomTo(float factor)
779 {
780 synchronized (mSyncedMembers) {
781 mSyncedMembers.mCameraSettings.mZoomFactor = factor;
782
783 if (!mSyncedMembers.mIsStarted) {
784 // Camera capture has not begun. Zoom will be applied during start().
785 return;
786 }
787
788 // TODO: In the future we can call .setRepeatingRequestToPreview() directly,
789 // which will recreate the request builder as necessary and apply it with all
790 // settings consistently.
791 applyZoomSettingsToRequestBuilder(mPreviewRequestBuilder, factor);
792 mPreviewRequest = mPreviewRequestBuilder.build();
793
794 if (mSyncedMembers.mIsTakingStillPhoto) {
795 // Don't set any request if we are in the middle of taking a still photo.
796 // The setting will be applied to the preview after the still photo routine is done.
797 return;
798 }
799 try {
800 mCaptureSession.setRepeatingRequest(
801 mPreviewRequest,
802 mPreviewCaptureCallback,
803 mBackgroundHandler);
804 } catch (Exception exception) {
805 Log.w("QtCamera2", "Failed to set zoom:" + exception);
806 }
807 }
808 }
809
810 // As described in QPlatformCamera::setFocusMode, this function must apply the focus-distance
811 // whenever the new QCamera::focusMode is set to Manual.
812 // For now, the QtCamera2 implementation only supports Auto and Manual FocusModes.
814 void setFocusMode(int newFocusMode)
815 {
816 // TODO: In the future, not all QCamera::FocusModes will have a 1:1 mapping to the
817 // CONTROL_AF_MODE values. We will need a general solution to translate between
818 // QCamera::FocusModes and the relevant Android Camera2 properties.
819
820 // Expand with more values in the future.
821 // Translate into the corresponding CONTROL_AF_MODE.
822 int newAfMode = 0;
823 if (newFocusMode == 0) // FocusModeAuto
824 newAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
825 else if (newFocusMode == 5) // FocusModeManual
826 newAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
827 else {
828 Log.d(
829 "QtCamera2",
830 "received a QCamera::FocusMode from native code that is not recognized. " +
831 "Likely Qt developer bug. Ignoring.");
832 return;
833 }
834
835 // TODO: Ideally this should check if newAfMode is supported through isAfModeAvailable()
836 // but in some situation, mCameraId will be null and therefore isAfModeAvailable will always
837 // return false. One example of this is during QCamera::setCameraDevice.
838 /*
839 if (!isAfModeAvailable(newAfMode)) {
840 Log.d(
841 "QtCamera2",
842 "received a QCamera::FocusMode from native code that is not reported as supported. " +
843 "Likely Qt developer bug. Ignoring.");
844 return;
845 }
846 */
847
848 synchronized (mSyncedMembers) {
849 mSyncedMembers.mCameraSettings.mAFMode = newAfMode;
850
851 // If the camera is not in the started state yet, we skip activating focus-mode here.
852 // Instead it will get applied when the camera is initialized.
853 if (!mSyncedMembers.mIsStarted)
854 return;
855
856 applyFocusSettingsToCaptureRequestBuilder(
857 mPreviewRequestBuilder,
858 mSyncedMembers.mCameraSettings,
859 false);
860 mPreviewRequest = mPreviewRequestBuilder.build();
861
862 if (mSyncedMembers.mIsTakingStillPhoto) {
863 // Don't set any request if we are in the middle of taking a still photo.
864 // The setting will be applied to the preview after the still photo routine is done.
865 return;
866 }
867 try {
868 mCaptureSession.setRepeatingRequest(
869 mPreviewRequest,
870 mPreviewCaptureCallback,
871 mBackgroundHandler);
872 } catch (Exception exception) {
873 Log.w("QtCamera2", "Failed to set focus mode:" + exception);
874 }
875 }
876 }
877
879 void setFlashMode(String flashMode)
880 {
881 synchronized (mSyncedMembers) {
882 int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
883 if (flashModeValue < 0) {
884 Log.w("QtCamera2", "Unknown flash mode");
885 return;
886 }
887 mSyncedMembers.mCameraSettings.mStillPhotoFlashMode = flashModeValue;
888 }
889 }
890
891 // Sets the focus distance of the camera. Input is the same that accepted by the
892 // QCamera public API. Accepts a float in the range 0,1. Where 0 means as close as possible,
893 // and 1 means infinity.
894 //
895 // This should never be called if the device specifies focus-distance as unsupported.
897 public void setFocusDistance(float distanceInput)
898 {
899 if (distanceInput < 0.f || distanceInput > 1.f) {
900 Log.w(
901 "QtCamera2",
902 "received out-of-bounds value when setting camera focus-distance. " +
903 "Likely Qt developer bug. Ignoring.");
904 return;
905 }
906
907 // TODO: Add error handling to check if current mCameraId supports setting focus-distance.
908 // See setFocusMode relevant issue.
909
910 synchronized (mSyncedMembers) {
911 mSyncedMembers.mCameraSettings.mFocusDistance = distanceInput;
912
913 // If the camera is not in the started state yet, we skip applying any camera-controls
914 // here. It will get applied once the camera is ready.
915 if (!mSyncedMembers.mIsStarted)
916 return;
917
918 // If we are currently in QCamera::FocusModeManual, we apply the focus distance
919 // immediately. Otherwise, we store the value and apply it during setFocusMode(Manual).
920 if (mSyncedMembers.mCameraSettings.mAFMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
921 applyFocusSettingsToCaptureRequestBuilder(
922 mPreviewRequestBuilder,
923 mSyncedMembers.mCameraSettings,
924 false);
925
926 mPreviewRequest = mPreviewRequestBuilder.build();
927
928 if (mSyncedMembers.mIsTakingStillPhoto) {
929 // Don't set any request if we are in the middle of taking a still photo.
930 // The setting will be applied to the preview after the still photo routine is done.
931 return;
932 }
933
934 try {
935 mCaptureSession.setRepeatingRequest(
936 mPreviewRequest,
937 mPreviewCaptureCallback,
938 mBackgroundHandler);
939 } catch (Exception exception) {
940 Log.w("QtCamera2", "Failed to set focus distance:" + exception);
941 }
942 }
943 }
944 }
945
946 private int getTorchModeValue(boolean mode)
947 {
948 return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
949 }
950
952 void setTorchMode(boolean torchMode)
953 {
954 synchronized (mSyncedMembers) {
955 mSyncedMembers.mCameraSettings.mTorchMode = getTorchModeValue(torchMode);
956
957 if (mSyncedMembers.mIsStarted) {
958 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mSyncedMembers.mCameraSettings.mTorchMode);
959 mPreviewRequest = mPreviewRequestBuilder.build();
960
961 if (mSyncedMembers.mIsTakingStillPhoto) {
962 // Don't set any request if we are in the middle of taking a still photo.
963 // The setting will be applied to the preview after the still photo routine is done.
964 return;
965 }
966
967 try {
968 mCaptureSession.setRepeatingRequest(
969 mPreviewRequest,
970 mPreviewCaptureCallback,
971 mBackgroundHandler);
972 } catch (Exception exception) {
973 Log.w("QtCamera2", "Failed to set flash mode:" + exception);
974 }
975 }
976 }
977 }
978
979 // Called indirectly from C++ when the QCamera goes active.
980 // Called again from Java camera background thread when a still photo is done.
982 private void setRepeatingRequestToPreview() throws CameraAccessException {
983 synchronized (mSyncedMembers) {
984 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
985 mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());
986
987 applyPreviewSettingsToCaptureRequestBuilder(
988 mPreviewRequestBuilder,
989 mSyncedMembers.mCameraSettings);
990
991 mPreviewRequest = mPreviewRequestBuilder.build();
992 mCaptureSession.setRepeatingRequest(
993 mPreviewRequest,
994 mPreviewCaptureCallback,
995 mBackgroundHandler);
996 }
997 }
998
999 private void applyStillPhotoSettingsToCaptureRequestBuilder(
1000 CaptureRequest.Builder requestBuilder,
1001 CameraSettings cameraSettings)
1002 {
1003 requestBuilder.set(
1004 CaptureRequest.CONTROL_CAPTURE_INTENT,
1005 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
1006 // Hint for the camera to use automatic modes for auto-focus, auto-exposure and
1007 // white-balance where applicable.
1008 requestBuilder.set(
1009 CaptureRequest.CONTROL_MODE,
1010 CaptureRequest.CONTROL_MODE_AUTO);
1011 // TODO: We don't support any other exposure modes (such as manual control) yet. Will need
1012 // to modify this in the future if we do.
1013 requestBuilder.set(
1014 CaptureRequest.CONTROL_AE_MODE,
1015 CaptureRequest.CONTROL_AE_MODE_ON);
1016
1017 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
1018
1019 applyFocusSettingsToCaptureRequestBuilder(
1020 requestBuilder,
1021 cameraSettings,
1022 true);
1023
1024 // Ideally we would pass AE_MODE_ON_ALWAYS_FLASH straight to the camera and let it
1025 // control the flash unit. This has proven unreliable during testing. Instead we use
1026 // regular CONTROL_AE_MODE_ON and force the flash on.
1027 if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
1028 requestBuilder.set(
1029 CaptureRequest.CONTROL_AE_MODE,
1030 CaptureRequest.CONTROL_AE_MODE_ON);
1031 // Ideally, this should be set to SINGLE, only when we are finalizing the capture.
1032 // However, this causes an issue on some Motorola devices where the flash will have
1033 // wrong timing compared to the capture, and we will end up with no flash in the final
1034 // photo.
1035 requestBuilder.set(
1036 CaptureRequest.FLASH_MODE,
1037 CaptureRequest.FLASH_MODE_TORCH);
1038 } else if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) {
1039 requestBuilder.set(
1040 CaptureRequest.CONTROL_AE_MODE,
1041 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
1042 }
1043 }
1044
1045 private void applyPreviewSettingsToCaptureRequestBuilder(
1046 CaptureRequest.Builder requestBuilder,
1047 CameraSettings cameraSettings)
1048 {
1049 requestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
1050
1051 applyFocusSettingsToCaptureRequestBuilder(
1052 requestBuilder,
1053 cameraSettings,
1054 false);
1055
1056 // TODO: Check if AE_MODE_ON is available
1057 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
1058 requestBuilder.set(CaptureRequest.FLASH_MODE, cameraSettings.mTorchMode);
1059
1060 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
1061 if (cameraSettings.mFpsRange != null) {
1062 requestBuilder.set(
1063 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
1064 cameraSettings.mFpsRange);
1065 }
1066
1067 // TODO: This should likely not be set because trigger-events should only be submitted
1068 // once. Meanwhile, this request will be used for preview which is repeating.
1069 mPreviewRequestBuilder.set(
1070 CaptureRequest.CONTROL_AF_TRIGGER,
1071 CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
1072 mPreviewRequestBuilder.set(
1073 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
1074 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
1075 }
1076
1077 // If taking still photo, remember to trigger auto-focus calibration if CONTROL_AF_MODE is set
1078 // to CONTROL_AF_MODE_AUTO.
1079 void applyFocusSettingsToCaptureRequestBuilder(
1080 CaptureRequest.Builder requestBuilder,
1081 CameraSettings cameraSettings,
1082 boolean stillPhoto)
1083 {
1084 int desiredAfMode = cameraSettings.mAFMode;
1085 // During still photo, If the camera settings is set to CONTINUOUS_PICTURE, this is an
1086 // indication that we are in QCamera::FocusModeAuto. In which case we should be using
1087 // AF_MODE_AUTO, which lets us lock in focus once and keep it there until still photo
1088 // is done.
1089 if (stillPhoto && desiredAfMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
1090 desiredAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
1091 }
1092
1093 if (!isAfModeAvailable(desiredAfMode)) {
1094 // If we don't support our desired AF_MODE, fallback to AF_MODE_OFF if that is
1095 // available. Otherwise don't set any focus-mode, leave it as default and
1096 // undefined state. Note: Setting CONTROL_AF_MODE to null is illegal and will cause an
1097 // exception thrown.
1098 if (isAfModeAvailable(CaptureRequest.CONTROL_AF_MODE_OFF)) {
1099 requestBuilder.set(
1100 CaptureRequest.CONTROL_AF_MODE,
1101 CaptureRequest.CONTROL_AF_MODE_OFF);
1102 }
1103
1104 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, null);
1105 return;
1106 }
1107
1108 requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, desiredAfMode);
1109
1110 // Set correct lens focus distance if we are in QCamera::FocusModeManual
1111 if (desiredAfMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
1112 final float lensFocusDistance = calcLensFocusDistanceFromQCameraFocusDistance(
1113 cameraSettings.mFocusDistance);
1114 if (lensFocusDistance < 0) {
1115 Log.w(
1116 "QtCamera2",
1117 "Tried to apply FocusModeManual on a camera that doesn't support "
1118 + "setting lens distance. Likely Qt developer bug. Ignoring.");
1119 } else {
1120 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, lensFocusDistance);
1121 }
1122 } else {
1123 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, null);
1124 }
1125 }
1126
1127 // Calculates the CaptureRequest.LENS_FOCUS_DISTANCE equivalent given a QCamera::focusDistance
1128 // value. Returns -1 on failure, such as if camera does not support setting manual focus
1129 // distance.
1130 private float calcLensFocusDistanceFromQCameraFocusDistance(float qCameraFocusDistance) {
1131 float lensMinimumFocusDistance =
1132 mVideoDeviceManager.getLensInfoMinimumFocusDistance(mCameraId);
1133 if (lensMinimumFocusDistance <= 0)
1134 return -1;
1135
1136 // Input is 0 to 1, with 0 meaning as close as possible.
1137 // Android Camera2 expects it to be in the range [0, minimumFocusDistance]
1138 // where higher values means closer to the camera and 0 means as far away as possible.
1139 // We need to map to this range.
1140 return (1.f - qCameraFocusDistance) * lensMinimumFocusDistance;
1141 }
1142
1143 // Helper function to check if a given CaptureRequest.CONTROL_AF_MODE is supported on this
1144 // device
1145 private boolean isAfModeAvailable(int afMode) {
1146 if (mVideoDeviceManager == null || mCameraId == null || mCameraId.isEmpty())
1147 return false;
1148 return mVideoDeviceManager.isAfModeAvailable(mCameraId, afMode);
1149 }
1150
1151 // AF_STATE_NOT_FOCUSED_LOCKED implies we tried to calibrate the auto-focus, but failed
1152 // to establish focus and the hardware has now given up and locked the focus.
1153 static boolean afStateIsReadyForCapture(Integer afState) {
1154 return afState == null
1155 || afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
1156 || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
1157 }
1158
1159 static boolean aeStateIsReadyForCapture(Integer aeState) {
1160 return aeState == null
1161 || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
1162 || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
1163 }
1164}
QPainter Context
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:398
QImageReader reader("image.png")
[1]
DBusConnection const char DBusError * error
GLenum mode
GLboolean r
GLuint start
GLfloat GLfloat f
[26]
GLint GLsizei width
GLdouble s
[6]
Definition qopenglext.h:235
GLint void * img
Definition qopenglext.h:233
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
@ Handler
EGLint EGLint EGLint format
QFrame frame
[0]
file open(QIODevice::ReadOnly)
QNetworkRequest request(url)
[0]