167QAndroidCamera::QAndroidCamera(QCamera *camera) : QPlatformCamera(camera)
169 m_jniCamera = QJniObject(QtJniTypes::Traits<QtJniTypes::QtCamera2>::className(),
170 QNativeInterface::QAndroidApplication::context());
172 m_hwAccel = QFFmpeg::HWAccel::create(AVHWDeviceType::AV_HWDEVICE_TYPE_MEDIACODEC);
174 m_cameraDevice = camera->cameraDevice();
175 m_cameraFormat = !camera->cameraFormat().isNull() ? camera->cameraFormat()
176 : getDefaultCameraFormat(m_cameraDevice);
177 updateCameraCharacteristics();
181 connect(qApp, &QGuiApplication::applicationStateChanged,
182 this, &QAndroidCamera::onApplicationStateChanged);
186QAndroidCamera::~QAndroidCamera()
189 QWriteLocker locker(rwLock);
190 g_qcameras->remove(QString::fromUtf8(m_cameraDevice.id()));
192 m_jniCamera.callMethod<
void>(
"stopAndClose");
193 setState(State::Closed);
196 m_jniCamera.callMethod<
void>(
"stopBackgroundThread");
199void QAndroidCamera::setCamera(
const QCameraDevice &camera)
201 const bool oldActive = isActive();
207 m_jniCamera.callMethod<
void>(
"resetControlProperties");
209 m_cameraDevice = camera;
210 updateCameraCharacteristics();
211 m_cameraFormat = getDefaultCameraFormat(camera);
217std::optional<
int> QAndroidCamera::ffmpegHWPixelFormat()
const
225 return QFFmpegVideoBuffer::toAVPixelFormat(m_androidFramePixelFormat);
228QVideoFrameFormat QAndroidCamera::frameFormat()
const
230 QVideoFrameFormat result = QPlatformCamera::frameFormat();
232 result.setRotation(rotation());
237void QAndroidCamera::frameAvailable(QJniObject image,
bool takePhoto)
239 if ((!(m_state == State::WaitingStart || m_state == State::Started) && !m_waitingForFirstFrame)
240 || m_frameFactory ==
nullptr) {
241 qCWarning(qLCAndroidCamera) <<
"Received frame when not active... ignoring";
242 qCWarning(qLCAndroidCamera) <<
"state:" << m_state;
243 image.callMethod<
void>(
"close");
247 QVideoFrame videoFrame = m_frameFactory->createVideoFrame(image, rotation());
248 if (!videoFrame.isValid())
255 m_androidFramePixelFormat = videoFrame.pixelFormat();
256 if (m_waitingForFirstFrame) {
257 m_waitingForFirstFrame =
false;
258 setState(State::Started);
261 videoFrame.setMirrored(m_cameraDevice.position() == QCameraDevice::Position::FrontFace);
264 emit newVideoFrame(videoFrame);
266 emit onStillPhotoCaptured(videoFrame);
269QtVideo::Rotation QAndroidCamera::rotation()
const
273 const QCameraDevice::Position cameraPosition = m_cameraDevice.position();
274 const bool isExternalCamera = cameraPosition == QCameraDevice::Position::UnspecifiedPosition;
275 if (isExternalCamera)
276 return QtVideo::Rotation::None;
278 auto screen = QGuiApplication::primaryScreen();
279 auto screenOrientation = screen->orientation();
280 if (screenOrientation == Qt::PrimaryOrientation)
281 screenOrientation = screen->primaryOrientation();
285 int deviceOrientation = 0;
286 switch (screenOrientation) {
287 case Qt::PrimaryOrientation:
288 case Qt::PortraitOrientation:
290 case Qt::LandscapeOrientation:
291 deviceOrientation = 270;
293 case Qt::InvertedPortraitOrientation:
294 deviceOrientation = 180;
296 case Qt::InvertedLandscapeOrientation:
297 deviceOrientation = 90;
301 const int sign = (cameraPosition == QCameraDevice::Position::FrontFace) ? 1 : -1;
302 int rotation = (sensorOrientation(QString::fromUtf8(m_cameraDevice.id()))
303 - deviceOrientation * sign + 360) % 360;
305 return QtVideo::Rotation(rotation);
308void QAndroidCamera::setActive(
bool active)
310 if (isActive() == active)
313 if (!m_jniCamera.isValid()) {
314 updateError(QCamera::CameraError, QStringLiteral(
"No connection to Android Camera2 API"));
318 if (active && checkCameraPermission()) {
319 QWriteLocker locker(rwLock);
320 int width = m_cameraFormat.resolution().width();
321 int height = m_cameraFormat.resolution().height();
323 if (width < 0 || height < 0) {
324 m_cameraFormat = getDefaultCameraFormat(m_cameraDevice);
325 width = m_cameraFormat.resolution().width();
326 height = m_cameraFormat.resolution().height();
329 width = FFALIGN(width, 16);
330 height = FFALIGN(height, 16);
332 setState(State::WaitingOpen);
333 g_qcameras->insert(QString::fromUtf8(m_cameraDevice.id()),
this);
336 m_frameFactory = QAndroidVideoFrameFactory::create();
341 const static int imageFormat =
342 QJniObject::getStaticField<QtJniTypes::ImageFormat, jint>(
"YUV_420_888");
343 m_jniCamera.callMethod<
void>(
"prepareCamera", jint(width), jint(height),
344 jint(imageFormat), jint(m_cameraFormat.minFrameRate()),
345 jint(m_cameraFormat.maxFrameRate()));
347 bool canOpen = m_jniCamera.callMethod<jboolean>(
349 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
352 g_qcameras->remove(QString::fromUtf8(m_cameraDevice.id()));
353 setState(State::Closed);
354 updateError(QCamera::CameraError,
355 QString(u"Failed to start camera: ").append(m_cameraDevice.description()));
358 m_jniCamera.callMethod<
void>(
"stopAndClose");
359 m_jniCamera.callMethod<
void>(
"clearSurfaces");
360 setState(State::Closed);
368void QAndroidCamera::setState(QAndroidCamera::State newState)
370 if (newState == m_state)
373 bool wasActive = isActive();
375 if (newState == State::Started)
376 m_state = State::Started;
378 if (m_state == State::Started && newState == State::Closed)
379 m_state = State::Closed;
381 if ((m_state == State::WaitingOpen || m_state == State::WaitingStart)
382 && newState == State::Closed) {
384 m_state = State::Closed;
386 updateError(QCamera::CameraError,
387 QString(u"Failed to start Camera %1").arg(m_cameraDevice.description()));
390 if (m_state == State::Closed && newState == State::WaitingOpen)
391 m_state = State::WaitingOpen;
393 if (m_state == State::WaitingOpen && newState == State::WaitingStart)
394 m_state = State::WaitingStart;
396 if (wasActive != isActive())
397 emit activeChanged(isActive());
400bool QAndroidCamera::setCameraFormat(
const QCameraFormat &format)
402 const auto chosenFormat = format.isNull() ? getDefaultCameraFormat(m_cameraDevice) : format;
404 if (chosenFormat == m_cameraFormat)
406 if (!m_cameraDevice.videoFormats().contains(chosenFormat))
409 m_cameraFormat = chosenFormat;
420void QAndroidCamera::updateCameraCharacteristics()
422 if (m_cameraDevice.id().isEmpty()) {
423 cleanCameraCharacteristics();
427 QJniObject deviceManager(QtJniTypes::Traits<QtJniTypes::QtVideoDeviceManager>::className(),
428 QNativeInterface::QAndroidApplication::context());
430 if (!deviceManager.isValid()) {
431 qCWarning(qLCAndroidCamera) <<
"Failed to connect to Qt Video Device Manager.";
432 cleanCameraCharacteristics();
438 QCamera::Features newSupportedFeatures = {};
440 float newMaxZoom = 1.f;
441 float newMinZoom = 1.f;
442 const auto newZoomRange = deviceManager.callMethod<jfloat[]>(
444 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
445 if (newZoomRange.isValid() && newZoomRange.size() == 2) {
446 newMinZoom = newZoomRange[0];
447 newMaxZoom = newZoomRange[1];
449 qCDebug(qLCAndroidCamera) <<
450 "received invalid float array when querying zoomRange from Android Camera2. "
451 "Likely Qt developer bug";
454 m_supportedFlashModes.clear();
455 m_supportedFlashModes.append(QCamera::FlashOff);
456 const QStringList flashModes = deviceManager.callMethod<QStringList>(
457 "getSupportedFlashModes",
458 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
459 for (
const auto &flashMode : flashModes) {
460 if (flashMode == QLatin1String(
"auto"))
461 m_supportedFlashModes.append(QCamera::FlashAuto);
462 else if (flashMode == QLatin1String(
"on"))
463 m_supportedFlashModes.append(QCamera::FlashOn);
466 m_TorchModeSupported = deviceManager.callMethod<jboolean>(
467 "isTorchModeSupported",
468 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
470 m_supportedFocusModes = qGetSupportedFocusModesFromAndroidCamera(
474 if (m_supportedFocusModes.contains(QCamera::FocusMode::FocusModeManual))
475 newSupportedFeatures |= QCamera::Feature::FocusDistance;
479 minimumZoomFactorChanged(newMinZoom);
480 maximumZoomFactorChanged(newMaxZoom);
481 supportedFeaturesChanged(newSupportedFeatures);
485 if (minZoomFactor() < maxZoomFactor()) {
487 const float newZoomFactor = qBound(zoomFactor(), minZoomFactor(), maxZoomFactor());
488 zoomTo(newZoomFactor, -1);
491 if (isFlashModeSupported(flashMode()))
492 setFlashMode(flashMode());
494 if (isTorchModeSupported(torchMode()))
495 setTorchMode(torchMode());
497 if (isFocusModeSupported(focusMode()))
498 setFocusMode(focusMode());
502 if (minZoomFactor() >= maxZoomFactor())
503 zoomFactorChanged(defaultZoomFactor());
505 if (!isFlashModeSupported(flashMode()))
506 flashModeChanged(defaultFlashMode());
508 if (!isTorchModeSupported(torchMode()))
509 torchModeChanged(defaultTorchMode());
511 if (!isFocusModeSupported(focusMode()))
512 focusModeChanged(defaultFocusMode());
516void QAndroidCamera::cleanCameraCharacteristics()
518 maximumZoomFactorChanged(1.0);
519 if (zoomFactor() != 1.0) {
522 if (torchMode() != QCamera::TorchOff) {
523 setTorchMode(QCamera::TorchOff);
525 m_TorchModeSupported =
false;
527 if (flashMode() != QCamera::FlashOff) {
528 setFlashMode(QCamera::FlashOff);
530 m_supportedFlashModes.clear();
531 m_supportedFlashModes.append(QCamera::FlashOff);
535 if (focusMode() != QCamera::FocusModeAuto)
536 setFocusMode(QCamera::FocusModeAuto);
537 m_supportedFocusModes.clear();
539 supportedFeaturesChanged({});
542void QAndroidCamera::setFocusDistance(
float distance)
544 if (QtPrivate::fuzzyCompare(focusDistance(), distance))
547 if (!(supportedFeatures() & QCamera::Feature::FocusDistance)) {
548 qCWarning(qLCAndroidCamera) <<
550 "attmpted to set focus-distance on camera without support for FocusDistance feature";
554 if (distance < 0 || distance > 1) {
555 qCWarning(qLCAndroidCamera) <<
557 "attempted to set camera focus-distance with out-of-bounds value";
564 m_jniCamera.callMethod<
void>(
"setFocusDistance", distance);
566 focusDistanceChanged(distance);
569void QAndroidCamera::setFocusMode(QCamera::FocusMode mode)
571 if (focusMode() == mode)
574 if (!isFocusModeSupported(mode)) {
575 qCWarning(qLCAndroidCamera) <<
577 QLatin1String(
"attempted to set focus-mode '%1' on camera where it is unsupported.")
578 .arg(QMetaEnum::fromType<QCamera::FocusMode>().valueToKey(mode));
584 m_jniCamera.callMethod<
void>(
586 static_cast<jint>(mode));
588 focusModeChanged(mode);
591void QAndroidCamera::setFlashMode(QCamera::FlashMode mode)
593 if (!isFlashModeSupported(mode))
598 case QCamera::FlashAuto:
599 flashMode = QLatin1String(
"auto");
601 case QCamera::FlashOn:
602 flashMode = QLatin1String(
"on");
604 case QCamera::FlashOff:
606 flashMode = QLatin1String(
"off");
610 m_jniCamera.callMethod<
void>(
"setFlashMode", QJniObject::fromString(flashMode).object<jstring>());
611 flashModeChanged(mode);
614bool QAndroidCamera::isFlashModeSupported(QCamera::FlashMode mode)
const
616 return m_supportedFlashModes.contains(mode);
619bool QAndroidCamera::isFlashReady()
const
623 return m_supportedFlashModes.size() > 1;
626bool QAndroidCamera::isFocusModeSupported(QCamera::FocusMode mode)
const
628 return QPlatformCamera::isFocusModeSupported(mode) || m_supportedFocusModes.contains(mode);
631bool QAndroidCamera::isTorchModeSupported(QCamera::TorchMode mode)
const
633 if (mode == QCamera::TorchOff)
635 else if (mode == QCamera::TorchOn)
636 return m_TorchModeSupported;
641void QAndroidCamera::setTorchMode(QCamera::TorchMode mode)
644 if (mode == QCamera::TorchOff) {
646 }
else if (mode == QCamera::TorchOn) {
649 qWarning() <<
"Unknown Torch mode";
652 m_jniCamera.callMethod<
void>(
"setTorchMode", jboolean(torchMode));
653 torchModeChanged(mode);
656void QAndroidCamera::zoomTo(
float factor,
float rate)
660 if (!m_cameraDevice.id().isEmpty()) {
661 m_jniCamera.callMethod<
void>(
"zoomTo", factor);
663 zoomFactorChanged(factor);
666void QAndroidCamera::onApplicationStateChanged()
668 switch (QGuiApplication::applicationState()) {
669 case Qt::ApplicationInactive:
675 case Qt::ApplicationActive:
687void QAndroidCamera::onCaptureSessionConfigured()
689 bool canStart = m_jniCamera.callMethod<jboolean>(
"start");
690 setState(canStart ? State::WaitingStart : State::Closed);
694void QAndroidCamera::onCaptureSessionConfigureFailed()
696 setState(State::Closed);
700void QAndroidCamera::onCameraOpened()
702 bool canStart = m_jniCamera.callMethod<jboolean>(
"createSession");
703 setState(canStart ? State::WaitingStart : State::Closed);
707void QAndroidCamera::onCameraDisconnect()
709 setState(State::Closed);
713void QAndroidCamera::onCameraError(
int reason)
715 updateError(QCamera::CameraError,
716 QString(u"Capture error with Camera %1. Camera2 Api error code: %2")
717 .arg(m_cameraDevice.description())
722void QAndroidCamera::onSessionActive()
724 m_waitingForFirstFrame =
true;
728void QAndroidCamera::onSessionClosed()
730 m_waitingForFirstFrame =
false;
731 setState(State::Closed);
734void QAndroidCamera::capture()
736 m_jniCamera.callMethod<
void>(
"beginStillPhotoCapture");
739void QAndroidCamera::updateExif(
const QString &filename)
741 m_jniCamera.callMethod<
void>(
"saveExifToFile", QJniObject::fromString(filename).object<jstring>());
744void QAndroidCamera::onCaptureSessionFailed(
int reason,
long frameNumber)
746 Q_UNUSED(frameNumber);
748 updateError(QCamera::CameraError,
749 QStringLiteral(
"Capture session failure with Camera %1. Camera2 Api error code: %2")
750 .arg(m_cameraDevice.description())
755void QAndroidCamera::onStillPhotoCaptureFailed()
759 emit onImageCaptureFailed(
760 QImageCapture::Error::ResourceError,
761 QStringLiteral(
"Unknown error"));
893bool QFFmpeg::QAndroidCamera::registerNativeMethods()
895 static const bool registered = []() {
896 return QJniEnvironment().registerNativeMethods(
897 QtJniTypes::Traits<QtJniTypes::QtCamera2>::className(),
899 Q_JNI_NATIVE_METHOD(onCameraOpened),
900 Q_JNI_NATIVE_METHOD(onCameraDisconnect),
901 Q_JNI_NATIVE_METHOD(onCameraError),
902 Q_JNI_NATIVE_METHOD(onCaptureSessionConfigured),
903 Q_JNI_NATIVE_METHOD(onCaptureSessionConfigureFailed),
904 Q_JNI_NATIVE_METHOD(onCaptureSessionFailed),
905 Q_JNI_NATIVE_METHOD(onPreviewFrameAvailable),
906 Q_JNI_NATIVE_METHOD(onStillPhotoAvailable),
907 Q_JNI_NATIVE_METHOD(onSessionActive),
908 Q_JNI_NATIVE_METHOD(onSessionClosed),
909 Q_JNI_NATIVE_METHOD(onStillPhotoCaptureFailed),