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;
29import org.qtproject.qt.android.UsedFromNativeCode;
42 CameraDevice mCameraDevice =
null;
43 QtVideoDeviceManager mVideoDeviceManager =
null;
45 HandlerThread mBackgroundThread;
49 ImageReader mPreviewImageReader =
null;
51 ImageReader mStillPhotoImageReader =
null;
52 CameraManager mCameraManager;
53 CameraCaptureSession mCaptureSession;
54 CaptureRequest.Builder mPreviewRequestBuilder;
55 CaptureRequest mPreviewRequest;
59 private static int MaxNumberFrames = 12;
66 boolean mIsStarted =
false;
68 boolean mIsTakingStillPhoto =
false;
70 private CameraSettings mCameraSettings =
new CameraSettings();
72 final SyncedMembers mSyncedMembers =
new SyncedMembers();
76 public void resetControlProperties() {
77 synchronized (mSyncedMembers) {
78 mSyncedMembers.mCameraSettings =
new CameraSettings();
83 private CameraSettings atomicCameraSettingsCopy() {
84 synchronized (mSyncedMembers) {
85 return new CameraSettings(mSyncedMembers.mCameraSettings);
89 QtExifDataHandler mExifDataHandler =
null;
95 CameraDeviceStateCallback mStateCallback =
new CameraDeviceStateCallback(
this);
100 CameraCaptureSessionStateCallback mCaptureStateCallback =
new CameraCaptureSessionStateCallback(
this);
108 class PreviewCaptureSessionCallback
extends CameraCaptureSession.CaptureCallback {
110 public void onCaptureFailed(
111 CameraCaptureSession session,
113 CaptureFailure failure)
115 super.onCaptureFailed(session,
request, failure);
123 PreviewCaptureSessionCallback mPreviewCaptureCallback =
new PreviewCaptureSessionCallback();
126 mCameraManager = (CameraManager)
context.getSystemService(
Context.CAMERA_SERVICE);
127 mVideoDeviceManager =
new QtVideoDeviceManager(
context);
128 startBackgroundThread();
131 void startBackgroundThread() {
132 mBackgroundThread =
new HandlerThread(
"CameraBackground");
133 mBackgroundThread.start();
134 mBackgroundHandler =
new Handler(mBackgroundThread.getLooper());
138 void stopBackgroundThread() {
139 mBackgroundThread.quitSafely();
141 mBackgroundThread.join();
142 mBackgroundThread =
null;
143 mBackgroundHandler =
null;
144 }
catch (Exception e) {
149 @SuppressLint(
"MissingPermission")
152 mCameraId = cameraId;
153 mCameraManager.openCamera(cameraId,mStateCallback,mBackgroundHandler);
155 }
catch (Exception e){
165 ImageReader.OnImageAvailableListener mOnStillPhotoAvailableListener =
new ImageReader.OnImageAvailableListener() {
167 public void onImageAvailable(ImageReader reader) {
168 QtCamera2.this.onStillPhotoAvailable(mCameraId,
reader.acquireLatestImage());
175 ImageReader.OnImageAvailableListener mOnPreviewImageAvailableListener =
new ImageReader.OnImageAvailableListener() {
177 public void onImageAvailable(ImageReader reader) {
181 QtCamera2.this.onPreviewFrameAvailable(mCameraId,
img);
182 }
catch (IllegalStateException e) {
185 Log.e(
LOG_TAG,
"Image processing taking too long. Let's wait 0,5s more " + e);
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);
192 String cameraId = mCameraId;
195 mPreviewImageReader.getWidth(),
196 mPreviewImageReader.getHeight(),
197 mPreviewImageReader.getImageFormat());
208 setFrameRate(minFps, maxFps);
213 if (mPreviewImageReader !=
null)
214 removeSurface(mPreviewImageReader.getSurface());
216 if (mStillPhotoImageReader !=
null)
217 removeSurface(mStillPhotoImageReader.getSurface());
219 mPreviewImageReader = ImageReader.newInstance(
width,
height,
format, MaxNumberFrames);
220 mPreviewImageReader.setOnImageAvailableListener(mOnPreviewImageAvailableListener, mBackgroundHandler);
221 addSurface(mPreviewImageReader.getSurface());
223 mStillPhotoImageReader =
224 ImageReader.newInstance(
width,
height, ImageFormat.JPEG, MaxNumberFrames);
225 mStillPhotoImageReader.setOnImageAvailableListener(mOnStillPhotoAvailableListener, mBackgroundHandler);
226 addSurface(mStillPhotoImageReader.getSurface());
229 private void setFrameRate(
int minFrameRate,
int maxFrameRate) {
230 synchronized (mSyncedMembers) {
231 if (minFrameRate <= 0 || maxFrameRate <= 0)
232 mSyncedMembers.mCameraSettings.mFpsRange =
null;
234 mSyncedMembers.mCameraSettings.mFpsRange =
new Range<>(minFrameRate, maxFrameRate);
238 boolean addSurface(Surface surface) {
239 if (mTargetSurfaces.contains(surface))
242 return mTargetSurfaces.add(surface);
245 boolean removeSurface(Surface surface) {
246 return mTargetSurfaces.remove(surface);
250 void clearSurfaces() {
251 mTargetSurfaces.clear();
255 boolean createSession() {
256 if (mCameraDevice ==
null)
262 mCameraDevice.createCaptureSession(mTargetSurfaces, mCaptureStateCallback, mBackgroundHandler);
264 }
catch (Exception exception) {
265 Log.w(
LOG_TAG,
"Failed to create a capture session:" + exception);
272 if (mCameraDevice ==
null)
275 if (mCaptureSession ==
null)
279 synchronized (mSyncedMembers) {
280 setRepeatingRequestToPreview();
281 mSyncedMembers.mIsStarted =
true;
284 }
catch (CameraAccessException exception) {
285 Log.w(
LOG_TAG,
"Failed to start preview:" + exception);
291 void stopAndClose() {
292 synchronized (mSyncedMembers) {
294 if (
null != mCaptureSession) {
295 mCaptureSession.close();
296 mCaptureSession =
null;
298 if (
null != mCameraDevice) {
299 mCameraDevice.close();
300 mCameraDevice =
null;
303 mTargetSurfaces.clear();
304 }
catch (Exception exception) {
305 Log.w(
LOG_TAG,
"Failed to stop and close:" + exception);
307 mSyncedMembers.mIsStarted =
false;
308 mSyncedMembers.mIsTakingStillPhoto =
false;
314 void finalizeStillPhoto(CameraSettings cameraSettings)
throws CameraAccessException
316 final CaptureRequest.Builder requestBuilder =
317 mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
318 requestBuilder.addTarget(mStillPhotoImageReader.getSurface());
320 CaptureRequest.CONTROL_CAPTURE_INTENT,
321 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
323 applyStillPhotoSettingsToCaptureRequestBuilder(
327 mCaptureSession.capture(
328 requestBuilder.build(),
329 new CameraStillPhotoFinalizerCallback(
this),
350 void beginStillPhotoCapture() {
351 synchronized (mSyncedMembers) {
352 if (mSyncedMembers.mIsTakingStillPhoto) {
357 "beginStillPhotoCapture() was called on camera backend while there " +
358 "is already a still photo in progress. This is not supported. Likely Qt " +
364 final CameraSettings cameraSettings = atomicCameraSettingsCopy();
366 submitNewStillPhotoCapture(cameraSettings);
367 }
catch (CameraAccessException e) {
368 Log.w(
LOG_TAG,
"Cannot get access to the camera: " + e);
376 void submitNewStillPhotoCapture(CameraSettings cameraSettings)
throws CameraAccessException
378 CaptureRequest.Builder requestBuilder = mCameraDevice.createCaptureRequest(
379 CameraDevice.TEMPLATE_STILL_CAPTURE);
382 requestBuilder.addTarget(mPreviewImageReader.getSurface());
384 applyStillPhotoSettingsToCaptureRequestBuilder(
392 final boolean triggerAutoFocus = requestBuilder.get(CaptureRequest.CONTROL_AF_MODE)
393 == CaptureResult.CONTROL_AF_MODE_AUTO;
395 final Integer aeMode = requestBuilder.get(CaptureRequest.CONTROL_AE_MODE);
396 boolean triggerAutoExposure = aeMode !=
null
397 && aeMode != CaptureResult.CONTROL_AE_MODE_OFF;
399 final CameraStillPhotoPrecaptureCallback precaptureCallback =
400 new CameraStillPhotoPrecaptureCallback(
404 triggerAutoExposure);
406 mCaptureSession.setRepeatingRequest(
407 requestBuilder.build(),
414 if (triggerAutoFocus) {
416 CaptureRequest.CONTROL_AF_TRIGGER,
417 CaptureRequest.CONTROL_AF_TRIGGER_START);
419 if (triggerAutoExposure) {
421 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
422 CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
426 mCaptureSession.capture(
427 requestBuilder.build(),
431 synchronized (mSyncedMembers) {
432 mSyncedMembers.mIsTakingStillPhoto =
true;
439 if (mExifDataHandler !=
null)
440 mExifDataHandler.save(
path);
445 private Rect getScalerCropRegion(
float zoomFactor)
447 Rect activePixels = mVideoDeviceManager.getActiveArraySize(mCameraId);
448 float zoomRatio = 1.0f;
449 if (zoomFactor != 0.0
f)
450 zoomRatio = 1.0f / zoomFactor;
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);
458 private void applyZoomSettingsToRequestBuilder(CaptureRequest.Builder requBuilder,
float zoomFactor)
460 if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R) {
461 requBuilder.set(CaptureRequest.SCALER_CROP_REGION, getScalerCropRegion(zoomFactor));
463 requBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
468 void zoomTo(
float factor)
470 synchronized (mSyncedMembers) {
471 mSyncedMembers.mCameraSettings.mZoomFactor = factor;
473 if (!mSyncedMembers.mIsStarted) {
481 applyZoomSettingsToRequestBuilder(mPreviewRequestBuilder, factor);
482 mPreviewRequest = mPreviewRequestBuilder.build();
484 if (mSyncedMembers.mIsTakingStillPhoto) {
490 mCaptureSession.setRepeatingRequest(
492 mPreviewCaptureCallback,
494 }
catch (Exception exception) {
495 Log.w(
LOG_TAG,
"Failed to set zoom:" + exception);
504 void setFocusMode(
int newFocusMode)
513 if (newFocusMode == 0)
514 newAfMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
515 else if (newFocusMode == 5)
516 newAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
520 "received a QCamera::FocusMode from native code that is not recognized. " +
521 "Likely Qt developer bug. Ignoring.");
538 synchronized (mSyncedMembers) {
539 mSyncedMembers.mCameraSettings.mAFMode = newAfMode;
543 if (!mSyncedMembers.mIsStarted)
546 applyFocusSettingsToCaptureRequestBuilder(
547 mPreviewRequestBuilder,
548 mSyncedMembers.mCameraSettings,
550 mPreviewRequest = mPreviewRequestBuilder.build();
552 if (mSyncedMembers.mIsTakingStillPhoto) {
558 mCaptureSession.setRepeatingRequest(
560 mPreviewCaptureCallback,
562 }
catch (Exception exception) {
563 Log.w(
LOG_TAG,
"Failed to set focus mode:" + exception);
569 void setFlashMode(
String flashMode)
571 synchronized (mSyncedMembers) {
572 int flashModeValue = mVideoDeviceManager.stringToControlAEMode(flashMode);
573 if (flashModeValue < 0) {
577 mSyncedMembers.mCameraSettings.mStillPhotoFlashMode = flashModeValue;
587 public void setFocusDistance(
float distanceInput)
589 if (distanceInput < 0.f || distanceInput > 1.
f) {
592 "received out-of-bounds value when setting camera focus-distance. " +
593 "Likely Qt developer bug. Ignoring.");
600 synchronized (mSyncedMembers) {
601 mSyncedMembers.mCameraSettings.mFocusDistance = distanceInput;
605 if (!mSyncedMembers.mIsStarted)
610 if (mSyncedMembers.mCameraSettings.mAFMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
611 applyFocusSettingsToCaptureRequestBuilder(
612 mPreviewRequestBuilder,
613 mSyncedMembers.mCameraSettings,
616 mPreviewRequest = mPreviewRequestBuilder.build();
618 if (mSyncedMembers.mIsTakingStillPhoto) {
625 mCaptureSession.setRepeatingRequest(
627 mPreviewCaptureCallback,
629 }
catch (Exception exception) {
630 Log.w(
LOG_TAG,
"Failed to set focus distance:" + exception);
636 private int getTorchModeValue(
boolean mode)
638 return mode ? CameraMetadata.FLASH_MODE_TORCH : CameraMetadata.FLASH_MODE_OFF;
642 void setTorchMode(
boolean torchMode)
644 synchronized (mSyncedMembers) {
645 mSyncedMembers.mCameraSettings.mTorchMode = getTorchModeValue(torchMode);
647 if (mSyncedMembers.mIsStarted) {
648 mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE, mSyncedMembers.mCameraSettings.mTorchMode);
649 mPreviewRequest = mPreviewRequestBuilder.build();
651 if (mSyncedMembers.mIsTakingStillPhoto) {
658 mCaptureSession.setRepeatingRequest(
660 mPreviewCaptureCallback,
662 }
catch (Exception exception) {
663 Log.w(
LOG_TAG,
"Failed to set flash mode:" + exception);
672 void setRepeatingRequestToPreview() throws CameraAccessException {
673 synchronized (mSyncedMembers) {
674 mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
675 mPreviewRequestBuilder.addTarget(mPreviewImageReader.getSurface());
677 applyPreviewSettingsToCaptureRequestBuilder(
678 mPreviewRequestBuilder,
679 mSyncedMembers.mCameraSettings);
681 mPreviewRequest = mPreviewRequestBuilder.build();
682 mCaptureSession.setRepeatingRequest(
684 mPreviewCaptureCallback,
689 private void applyStillPhotoSettingsToCaptureRequestBuilder(
690 CaptureRequest.Builder requestBuilder,
691 CameraSettings cameraSettings)
694 CaptureRequest.CONTROL_CAPTURE_INTENT,
695 CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
699 CaptureRequest.CONTROL_MODE,
700 CaptureRequest.CONTROL_MODE_AUTO);
704 CaptureRequest.CONTROL_AE_MODE,
705 CaptureRequest.CONTROL_AE_MODE_ON);
707 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
709 applyFocusSettingsToCaptureRequestBuilder(
717 if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH) {
719 CaptureRequest.CONTROL_AE_MODE,
720 CaptureRequest.CONTROL_AE_MODE_ON);
726 CaptureRequest.FLASH_MODE,
727 CaptureRequest.FLASH_MODE_TORCH);
728 }
else if (cameraSettings.mStillPhotoFlashMode == CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH) {
730 CaptureRequest.CONTROL_AE_MODE,
731 CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
735 private void applyPreviewSettingsToCaptureRequestBuilder(
736 CaptureRequest.Builder requestBuilder,
737 CameraSettings cameraSettings)
739 requestBuilder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CameraMetadata.CONTROL_CAPTURE_INTENT_VIDEO_RECORD);
741 applyFocusSettingsToCaptureRequestBuilder(
747 requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
748 requestBuilder.set(CaptureRequest.FLASH_MODE, cameraSettings.mTorchMode);
750 applyZoomSettingsToRequestBuilder(requestBuilder, cameraSettings.mZoomFactor);
751 if (cameraSettings.mFpsRange !=
null) {
753 CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
754 cameraSettings.mFpsRange);
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);
769 void applyFocusSettingsToCaptureRequestBuilder(
770 CaptureRequest.Builder requestBuilder,
771 CameraSettings cameraSettings,
774 int desiredAfMode = cameraSettings.mAFMode;
779 if (stillPhoto && desiredAfMode == CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) {
780 desiredAfMode = CaptureRequest.CONTROL_AF_MODE_AUTO;
783 if (!isAfModeAvailable(desiredAfMode)) {
788 if (isAfModeAvailable(CaptureRequest.CONTROL_AF_MODE_OFF)) {
790 CaptureRequest.CONTROL_AF_MODE,
791 CaptureRequest.CONTROL_AF_MODE_OFF);
794 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE,
null);
798 requestBuilder.set(CaptureRequest.CONTROL_AF_MODE, desiredAfMode);
801 if (desiredAfMode == CaptureRequest.CONTROL_AF_MODE_OFF) {
802 final float lensFocusDistance = calcLensFocusDistanceFromQCameraFocusDistance(
803 cameraSettings.mFocusDistance);
804 if (lensFocusDistance < 0) {
807 "Tried to apply FocusModeManual on a camera that doesn't support "
808 +
"setting lens distance. Likely Qt developer bug. Ignoring.");
810 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE, lensFocusDistance);
813 requestBuilder.set(CaptureRequest.LENS_FOCUS_DISTANCE,
null);
820 private float calcLensFocusDistanceFromQCameraFocusDistance(
float qCameraFocusDistance) {
821 float lensMinimumFocusDistance =
822 mVideoDeviceManager.getLensInfoMinimumFocusDistance(mCameraId);
823 if (lensMinimumFocusDistance <= 0)
830 return (1.
f - qCameraFocusDistance) * lensMinimumFocusDistance;
835 private boolean isAfModeAvailable(
int afMode) {
836 if (mVideoDeviceManager ==
null || mCameraId ==
null || mCameraId.isEmpty())
838 return mVideoDeviceManager.isAfModeAvailable(mCameraId, afMode);
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;
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;
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]