Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
7
8import android.annotation.SuppressLint;
9import android.annotation.TargetApi;
10import android.content.Context;
11import android.graphics.Rect;
12import android.hardware.camera2.CameraAccessException;
13import android.hardware.camera2.CameraCaptureSession;
14import android.hardware.camera2.CameraDevice;
15import android.hardware.camera2.CameraMetadata;
16import android.hardware.camera2.CameraManager;
17import android.hardware.camera2.CaptureFailure;
18import android.hardware.camera2.CaptureResult;
19import android.hardware.camera2.CaptureRequest;
20import android.hardware.camera2.TotalCaptureResult;
21import android.media.Image;
22import android.media.ImageReader;
23import android.graphics.ImageFormat;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.util.Log;
27import android.util.Range;
28import android.view.Surface;
29import android.media.MediaCodec;
30import android.media.MediaCodecInfo;
31import android.media.MediaFormat;
32import java.lang.Thread;
33import java.util.ArrayList;
34import java.util.List;
35
36@TargetApi(23)
37public class QtCamera2 {
38
39 CameraDevice mCameraDevice = null;
40 QtVideoDeviceManager mVideoDeviceManager = null;
41 HandlerThread mBackgroundThread;
42 Handler mBackgroundHandler;
43 ImageReader mImageReader = null;
44 ImageReader mCapturedPhotoReader = null;
45 CameraManager mCameraManager;
46 CameraCaptureSession mCaptureSession;
47 CaptureRequest.Builder mPreviewRequestBuilder;
48 CaptureRequest mPreviewRequest;
49 String mCameraId;
50 List<Surface> mTargetSurfaces = new ArrayList<>();
51
52 private static final int STATE_PREVIEW = 0;
53 private static final int STATE_WAITING_LOCK = 1;
54 private static final int STATE_WAITING_PRECAPTURE = 2;
55 private static final int STATE_WAITING_NON_PRECAPTURE = 3;
56 private static final int STATE_PICTURE_TAKEN = 4;
57
58 private int mState = STATE_PREVIEW;
59 private Object mStartMutex = new Object();
60 private boolean mIsStarted = false;
61 private static int MaxNumberFrames = 12;
62 private int mFlashMode = CaptureRequest.CONTROL_AE_MODE_ON;
63 private int mTorchMode = CameraMetadata.FLASH_MODE_OFF;
64 private int mAFMode = CaptureRequest.CONTROL_AF_MODE_OFF;
65 private float mZoomFactor = 1.0f;
66 private Range<Integer> mFpsRange = null;
67 private QtExifDataHandler mExifDataHandler = null;
68
69 native void onCameraOpened(String cameraId);
70 native void onCameraDisconnect(String cameraId);
71 native void onCameraError(String cameraId, int error);
72 CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
73 @Override
74 public void onOpened(CameraDevice cameraDevice) {
75 if (mCameraDevice != null)
76 mCameraDevice.close();
77 mCameraDevice = cameraDevice;
78 onCameraOpened(mCameraId);
79 }
80 @Override
81 public void onDisconnected(CameraDevice cameraDevice) {
82 cameraDevice.close();
83 if (mCameraDevice == cameraDevice)
84 mCameraDevice = null;
85 onCameraDisconnect(mCameraId);
86 }
87 @Override
88 public void onError(CameraDevice cameraDevice, int error) {
89 cameraDevice.close();
90 if (mCameraDevice == cameraDevice)
91 mCameraDevice = null;
92 onCameraError(mCameraId, error);
93 }
94 };
95
96 native void onCaptureSessionConfigured(String cameraId);
97 native void onCaptureSessionConfigureFailed(String cameraId);
98 CameraCaptureSession.StateCallback mCaptureStateCallback = new CameraCaptureSession.StateCallback() {
99 @Override
100 public void onConfigured(CameraCaptureSession cameraCaptureSession) {
101 mCaptureSession = cameraCaptureSession;
103 }
104
105 @Override
106 public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) {
108 }
109
110 @Override
111 public void onActive(CameraCaptureSession cameraCaptureSession) {
112 super.onActive(cameraCaptureSession);
113 onSessionActive(mCameraId);
114 }
115
116 @Override
117 public void onClosed(CameraCaptureSession cameraCaptureSession) {
118 super.onClosed(cameraCaptureSession);
119 onSessionClosed(mCameraId);
120 }
121 };
122
123 native void onSessionActive(String cameraId);
124 native void onSessionClosed(String cameraId);
125 native void onCaptureSessionFailed(String cameraId, int reason, long frameNumber);
126 CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() {
127 public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request, CaptureFailure failure) {
128 super.onCaptureFailed(session, request, failure);
129 onCaptureSessionFailed(mCameraId, failure.getReason(), failure.getFrameNumber());
130 }
131
132 private void process(CaptureResult result) {
133 switch (mState) {
134 case STATE_WAITING_LOCK: {
135 Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
136 if (afState == null) {
137 capturePhoto();
138 } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState ||
139 CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) {
140 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
141 if (aeState == null ||
142 aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
143 mState = STATE_PICTURE_TAKEN;
144 capturePhoto();
145 } else {
146 try {
147 mPreviewRequestBuilder.set(
148 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
149 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
150 mState = STATE_WAITING_PRECAPTURE;
151 mCaptureSession.capture(mPreviewRequestBuilder.build(),
152 mCaptureCallback,
153 mBackgroundHandler);
154 } catch (CameraAccessException e) {
155 Log.w("QtCamera2", "Cannot get access to the camera: " + e);
156 }
157 }
158 }
159 break;
160 }
161 case STATE_WAITING_PRECAPTURE: {
162 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
163 if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
164 mState = STATE_WAITING_NON_PRECAPTURE;
165 }
166 break;
167 }
168 case STATE_WAITING_NON_PRECAPTURE: {
169 Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
170 if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
171 mState = STATE_PICTURE_TAKEN;
172 capturePhoto();
173 }
174 break;
175 }
176 default:
177 break;
178 }
179 }
180
181 @Override
182 public void onCaptureProgressed(CameraCaptureSession s, CaptureRequest r, CaptureResult partialResult) {
183 process(partialResult);
184 }
185
186 @Override
187 public void onCaptureCompleted(CameraCaptureSession s, CaptureRequest r, TotalCaptureResult result) {
188 process(result);
189 }
190 };
191
192 public QtCamera2(Context context) {
193 mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
194 mVideoDeviceManager = new QtVideoDeviceManager(context);
195 startBackgroundThread();
196 }
197
198 void startBackgroundThread() {
199 mBackgroundThread = new HandlerThread("CameraBackground");
200 mBackgroundThread.start();
201 mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
202 }
203
204 void stopBackgroundThread() {
205 mBackgroundThread.quitSafely();
206 try {
207 mBackgroundThread.join();
208 mBackgroundThread = null;
209 mBackgroundHandler = null;
210 } catch (Exception e) {
211 e.printStackTrace();
212 }
213 }
214
215 @SuppressLint("MissingPermission")
216 public boolean open(String cameraId) {
217 try {
218 mCameraId = cameraId;
219 mCameraManager.openCamera(cameraId,mStateCallback,mBackgroundHandler);
220 return true;
221 } catch (Exception e){
222 Log.w("QtCamera2", "Failed to open camera:" + e);
223 }
224
225 return false;
226 }
227
228 native void onPhotoAvailable(String cameraId, Image frame);
229
230 ImageReader.OnImageAvailableListener mOnPhotoAvailableListener = new ImageReader.OnImageAvailableListener() {
231 @Override
232 public void onImageAvailable(ImageReader reader) {
233 QtCamera2.this.onPhotoAvailable(mCameraId, reader.acquireLatestImage());
234 }
235 };
236
237 native void onFrameAvailable(String cameraId, Image frame);
238
239 ImageReader.OnImageAvailableListener mOnImageAvailableListener = new ImageReader.OnImageAvailableListener() {
240 @Override
241 public void onImageAvailable(ImageReader reader) {
242 try {
243 Image img = reader.acquireLatestImage();
244 if (img != null)
245 QtCamera2.this.onFrameAvailable(mCameraId, img);
246 } catch (IllegalStateException e) {
247 // It seems that ffmpeg is processing images for too long (and does not close it)
248 // Give it a little more time. Restarting the camera session if it doesn't help
249 Log.e("QtCamera2", "Image processing taking too long. Let's wait 0,5s more " + e);
250 try {
251 Thread.sleep(500);
252 QtCamera2.this.onFrameAvailable(mCameraId, reader.acquireLatestImage());
253 } catch (IllegalStateException | InterruptedException e2) {
254 Log.e("QtCamera2", "Will not wait anymore. Restart camera session. " + e2);
255 // Remember current used camera ID, because stopAndClose will clear the value
256 String cameraId = mCameraId;
257 stopAndClose();
258 addImageReader(mImageReader.getWidth(), mImageReader.getHeight(),
259 mImageReader.getImageFormat());
260 open(cameraId);
261 }
262 }
263 }
264 };
265
266
267 public void prepareCamera(int width, int height, int format, int minFps, int maxFps) {
268
269 addImageReader(width, height, format);
270 setFrameRate(minFps, maxFps);
271 }
272
273 private void addImageReader(int width, int height, int format) {
274
275 if (mImageReader != null)
276 removeSurface(mImageReader.getSurface());
277
278 if (mCapturedPhotoReader != null)
279 removeSurface(mCapturedPhotoReader.getSurface());
280
281 mImageReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
282 mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mBackgroundHandler);
283 addSurface(mImageReader.getSurface());
284
285 mCapturedPhotoReader = ImageReader.newInstance(width, height, format, MaxNumberFrames);
286 mCapturedPhotoReader.setOnImageAvailableListener(mOnPhotoAvailableListener, mBackgroundHandler);
287 addSurface(mCapturedPhotoReader.getSurface());
288 }
289
290 private void setFrameRate(int minFrameRate, int maxFrameRate) {
291
292 if (minFrameRate <= 0 || maxFrameRate <= 0)
293 mFpsRange = null;
294 else
295 mFpsRange = new Range<>(minFrameRate, maxFrameRate);
296 }
297
298 public boolean addSurface(Surface surface) {
299 if (mTargetSurfaces.contains(surface))
300 return true;
301
302 return mTargetSurfaces.add(surface);
303 }
304
305 public boolean removeSurface(Surface surface) {
306 return mTargetSurfaces.remove(surface);
307 }
308
309 public void clearSurfaces() {
310 mTargetSurfaces.clear();
311 }
312
313 public boolean createSession() {
314 if (mCameraDevice == null)
315 return false;
316
317 try {
318 mCameraDevice.createCaptureSession(mTargetSurfaces, mCaptureStateCallback, mBackgroundHandler);
319 return true;
320 } catch (Exception exception) {
321 Log.w("QtCamera2", "Failed to create a capture session:" + exception);
322 }
323 return false;
324 }
325
326 public boolean start(int template) {
327
328 if (mCameraDevice == null)
329 return false;
330
331 if (mCaptureSession == null)
332 return false;
333
334 synchronized (mStartMutex) {
335 try {
336 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(template);
337 mPreviewRequestBuilder.addTarget(mImageReader.getSurface());
338 mAFMode = CaptureRequest.CONTROL_AF_MODE_OFF;
339 for (int mode : mVideoDeviceManager.getSupportedAfModes(mCameraId)) {
340 if (mode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
341 mAFMode = mode;
342 break;
343 }
344 }
345
346 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode);
347 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mTorchMode);
348 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
349 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, mAFMode);
350 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
351 if (mZoomFactor != 1.0f)
352 mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
353 if (mFpsRange != null)
354 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, mFpsRange);
355 mPreviewRequest = mPreviewRequestBuilder.build();
356 mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
357 mIsStarted = true;
358 return true;
359
360 } catch (Exception exception) {
361 Log.w("QtCamera2", "Failed to start preview:" + exception);
362 }
363 return false;
364 }
365 }
366
367 public void stopAndClose() {
368 synchronized (mStartMutex) {
369 try {
370 if (null != mCaptureSession) {
371 mCaptureSession.close();
372 mCaptureSession = null;
373 }
374 if (null != mCameraDevice) {
375 mCameraDevice.close();
376 mCameraDevice = null;
377 }
378 mCameraId = "";
379 mTargetSurfaces.clear();
380 } catch (Exception exception) {
381 Log.w("QtCamera2", "Failed to stop and close:" + exception);
382 }
383 mIsStarted = false;
384 }
385 }
386
387 private void capturePhoto() {
388 try {
389 final CaptureRequest.Builder captureBuilder =
390 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
391 captureBuilder.addTarget(mCapturedPhotoReader.getSurface());
392 captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode);
393 if (mZoomFactor != 1.0f)
394 captureBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
395
396 CameraCaptureSession.CaptureCallback captureCallback
397 = new CameraCaptureSession.CaptureCallback() {
398 @Override
399 public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
400 TotalCaptureResult result) {
401 try {
402 mExifDataHandler = new QtExifDataHandler(result);
403 // Reset the focus/flash and go back to the normal state of preview.
404 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
405 CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
406 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
407 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
408 mPreviewRequest = mPreviewRequestBuilder.build();
409 mState = STATE_PREVIEW;
410 mCaptureSession.setRepeatingRequest(mPreviewRequest,
411 mCaptureCallback,
412 mBackgroundHandler);
413 } catch (CameraAccessException e) {
414 e.printStackTrace();
415 }
416 }
417 };
418
419 mCaptureSession.capture(captureBuilder.build(), captureCallback, mBackgroundHandler);
420 } catch (CameraAccessException e) {
421 e.printStackTrace();
422 }
423 }
424
425 public void takePhoto() {
426 try {
427 if (mAFMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
428 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START);
429 mState = STATE_WAITING_LOCK;
430 mCaptureSession.capture(mPreviewRequestBuilder.build(), mCaptureCallback, mBackgroundHandler);
431 } else {
432 capturePhoto();
433 }
434 } catch (CameraAccessException e) {
435 Log.w("QtCamera2", "Cannot get access to the camera: " + e);
436 }
437 }
438
439 public void saveExifToFile(String path)
440 {
441 if (mExifDataHandler != null)
442 mExifDataHandler.save(path);
443 else
444 Log.e("QtCamera2", "No Exif data that could be saved to " + path);
445 }
446
447 private Rect getScalerCropRegion()
448 {
449 Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
450 float zoomRatio = 1.0f;
451 if (mZoomFactor != 0.0f)
452 zoomRatio = 1.0f/mZoomFactor;
453 int croppedWidth = activePixels.width() - (int)(activePixels.width() * zoomRatio);
454 int croppedHeight = activePixels.height() - (int)(activePixels.height() * zoomRatio);
455 return new Rect(croppedWidth/2, croppedHeight/2, activePixels.width() - croppedWidth/2,
456 activePixels.height() - croppedHeight/2);
457 }
458
459 public void zoomTo(float factor)
460 {
461 synchronized (mStartMutex) {
462 mZoomFactor = factor;
463
464 if (!mIsStarted) {
465 Log.w("QtCamera2", "Cannot set zoom on invalid camera");
466 return;
467 }
468
469 mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion());
470 mPreviewRequest = mPreviewRequestBuilder.build();
471
472 try {
473 mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
474 } catch (Exception exception) {
475 Log.w("QtCamera2", "Failed to set zoom:" + exception);
476 }
477 }
478 }
479 public void setFlashMode(String flashMode)
480 {
481 synchronized (mStartMutex) {
482
483 int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
484 if (flashModeValue < 0) {
485 Log.w("QtCamera2", "Unknown flash mode");
486 return;
487 }
488 mFlashMode = flashModeValue;
489
490 if (!mIsStarted)
491 return;
492
493 mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, mFlashMode);
494 mPreviewRequest = mPreviewRequestBuilder.build();
495
496 try {
497 mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
498 } catch (Exception exception) {
499 Log.w("QtCamera2", "Failed to set flash mode:" + exception);
500 }
501 }
502 }
503
504 private int getTorchModeValue(boolean mode)
505 {
506 return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
507 }
508
509 public void setTorchMode(boolean torchMode)
510 {
511 synchronized (mStartMutex) {
512 mTorchMode = getTorchModeValue(torchMode);
513
514 if (mIsStarted) {
515 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mTorchMode);
516 mPreviewRequest = mPreviewRequestBuilder.build();
517
518 try {
519 mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler);
520 } catch (Exception exception) {
521 Log.w("QtCamera2", "Failed to set flash mode:" + exception);
522 }
523 }
524 }
525 }
526}
Definition main.cpp:8
void prepareCamera(int width, int height, int format, int minFps, int maxFps)
static void onFrameAvailable(JNIEnv *env, jobject obj, jstring cameraId, QtJniTypes::AndroidImage 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 onPhotoAvailable(JNIEnv *env, jobject obj, jstring cameraId, QtJniTypes::AndroidImage image)
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 * context
static org qtproject qt android multimedia QtVideoDeviceManager
DBusConnection const char DBusError * error
GLenum mode
GLint GLsizei GLsizei height
GLboolean r
[2]
GLfloat GLfloat f
GLint GLsizei width
GLint GLsizei GLsizei GLenum format
GLdouble s
[6]
Definition qopenglext.h:235
GLint void * img
Definition qopenglext.h:233
GLsizei const GLchar *const * path
GLuint64EXT * result
[6]
@ Handler
file open(QIODevice::ReadOnly)
QFrame frame
[0]
QNetworkRequest request(url)