165QAndroidCamera::QAndroidCamera(QCamera *camera) : QPlatformCamera(camera)
167 m_jniCamera = QJniObject(QtJniTypes::Traits<QtJniTypes::QtCamera2>::className(),
168 QNativeInterface::QAndroidApplication::context());
170 m_hwAccel = QFFmpeg::HWAccel::create(AVHWDeviceType::AV_HWDEVICE_TYPE_MEDIACODEC);
172 m_cameraDevice = camera->cameraDevice();
173 m_cameraFormat = !camera->cameraFormat().isNull() ? camera->cameraFormat()
174 : getDefaultCameraFormat(m_cameraDevice);
175 updateCameraCharacteristics();
179 connect(qApp, &QGuiApplication::applicationStateChanged,
180 this, &QAndroidCamera::onApplicationStateChanged);
184QAndroidCamera::~QAndroidCamera()
187 QWriteLocker locker(rwLock);
188 g_qcameras->remove(QString::fromUtf8(m_cameraDevice.id()));
190 m_jniCamera.callMethod<
void>(
"stopAndClose");
191 setState(State::Closed);
194 m_jniCamera.callMethod<
void>(
"stopBackgroundThread");
197void QAndroidCamera::setCamera(
const QCameraDevice &camera)
199 const bool oldActive = isActive();
205 m_jniCamera.callMethod<
void>(
"resetControlProperties");
207 m_cameraDevice = camera;
208 updateCameraCharacteristics();
209 m_cameraFormat = getDefaultCameraFormat(camera);
215std::optional<
int> QAndroidCamera::ffmpegHWPixelFormat()
const
223 return QFFmpegVideoBuffer::toAVPixelFormat(m_androidFramePixelFormat);
226QVideoFrameFormat QAndroidCamera::frameFormat()
const
228 QVideoFrameFormat result = QPlatformCamera::frameFormat();
230 result.setRotation(rotation());
235void QAndroidCamera::frameAvailable(QJniObject image,
bool takePhoto)
237 if ((!(m_state == State::WaitingStart || m_state == State::Started) && !m_waitingForFirstFrame)
238 || m_frameFactory ==
nullptr) {
239 qCWarning(qLCAndroidCamera) <<
"Received frame when not active... ignoring";
240 qCWarning(qLCAndroidCamera) <<
"state:" << m_state;
241 image.callMethod<
void>(
"close");
245 QVideoFrame videoFrame = m_frameFactory->createVideoFrame(image, rotation());
246 if (!videoFrame.isValid())
253 m_androidFramePixelFormat = videoFrame.pixelFormat();
254 if (m_waitingForFirstFrame) {
255 m_waitingForFirstFrame =
false;
256 setState(State::Started);
259 videoFrame.setMirrored(m_cameraDevice.position() == QCameraDevice::Position::FrontFace);
262 emit newVideoFrame(videoFrame);
264 emit onStillPhotoCaptured(videoFrame);
267QtVideo::Rotation QAndroidCamera::rotation()
const
271 const QCameraDevice::Position cameraPosition = m_cameraDevice.position();
272 const bool isExternalCamera = cameraPosition == QCameraDevice::Position::UnspecifiedPosition;
273 if (isExternalCamera)
274 return QtVideo::Rotation::None;
276 auto screen = QGuiApplication::primaryScreen();
277 auto screenOrientation = screen->orientation();
278 if (screenOrientation == Qt::PrimaryOrientation)
279 screenOrientation = screen->primaryOrientation();
283 int deviceOrientation = 0;
284 switch (screenOrientation) {
285 case Qt::PrimaryOrientation:
286 case Qt::PortraitOrientation:
288 case Qt::LandscapeOrientation:
289 deviceOrientation = 270;
291 case Qt::InvertedPortraitOrientation:
292 deviceOrientation = 180;
294 case Qt::InvertedLandscapeOrientation:
295 deviceOrientation = 90;
299 const int sign = (cameraPosition == QCameraDevice::Position::FrontFace) ? 1 : -1;
300 int rotation = (sensorOrientation(QString::fromUtf8(m_cameraDevice.id()))
301 - deviceOrientation * sign + 360) % 360;
303 return QtVideo::Rotation(rotation);
306void QAndroidCamera::setActive(
bool active)
308 if (isActive() == active)
311 if (!m_jniCamera.isValid()) {
312 updateError(QCamera::CameraError, QStringLiteral(
"No connection to Android Camera2 API"));
316 if (active && checkCameraPermission()) {
317 QWriteLocker locker(rwLock);
318 int width = m_cameraFormat.resolution().width();
319 int height = m_cameraFormat.resolution().height();
321 if (width < 0 || height < 0) {
322 m_cameraFormat = getDefaultCameraFormat(m_cameraDevice);
323 width = m_cameraFormat.resolution().width();
324 height = m_cameraFormat.resolution().height();
327 width = FFALIGN(width, 16);
328 height = FFALIGN(height, 16);
330 setState(State::WaitingOpen);
331 g_qcameras->insert(QString::fromUtf8(m_cameraDevice.id()),
this);
334 m_frameFactory = QAndroidVideoFrameFactory::create();
339 const static int imageFormat =
340 QJniObject::getStaticField<QtJniTypes::ImageFormat, jint>(
"YUV_420_888");
341 m_jniCamera.callMethod<
void>(
"prepareCamera", jint(width), jint(height),
342 jint(imageFormat), jint(m_cameraFormat.minFrameRate()),
343 jint(m_cameraFormat.maxFrameRate()));
345 bool canOpen = m_jniCamera.callMethod<jboolean>(
347 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
350 g_qcameras->remove(QString::fromUtf8(m_cameraDevice.id()));
351 setState(State::Closed);
352 updateError(QCamera::CameraError,
353 QString(u"Failed to start camera: ").append(m_cameraDevice.description()));
356 m_jniCamera.callMethod<
void>(
"stopAndClose");
357 m_jniCamera.callMethod<
void>(
"clearSurfaces");
358 setState(State::Closed);
366void QAndroidCamera::setState(QAndroidCamera::State newState)
368 if (newState == m_state)
371 bool wasActive = isActive();
373 if (newState == State::Started)
374 m_state = State::Started;
376 if (m_state == State::Started && newState == State::Closed)
377 m_state = State::Closed;
379 if ((m_state == State::WaitingOpen || m_state == State::WaitingStart)
380 && newState == State::Closed) {
382 m_state = State::Closed;
384 updateError(QCamera::CameraError,
385 QString(u"Failed to start Camera %1").arg(m_cameraDevice.description()));
388 if (m_state == State::Closed && newState == State::WaitingOpen)
389 m_state = State::WaitingOpen;
391 if (m_state == State::WaitingOpen && newState == State::WaitingStart)
392 m_state = State::WaitingStart;
394 if (wasActive != isActive())
395 emit activeChanged(isActive());
398bool QAndroidCamera::setCameraFormat(
const QCameraFormat &format)
400 const auto chosenFormat = format.isNull() ? getDefaultCameraFormat(m_cameraDevice) : format;
402 if (chosenFormat == m_cameraFormat)
404 if (!m_cameraDevice.videoFormats().contains(chosenFormat))
407 m_cameraFormat = chosenFormat;
418void QAndroidCamera::updateCameraCharacteristics()
420 if (m_cameraDevice.id().isEmpty()) {
421 cleanCameraCharacteristics();
425 QJniObject deviceManager(QtJniTypes::Traits<QtJniTypes::QtVideoDeviceManager>::className(),
426 QNativeInterface::QAndroidApplication::context());
428 if (!deviceManager.isValid()) {
429 qCWarning(qLCAndroidCamera) <<
"Failed to connect to Qt Video Device Manager.";
430 cleanCameraCharacteristics();
436 QCamera::Features newSupportedFeatures = {};
438 float newMaxZoom = 1.f;
439 float newMinZoom = 1.f;
440 const auto newZoomRange = deviceManager.callMethod<jfloat[]>(
442 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
443 if (newZoomRange.isValid() && newZoomRange.size() == 2) {
444 newMinZoom = newZoomRange[0];
445 newMaxZoom = newZoomRange[1];
447 qCDebug(qLCAndroidCamera) <<
448 "received invalid float array when querying zoomRange from Android Camera2. "
449 "Likely Qt developer bug";
452 m_supportedFlashModes.clear();
453 m_supportedFlashModes.append(QCamera::FlashOff);
454 const QStringList flashModes = deviceManager.callMethod<QStringList>(
455 "getSupportedFlashModes",
456 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
457 for (
const auto &flashMode : flashModes) {
458 if (flashMode == QLatin1String(
"auto"))
459 m_supportedFlashModes.append(QCamera::FlashAuto);
460 else if (flashMode == QLatin1String(
"on"))
461 m_supportedFlashModes.append(QCamera::FlashOn);
464 m_TorchModeSupported = deviceManager.callMethod<jboolean>(
465 "isTorchModeSupported",
466 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
468 m_supportedFocusModes = qGetSupportedFocusModesFromAndroidCamera(
472 if (m_supportedFocusModes.contains(QCamera::FocusMode::FocusModeManual))
473 newSupportedFeatures |= QCamera::Feature::FocusDistance;
477 minimumZoomFactorChanged(newMinZoom);
478 maximumZoomFactorChanged(newMaxZoom);
479 supportedFeaturesChanged(newSupportedFeatures);
483 if (minZoomFactor() < maxZoomFactor()) {
485 const float newZoomFactor = qBound(zoomFactor(), minZoomFactor(), maxZoomFactor());
486 zoomTo(newZoomFactor, -1);
489 if (isFlashModeSupported(flashMode()))
490 setFlashMode(flashMode());
492 if (isTorchModeSupported(torchMode()))
493 setTorchMode(torchMode());
495 if (isFocusModeSupported(focusMode()))
496 setFocusMode(focusMode());
500 if (minZoomFactor() >= maxZoomFactor())
501 zoomFactorChanged(defaultZoomFactor());
503 if (!isFlashModeSupported(flashMode()))
504 flashModeChanged(defaultFlashMode());
506 if (!isTorchModeSupported(torchMode()))
507 torchModeChanged(defaultTorchMode());
509 if (!isFocusModeSupported(focusMode()))
510 focusModeChanged(defaultFocusMode());
514void QAndroidCamera::cleanCameraCharacteristics()
516 maximumZoomFactorChanged(1.0);
517 if (zoomFactor() != 1.0) {
520 if (torchMode() != QCamera::TorchOff) {
521 setTorchMode(QCamera::TorchOff);
523 m_TorchModeSupported =
false;
525 if (flashMode() != QCamera::FlashOff) {
526 setFlashMode(QCamera::FlashOff);
528 m_supportedFlashModes.clear();
529 m_supportedFlashModes.append(QCamera::FlashOff);
533 if (focusMode() != QCamera::FocusModeAuto)
534 setFocusMode(QCamera::FocusModeAuto);
535 m_supportedFocusModes.clear();
537 supportedFeaturesChanged({});
540void QAndroidCamera::setFocusDistance(
float distance)
542 if (qFuzzyCompare(focusDistance(), distance))
545 if (!(supportedFeatures() & QCamera::Feature::FocusDistance)) {
546 qCWarning(qLCAndroidCamera) <<
548 "attmpted to set focus-distance on camera without support for FocusDistance feature";
552 if (distance < 0 || distance > 1) {
553 qCWarning(qLCAndroidCamera) <<
555 "attempted to set camera focus-distance with out-of-bounds value";
562 m_jniCamera.callMethod<
void>(
"setFocusDistance", distance);
564 focusDistanceChanged(distance);
567void QAndroidCamera::setFocusMode(QCamera::FocusMode mode)
569 if (focusMode() == mode)
572 if (!isFocusModeSupported(mode)) {
573 qCWarning(qLCAndroidCamera) <<
575 QLatin1String(
"attempted to set focus-mode '%1' on camera where it is unsupported.")
576 .arg(QMetaEnum::fromType<QCamera::FocusMode>().valueToKey(mode));
582 m_jniCamera.callMethod<
void>(
584 static_cast<jint>(mode));
586 focusModeChanged(mode);
589void QAndroidCamera::setFlashMode(QCamera::FlashMode mode)
591 if (!isFlashModeSupported(mode))
596 case QCamera::FlashAuto:
597 flashMode = QLatin1String(
"auto");
599 case QCamera::FlashOn:
600 flashMode = QLatin1String(
"on");
602 case QCamera::FlashOff:
604 flashMode = QLatin1String(
"off");
608 m_jniCamera.callMethod<
void>(
"setFlashMode", QJniObject::fromString(flashMode).object<jstring>());
609 flashModeChanged(mode);
612bool QAndroidCamera::isFlashModeSupported(QCamera::FlashMode mode)
const
614 return m_supportedFlashModes.contains(mode);
617bool QAndroidCamera::isFlashReady()
const
621 return m_supportedFlashModes.size() > 1;
624bool QAndroidCamera::isFocusModeSupported(QCamera::FocusMode mode)
const
626 return QPlatformCamera::isFocusModeSupported(mode) || m_supportedFocusModes.contains(mode);
629bool QAndroidCamera::isTorchModeSupported(QCamera::TorchMode mode)
const
631 if (mode == QCamera::TorchOff)
633 else if (mode == QCamera::TorchOn)
634 return m_TorchModeSupported;
639void QAndroidCamera::setTorchMode(QCamera::TorchMode mode)
642 if (mode == QCamera::TorchOff) {
644 }
else if (mode == QCamera::TorchOn) {
647 qWarning() <<
"Unknown Torch mode";
650 m_jniCamera.callMethod<
void>(
"setTorchMode", jboolean(torchMode));
651 torchModeChanged(mode);
654void QAndroidCamera::zoomTo(
float factor,
float rate)
658 if (!m_cameraDevice.id().isEmpty()) {
659 m_jniCamera.callMethod<
void>(
"zoomTo", factor);
661 zoomFactorChanged(factor);
664void QAndroidCamera::onApplicationStateChanged()
666 switch (QGuiApplication::applicationState()) {
667 case Qt::ApplicationInactive:
673 case Qt::ApplicationActive:
685void QAndroidCamera::onCaptureSessionConfigured()
687 bool canStart = m_jniCamera.callMethod<jboolean>(
"start");
688 setState(canStart ? State::WaitingStart : State::Closed);
692void QAndroidCamera::onCaptureSessionConfigureFailed()
694 setState(State::Closed);
698void QAndroidCamera::onCameraOpened()
700 bool canStart = m_jniCamera.callMethod<jboolean>(
"createSession");
701 setState(canStart ? State::WaitingStart : State::Closed);
705void QAndroidCamera::onCameraDisconnect()
707 setState(State::Closed);
711void QAndroidCamera::onCameraError(
int reason)
713 updateError(QCamera::CameraError,
714 QString(u"Capture error with Camera %1. Camera2 Api error code: %2")
715 .arg(m_cameraDevice.description())
720void QAndroidCamera::onSessionActive()
722 m_waitingForFirstFrame =
true;
726void QAndroidCamera::onSessionClosed()
728 m_waitingForFirstFrame =
false;
729 setState(State::Closed);
732void QAndroidCamera::capture()
734 m_jniCamera.callMethod<
void>(
"beginStillPhotoCapture");
737void QAndroidCamera::updateExif(
const QString &filename)
739 m_jniCamera.callMethod<
void>(
"saveExifToFile", QJniObject::fromString(filename).object<jstring>());
742void QAndroidCamera::onCaptureSessionFailed(
int reason,
long frameNumber)
744 Q_UNUSED(frameNumber);
746 updateError(QCamera::CameraError,
747 QStringLiteral(
"Capture session failure with Camera %1. Camera2 Api error code: %2")
748 .arg(m_cameraDevice.description())
753void QAndroidCamera::onStillPhotoCaptureFailed()
757 emit onImageCaptureFailed(
758 QImageCapture::Error::ResourceError,
759 QStringLiteral(
"Unknown error"));
891bool QFFmpeg::QAndroidCamera::registerNativeMethods()
893 static const bool registered = []() {
894 return QJniEnvironment().registerNativeMethods(
895 QtJniTypes::Traits<QtJniTypes::QtCamera2>::className(),
897 Q_JNI_NATIVE_METHOD(onCameraOpened),
898 Q_JNI_NATIVE_METHOD(onCameraDisconnect),
899 Q_JNI_NATIVE_METHOD(onCameraError),
900 Q_JNI_NATIVE_METHOD(onCaptureSessionConfigured),
901 Q_JNI_NATIVE_METHOD(onCaptureSessionConfigureFailed),
902 Q_JNI_NATIVE_METHOD(onCaptureSessionFailed),
903 Q_JNI_NATIVE_METHOD(onPreviewFrameAvailable),
904 Q_JNI_NATIVE_METHOD(onStillPhotoAvailable),
905 Q_JNI_NATIVE_METHOD(onSessionActive),
906 Q_JNI_NATIVE_METHOD(onSessionClosed),
907 Q_JNI_NATIVE_METHOD(onStillPhotoCaptureFailed),