3package org.qtproject.qt.android.multimedia.qffmpeg;
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;
28import java.util.concurrent.CountDownLatch;
29import java.util.concurrent.TimeUnit;
31import org.qtproject.qt.android.UsedFromNativeCode;
44 CameraDevice mCameraDevice =
null;
46 volatile CountDownLatch mDeviceClosedLatch =
null;
47 QtVideoDeviceManager mVideoDeviceManager =
null;
49 HandlerThread mBackgroundThread;
53 ImageReader mPreviewImageReader =
null;
55 ImageReader mStillPhotoImageReader =
null;
56 CameraManager mCameraManager;
57 CameraCaptureSession mCaptureSession;
58 CaptureRequest.Builder mPreviewRequestBuilder;
59 CaptureRequest mPreviewRequest;
63 private static int MaxNumberFrames = 12;
70 boolean mIsStarted =
false;
72 boolean mIsTakingStillPhoto =
false;
74 private CameraSettings mCameraSettings =
new CameraSettings();
76 final SyncedMembers mSyncedMembers =
new SyncedMembers();
80 public void resetControlProperties() {
81 synchronized (mSyncedMembers) {
82 mSyncedMembers.mCameraSettings =
new CameraSettings();
87 private CameraSettings atomicCameraSettingsCopy() {
88 synchronized (mSyncedMembers) {
89 return new CameraSettings(mSyncedMembers.mCameraSettings);
93 QtExifDataHandler mExifDataHandler =
null;
99 CameraDeviceStateCallback mStateCallback =
new CameraDeviceStateCallback(
this);
104 CameraCaptureSessionStateCallback mCaptureStateCallback =
new CameraCaptureSessionStateCallback(
this);
112 class PreviewCaptureSessionCallback
extends CameraCaptureSession.CaptureCallback {
114 public void onCaptureFailed(
115 CameraCaptureSession session,
117 CaptureFailure failure)
119 super.onCaptureFailed(session,
request, failure);
127 PreviewCaptureSessionCallback mPreviewCaptureCallback =
new PreviewCaptureSessionCallback();
130 mCameraManager = (CameraManager)
context.getSystemService(
Context.CAMERA_SERVICE);
131 mVideoDeviceManager =
new QtVideoDeviceManager(
context);
132 startBackgroundThread();
135 void startBackgroundThread() {
136 mBackgroundThread =
new HandlerThread(
"CameraBackground");
137 mBackgroundThread.start();
138 mBackgroundHandler =
new Handler(mBackgroundThread.getLooper());
142 void stopBackgroundThread() {
143 mBackgroundThread.quitSafely();
145 mBackgroundThread.join();
146 mBackgroundThread =
null;
147 mBackgroundHandler =
null;
148 }
catch (Exception e) {
153 @SuppressLint(
"MissingPermission")
156 mCameraId = cameraId;
157 mCameraManager.openCamera(cameraId,mStateCallback,mBackgroundHandler);
159 }
catch (Exception e){
169 ImageReader.OnImageAvailableListener mOnStillPhotoAvailableListener =
new ImageReader.OnImageAvailableListener() {
171 public void onImageAvailable(ImageReader reader) {
172 QtCamera2.this.onStillPhotoAvailable(mCameraId,
reader.acquireLatestImage());
179 ImageReader.OnImageAvailableListener mOnPreviewImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
181 public void onImageAvailable(ImageReader reader) {
185 QtCamera2.this.onPreviewFrameAvailable(mCameraId,
img);
186 }
catch (IllegalStateException e) {
189 Log.e(
LOG_TAG,
"Image processing taking too long. Let's wait 0,5s more " + e);
192 QtCamera2.this.onPreviewFrameAvailable(mCameraId,
reader.acquireLatestImage());
193 }
catch (IllegalStateException | InterruptedException e2) {
194 Log.e(
LOG_TAG,
"Will not wait anymore. Restart camera session. " + e2);
196 String cameraId = mCameraId;
199 mPreviewImageReader.getWidth(),
200 mPreviewImageReader.getHeight(),
201 mPreviewImageReader.getImageFormat());
212 setFrameRate(minFps, maxFps);
217 if (mPreviewImageReader !=
null)
218 removeSurface(mPreviewImageReader.getSurface());
220 if (mStillPhotoImageReader !=
null)
221 removeSurface(mStillPhotoImageReader.getSurface());
223 mPreviewImageReader = ImageReader.newInstance(
width,
height,
format, MaxNumberFrames);
224 mPreviewImageReader.setOnImageAvailableListener(mOnPreviewImageAvailableListener, mBackgroundHandler);
225 addSurface(mPreviewImageReader.getSurface());
227 mStillPhotoImageReader =
228 ImageReader.newInstance(
width,
height, ImageFormat.JPEG, MaxNumberFrames);
229 mStillPhotoImageReader.setOnImageAvailableListener(mOnStillPhotoAvailableListener, mBackgroundHandler);
230 addSurface(mStillPhotoImageReader.getSurface());
233 private void setFrameRate(
int minFrameRate,
int maxFrameRate) {
234 synchronized (mSyncedMembers) {
235 if (minFrameRate <= 0 || maxFrameRate <= 0)
236 mSyncedMembers.mCameraSettings.mFpsRange =
null;
238 mSyncedMembers.mCameraSettings.mFpsRange =
new Range<>(minFrameRate, maxFrameRate);
242 boolean addSurface(Surface surface) {
243 if (mTargetSurfaces.contains(surface))
246 return mTargetSurfaces.add(surface);
249 boolean removeSurface(Surface surface) {
250 return mTargetSurfaces.remove(surface);
254 void clearSurfaces() {
255 mTargetSurfaces.clear();
259 boolean createSession() {
260 if (mCameraDevice ==
null)
266 mCameraDevice.createCaptureSession(mTargetSurfaces, mCaptureStateCallback, mBackgroundHandler);
268 }
catch (Exception exception) {
269 Log.w(
LOG_TAG,
"Failed to create a capture session:" + exception);
276 if (mCameraDevice ==
null)
279 if (mCaptureSession ==
null)
283 synchronized (mSyncedMembers) {
284 setRepeatingRequestToPreview();
285 mSyncedMembers.mIsStarted =
true;
288 }
catch (CameraAccessException exception) {
289 Log.w(
LOG_TAG,
"Failed to start preview:" + exception);
295 void stopAndClose() {
298 CountDownLatch closedLatch =
null;
301 String abortedStillPhotoCameraId =
null;
303 synchronized (mSyncedMembers) {
305 if (mSyncedMembers.mIsTakingStillPhoto)
306 abortedStillPhotoCameraId = mCameraId;
307 if (
null != mCaptureSession) {
308 mCaptureSession.close();
309 mCaptureSession =
null;
311 if (
null != mCameraDevice) {
312 closedLatch =
new CountDownLatch(1);
313 mDeviceClosedLatch = closedLatch;
314 mCameraDevice.close();
315 mCameraDevice =
null;
318 mTargetSurfaces.clear();
319 }
catch (Exception exception) {
320 Log.w(
LOG_TAG,
"Failed to stop and close:" + exception);
322 mSyncedMembers.mIsStarted =
false;
323 mSyncedMembers.mIsTakingStillPhoto =
false;
326 if (abortedStillPhotoCameraId !=
null)
330 if (closedLatch !=
null) {
332 if (!closedLatch.await(2, TimeUnit.SECONDS))
334 Log.w(
LOG_TAG,
"Timed out waiting for camera device to close");
335 }
catch (InterruptedException exception) {
336 Thread.currentThread().interrupt();
343 void finalizeStillPhoto(CameraSettings cameraSettings)
throws CameraAccessException
345 final CaptureRequest.Builder requestBuilder =
346 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
347 requestBuilder.addTarget(mStillPhotoImageReader.getSurface());
349 CaptureRequest.CONTROL_CAPTURE_INTENT,
350 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
352 applyStillPhotoSettingsToCaptureRequestBuilder(
356 mCaptureSession.capture(
357 requestBuilder.build(),
358 new CameraStillPhotoFinalizerCallback(
this),
379 void beginStillPhotoCapture() {
380 synchronized (mSyncedMembers) {
381 if (mSyncedMembers.mIsTakingStillPhoto) {
386 "beginStillPhotoCapture() was called on camera backend while there " +
387 "is already a still photo in progress. This is not supported. Likely Qt " +
393 final CameraSettings cameraSettings = atomicCameraSettingsCopy();
395 submitNewStillPhotoCapture(cameraSettings);
396 }
catch (Exception e) {
397 Log.w(
LOG_TAG,
"Failed to begin still photo capture: " + e);
405 void submitNewStillPhotoCapture(CameraSettings cameraSettings)
throws CameraAccessException
407 CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
408 CameraDevice.TEMPLATE_STILL_CAPTURE);
411 requestBuilder.addTarget(mPreviewImageReader.getSurface());
413 applyStillPhotoSettingsToCaptureRequestBuilder(
421 final boolean triggerAutoFocus = requestBuilder.get(CaptureRequest.CONTROL_AF_MODE)
422 == CaptureResult.CONTROL_AF_MODE_AUTO;
424 final Integer aeMode = requestBuilder.get(CaptureRequest.CONTROL_AE_MODE);
425 boolean triggerAutoExposure = aeMode !=
null
426 && aeMode != CaptureResult.CONTROL_AE_MODE_OFF;
428 final CameraStillPhotoPrecaptureCallback precaptureCallback =
429 new CameraStillPhotoPrecaptureCallback(
433 triggerAutoExposure);
435 mCaptureSession.setRepeatingRequest(
436 requestBuilder.build(),
443 if (triggerAutoFocus) {
445 CaptureRequest.CONTROL_AF_TRIGGER,
446 CaptureRequest.CONTROL_AF_TRIGGER_START);
448 if (triggerAutoExposure) {
450 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
451 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
455 mCaptureSession.capture(
456 requestBuilder.build(),
460 synchronized (mSyncedMembers) {
461 mSyncedMembers.mIsTakingStillPhoto =
true;
468 if (mExifDataHandler !=
null)
469 mExifDataHandler.save(
path);
474 private Rect getScalerCropRegion(
float zoomFactor)
476 Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
477 float zoomRatio = 1.0f;
478 if (zoomFactor != 0.0
f)
479 zoomRatio = 1.0f / zoomFactor;
481 int croppedWidth = activePixels.width() - (int)(activePixels.width() * zoomRatio);
482 int croppedHeight = activePixels.height() - (int)(activePixels.height() * zoomRatio);
483 return new Rect(croppedWidth/2, croppedHeight/2, activePixels.width() - croppedWidth/2,
484 activePixels.height() - croppedHeight/2);
487 private void applyZoomSettingsToRequestBuilder(CaptureRequest.Builder requBuilder,
float zoomFactor)
489 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) {
490 requBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion(zoomFactor));
492 requBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
497 void zoomTo(
float factor)
499 synchronized (mSyncedMembers) {
500 mSyncedMembers.mCameraSettings.mZoomFactor = factor;
502 if (!mSyncedMembers.mIsStarted) {
510 applyZoomSettingsToRequestBuilder(mPreviewRequestBuilder, factor);
511 mPreviewRequest = mPreviewRequestBuilder.build();
513 if (mSyncedMembers.mIsTakingStillPhoto) {
519 mCaptureSession.setRepeatingRequest(
521 mPreviewCaptureCallback,
523 }
catch (Exception exception) {
524 Log.w(
LOG_TAG,
"Failed to set zoom:" + exception);
533 void setFocusMode(
int newFocusMode)
542 if (newFocusMode == 0)
543 newAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
544 else if (newFocusMode == 5)
545 newAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
549 "received a QCamera::FocusMode from native code that is not recognized. " +
550 "Likely Qt developer bug. Ignoring.");
567 synchronized (mSyncedMembers) {
568 mSyncedMembers.mCameraSettings.mAFMode = newAfMode;
572 if (!mSyncedMembers.mIsStarted)
575 applyFocusSettingsToCaptureRequestBuilder(
576 mPreviewRequestBuilder,
577 mSyncedMembers.mCameraSettings,
579 mPreviewRequest = mPreviewRequestBuilder.build();
581 if (mSyncedMembers.mIsTakingStillPhoto) {
587 mCaptureSession.setRepeatingRequest(
589 mPreviewCaptureCallback,
591 }
catch (Exception exception) {
592 Log.w(
LOG_TAG,
"Failed to set focus mode:" + exception);
598 void setFlashMode(
String flashMode)
600 synchronized (mSyncedMembers) {
601 int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
602 if (flashModeValue < 0) {
606 mSyncedMembers.mCameraSettings.mStillPhotoFlashMode = flashModeValue;
616 public void setFocusDistance(
float distanceInput)
618 if (distanceInput < 0.f || distanceInput > 1.
f) {
621 "received out-of-bounds value when setting camera focus-distance. " +
622 "Likely Qt developer bug. Ignoring.");
629 synchronized (mSyncedMembers) {
630 mSyncedMembers.mCameraSettings.mFocusDistance = distanceInput;
634 if (!mSyncedMembers.mIsStarted)
639 if (mSyncedMembers.mCameraSettings.mAFMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
640 applyFocusSettingsToCaptureRequestBuilder(
641 mPreviewRequestBuilder,
642 mSyncedMembers.mCameraSettings,
645 mPreviewRequest = mPreviewRequestBuilder.build();
647 if (mSyncedMembers.mIsTakingStillPhoto) {
654 mCaptureSession.setRepeatingRequest(
656 mPreviewCaptureCallback,
658 }
catch (Exception exception) {
659 Log.w(
LOG_TAG,
"Failed to set focus distance:" + exception);
665 private int getTorchModeValue(
boolean mode)
667 return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
671 void setTorchMode(
boolean torchMode)
673 synchronized (mSyncedMembers) {
674 mSyncedMembers.mCameraSettings.mTorchMode = getTorchModeValue(torchMode);
676 if (mSyncedMembers.mIsStarted) {
677 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mSyncedMembers.mCameraSettings.mTorchMode);
678 mPreviewRequest = mPreviewRequestBuilder.build();
680 if (mSyncedMembers.mIsTakingStillPhoto) {
687 mCaptureSession.setRepeatingRequest(
689 mPreviewCaptureCallback,
691 }
catch (Exception exception) {
692 Log.w(
LOG_TAG,
"Failed to set flash mode:" + exception);
701 void setRepeatingRequestToPreview() throws CameraAccessException {
702 synchronized (mSyncedMembers) {
703 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
704 mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());
706 applyPreviewSettingsToCaptureRequestBuilder(
707 mPreviewRequestBuilder,
708 mSyncedMembers.mCameraSettings);
710 mPreviewRequest = mPreviewRequestBuilder.build();
711 mCaptureSession.setRepeatingRequest(
713 mPreviewCaptureCallback,
718 private void applyStillPhotoSettingsToCaptureRequestBuilder(
719 CaptureRequest.Builder requestBuilder,
720 CameraSettings cameraSettings)
723 CaptureRequest.CONTROL_CAPTURE_INTENT,
724 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
728 CaptureRequest.CONTROL_MODE,
729 CaptureRequest.CONTROL_MODE_AUTO);
733 CaptureRequest.CONTROL_AE_MODE,
734 CaptureRequest.CONTROL_AE_MODE_ON);
736 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
738 applyFocusSettingsToCaptureRequestBuilder(
746 if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
748 CaptureRequest.CONTROL_AE_MODE,
749 CaptureRequest.CONTROL_AE_MODE_ON);
755 CaptureRequest.FLASH_MODE,
756 CaptureRequest.FLASH_MODE_TORCH);
757 }
else if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) {
759 CaptureRequest.CONTROL_AE_MODE,
760 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
764 private void applyPreviewSettingsToCaptureRequestBuilder(
765 CaptureRequest.Builder requestBuilder,
766 CameraSettings cameraSettings)
768 requestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
770 applyFocusSettingsToCaptureRequestBuilder(
776 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
777 requestBuilder.set(CaptureRequest.FLASH_MODE, cameraSettings.mTorchMode);
779 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
780 if (cameraSettings.mFpsRange !=
null) {
782 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
783 cameraSettings.mFpsRange);
788 mPreviewRequestBuilder.set(
789 CaptureRequest.CONTROL_AF_TRIGGER,
790 CameraMetadata.CONTROL_AF_TRIGGER_IDLE);
791 mPreviewRequestBuilder.set(
792 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
793 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
798 void applyFocusSettingsToCaptureRequestBuilder(
799 CaptureRequest.Builder requestBuilder,
800 CameraSettings cameraSettings,
803 int desiredAfMode = cameraSettings.mAFMode;
808 if (stillPhoto && desiredAfMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
809 desiredAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
812 if (!isAfModeAvailable(desiredAfMode)) {
817 if (isAfModeAvailable(CaptureRequest.CONTROL_AF_MODE_OFF)) {
819 CaptureRequest.CONTROL_AF_MODE,
820 CaptureRequest.CONTROL_AF_MODE_OFF);
823 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE,
null);
827 requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, desiredAfMode);
830 if (desiredAfMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
831 final float lensFocusDistance = calcLensFocusDistanceFromQCameraFocusDistance(
832 cameraSettings.mFocusDistance);
833 if (lensFocusDistance < 0) {
836 "Tried to apply FocusModeManual on a camera that doesn't support "
837 +
"setting lens distance. Likely Qt developer bug. Ignoring.");
839 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, lensFocusDistance);
842 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE,
null);
849 private float calcLensFocusDistanceFromQCameraFocusDistance(
float qCameraFocusDistance) {
850 float lensMinimumFocusDistance =
851 mVideoDeviceManager.getLensInfoMinimumFocusDistance(mCameraId);
852 if (lensMinimumFocusDistance <= 0)
859 return (1.
f - qCameraFocusDistance) * lensMinimumFocusDistance;
864 private boolean isAfModeAvailable(
int afMode) {
865 if (mVideoDeviceManager ==
null || mCameraId ==
null || mCameraId.isEmpty())
867 return mVideoDeviceManager.isAfModeAvailable(mCameraId, afMode);
872 static boolean afStateIsReadyForCapture(
Integer afState) {
873 return afState ==
null
874 || afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
875 || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED;
878 static boolean aeStateIsReadyForCapture(
Integer aeState) {
879 return aeState ==
null
880 || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED
881 || aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED;
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()
QImageReader reader("image.png")
[1]
DBusConnection const char DBusError * error
GLsizei const GLchar *const * path
file open(QIODevice::ReadOnly)
QNetworkRequest request(url)
[0]