Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qandroidcamera.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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
3
4#include <QtFFmpegMediaPluginImpl/private/qandroidcamera_p.h>
5
6#include <jni.h>
7
8#include <memory>
9#include <optional>
10#include <utility>
11
12#include <QtCore/qdebug.h>
13#include <QtCore/qloggingcategory.h>
14#include <QtCore/qmetaobject.h>
15#include <QtCore/qpermissions.h>
16#include <QtCore/qreadwritelock.h>
17#include <QtCore/private/qandroidextras_p.h>
18
19#include <QtGui/qguiapplication.h>
20#include <QtGui/qscreen.h>
21
22#include <QtFFmpegMediaPluginImpl/private/qandroidvideoframefactory_p.h>
23#include <QtFFmpegMediaPluginImpl/private/qffmpegvideobuffer_p.h>
24
25#include <QtMultimedia/qmediaformat.h>
26#include <QtMultimedia/qmediadevices.h>
27#include <QtMultimedia/private/qcameradevice_p.h>
28#include <QtMultimedia/private/qmultimediautils_p.h>
29#include <QtMultimedia/private/qvideoframe_p.h>
30#include <QtMultimedia/private/qvideoframeconverter_p.h>
31#include <QtMultimedia/private/qvideotexturehelper_p.h>
32
33extern "C" {
34#include "libavutil/hwcontext.h"
35}
36
38Q_STATIC_LOGGING_CATEGORY(qLCAndroidCamera, "qt.multimedia.ffmpeg.androidCamera");
39
40// TODO: Should be reworked to just pass a pointer of the QAndroidCamera object into the QtCamera2
41// Java instance.
43Q_GLOBAL_STATIC(QAndroidCameraMap, g_qcameras)
44Q_GLOBAL_STATIC(QReadWriteLock, rwLock)
45
46namespace {
47
48// TODO: Unify format selection with cross-platform layer
49QCameraFormat getDefaultCameraFormat(const QCameraDevice & cameraDevice)
50{
51 // default settings
52 const auto defaultFrameFormat = QVideoFrameFormat::Format_YUV420P;
53 const auto defaultResolution = QSize(1920, 1080);
54 QCameraFormatPrivate *defaultFormat = new QCameraFormatPrivate{
55 .pixelFormat = defaultFrameFormat,
56 .resolution = defaultResolution,
57 .minFrameRate = 12,
58 .maxFrameRate = 30,
59 };
60
61 QCameraFormat resultFormat = defaultFormat->create();
62 const auto &supportedFormats = cameraDevice.videoFormats();
63
64 if (supportedFormats.empty() || supportedFormats.contains(resultFormat))
65 return resultFormat;
66
67 auto pixelCount = [](const QSize& resolution) {
68 Q_ASSERT(resolution.isValid());
69 return resolution.width() * resolution.height();
70 };
71
72 const int defaultPixelCount = pixelCount(defaultResolution);
73
74 // The lower the score, the better the format suits
75 int differenceScore = std::numeric_limits<int>::max();
76
77 auto calcDifferenceScore = [defaultPixelCount, pixelCount](const QCameraFormat& format) {
78 const int pixelDifference = pixelCount(format.resolution()) - defaultPixelCount;
79 // prefer:
80 // 1. 'pixels count >= default' over 'pixels count < default'
81 // 2. lower abs(pixelDifference)
82 return pixelDifference < 0
83 ? -pixelDifference
84 : std::numeric_limits<int>::min() + pixelDifference;
85 };
86
87 for (const auto &supportedFormat : supportedFormats) {
88 if (supportedFormat.pixelFormat() == defaultFrameFormat) {
89 if (supportedFormat.resolution() == defaultResolution)
90 return supportedFormat;
91
92 const int currentDifferenceScore = calcDifferenceScore(supportedFormat);
93
94 if (currentDifferenceScore < differenceScore) {
95 differenceScore = currentDifferenceScore;
96 resultFormat = supportedFormat;
97 }
98 }
99 }
100
101 return resultFormat;
102}
103
104bool checkCameraPermission()
105{
106 QCameraPermission permission;
107
108 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
109 if (!granted)
110 qCWarning(qLCAndroidCamera) << "Access to camera not granted!";
111
112 return granted;
113}
114
115int sensorOrientation(QString cameraId)
116{
117 QJniObject deviceManager(QtJniTypes::Traits<QtJniTypes::QtVideoDeviceManager>::className(),
118 QNativeInterface::QAndroidApplication::context());
119
120 if (!deviceManager.isValid()) {
121 qCWarning(qLCAndroidCamera) << "Failed to connect to Qt Video Device Manager.";
122 return 0;
123 }
124
125 return deviceManager.callMethod<jint>("getSensorOrientation",
126 QJniObject::fromString(cameraId).object<jstring>());
127}
128
129// Returns the FocusModes that are available on the physical device, for which we also have
130// an implementation.
131[[nodiscard]] static QList<QCamera::FocusMode> qGetSupportedFocusModesFromAndroidCamera(
132 const QJniObject &deviceManager,
133 const QCameraDevice &cameraDevice)
134{
135 QList<QCamera::FocusMode> returnValue;
136
137 const QStringList focusModeStrings = deviceManager.callMethod<QStringList>(
138 "getSupportedQCameraFocusModesAsStrings",
139 QJniObject::fromString(QString::fromUtf8(cameraDevice.id())).object<jstring>());
140
141 // Translate the strings into enums if possible.
142 for (const QString &focusModeString : focusModeStrings) {
143 bool ok = false;
144 const auto focusMode = static_cast<QCamera::FocusMode>(
145 QMetaEnum::fromType<QCamera::FocusMode>()
146 .keyToValue(
147 focusModeString.toLatin1().data(),
148 &ok));
149 if (ok)
150 returnValue.push_back(focusMode);
151 else
152 qCDebug(qLCAndroidCamera) <<
153 "received a QCamera::FocusMode string from Android "
154 "QtVideoDeviceManager.java that was not recognized.";
155 }
156
157 return returnValue;
158}
159
160} // namespace
161
162namespace QFFmpeg {
163
164// QAndroidCamera
165
166QAndroidCamera::QAndroidCamera(QCamera *camera)
167 : QPlatformCamera(camera)
168 , m_jniCamera(QNativeInterface::QAndroidApplication::context())
169{
170
171 m_hwAccel = QFFmpeg::HWAccel::create(AVHWDeviceType::AV_HWDEVICE_TYPE_MEDIACODEC);
172 if (camera) {
173 m_cameraDevice = camera->cameraDevice();
174 m_cameraFormat = !camera->cameraFormat().isNull() ? camera->cameraFormat()
175 : getDefaultCameraFormat(m_cameraDevice);
176 updateCameraCharacteristics();
177 }
178
179 if (qApp) {
180 connect(qApp, &QGuiApplication::applicationStateChanged,
181 this, &QAndroidCamera::onApplicationStateChanged);
182 }
183};
184
185QAndroidCamera::~QAndroidCamera()
186{
187 // stopAndClose() waits for onClosed(), possibly queued behind other calls
188 // which need rwLock
189 m_jniCamera.callMethod<void>("stopAndClose");
190 setState(State::Closed);
191
192 {
193 QWriteLocker locker(rwLock);
194 g_qcameras->remove(QString::fromUtf8(m_cameraDevice.id()));
195 }
196
197 m_jniCamera.callMethod<void>("stopBackgroundThread");
198}
199
200void QAndroidCamera::setCamera(const QCameraDevice &camera)
201{
202 const bool oldActive = isActive();
203 if (oldActive)
204 setActive(false);
205
206 // Reset all our control-members on the Java-side to default
207 // values. Then populate them again during updateCameraCharacteristics()
208 m_jniCamera.callMethod<void>("resetControlProperties");
209
210 m_cameraDevice = camera;
211 updateCameraCharacteristics();
212 m_cameraFormat = getDefaultCameraFormat(camera);
213
214 if (oldActive)
215 setActive(true);
216}
217
218std::optional<int> QAndroidCamera::ffmpegHWPixelFormat() const
219{
220 // TODO: m_androidFramePixelFormat is continuously being written to by
221 // the Java-side capture-processing background thread when receiving frame, while this
222 // function is commonly called by the media recording engine on other threads.
223 // A potential solution might include a mutex-lock and/or determining
224 // the pixelFormat ahead of time by checking what format we request
225 // when starting the Android camera capture session.
226 return QFFmpegVideoBuffer::toAVPixelFormat(m_androidFramePixelFormat);
227}
228
229QVideoFrameFormat QAndroidCamera::frameFormat() const
230{
231 QVideoFrameFormat result = QPlatformCamera::frameFormat();
232 // Apply rotation for surface only
233 result.setRotation(rotation());
234 return result;
235}
236
237// Called by the Java-side processing background thread.
238void QAndroidCamera::frameAvailable(QJniObject image, bool takePhoto)
239{
240 if ((!(m_state == State::WaitingStart || m_state == State::Started) && !m_waitingForFirstFrame)
241 || m_frameFactory == nullptr) {
242 qCWarning(qLCAndroidCamera) << "Received frame when not active... ignoring";
243 qCWarning(qLCAndroidCamera) << "state:" << m_state;
244 image.callMethod<void>("close");
245 return;
246 }
247
248 QVideoFrame videoFrame = m_frameFactory->createVideoFrame(image, rotation());
249 if (!videoFrame.isValid())
250 return;
251
252 // TODO: m_androidFramePixelFormat is written by the Java-side processing
253 // background thread, but read by the QCamera thread during QAndroid::ffmpegHWPixelFormat().
254 // This causes a race condition (not severe). We should eventually implement some
255 // synchronization strategy.
256 m_androidFramePixelFormat = videoFrame.pixelFormat(); // NOTE: Should this be updated on stills?
257 if (m_waitingForFirstFrame) {
258 m_waitingForFirstFrame = false;
259 setState(State::Started);
260 // Apply a still resolution changed during startup
261 QMetaObject::invokeMethod(this, &QAndroidCamera::applyStillResolution,
262 Qt::QueuedConnection);
263 }
264
265 videoFrame.setMirrored(m_cameraDevice.position() == QCameraDevice::Position::FrontFace);
266
267 if (!takePhoto)
268 Q_EMIT newVideoFrame(videoFrame);
269 else
270 Q_EMIT onStillPhotoCaptured(videoFrame);
271}
272
273QtVideo::Rotation QAndroidCamera::rotation() const
274{
275 // If the camera is not attached to the main display, we don't want to apply rotation
276 // based on primary screen. We assume we are an external camera.
277 const QCameraDevice::Position cameraPosition = m_cameraDevice.position();
278 const bool isExternalCamera = cameraPosition == QCameraDevice::Position::UnspecifiedPosition;
279 if (isExternalCamera)
280 return QtVideo::Rotation::None;
281
282 auto screen = QGuiApplication::primaryScreen();
283 auto screenOrientation = screen->orientation();
284 if (screenOrientation == Qt::PrimaryOrientation)
285 screenOrientation = screen->primaryOrientation();
286
287 // Display rotation is the opposite direction of the physical device rotation. We need the
288 // device rotation, that's why Landscape is 270 and InvertedLandscape is 90
289 int deviceOrientation = 0;
290 switch (screenOrientation) {
291 case Qt::PrimaryOrientation:
292 case Qt::PortraitOrientation:
293 break;
294 case Qt::LandscapeOrientation:
295 deviceOrientation = 270;
296 break;
297 case Qt::InvertedPortraitOrientation:
298 deviceOrientation = 180;
299 break;
300 case Qt::InvertedLandscapeOrientation:
301 deviceOrientation = 90;
302 break;
303 }
304
305 const int sign = (cameraPosition == QCameraDevice::Position::FrontFace) ? 1 : -1;
306 int rotation = (sensorOrientation(QString::fromUtf8(m_cameraDevice.id()))
307 - deviceOrientation * sign + 360) % 360;
308
309 return QtVideo::Rotation(rotation);
310}
311
312void QAndroidCamera::setActive(bool active)
313{
314 Q_ASSERT(thread()->isCurrentThread());
315 if (isActive() == active)
316 return;
317
318 if (!m_jniCamera.isValid()) {
319 updateError(QCamera::CameraError, QStringLiteral("No connection to Android Camera2 API"));
320 return;
321 }
322
323 if (!active || !checkCameraPermission()) {
324 m_jniCamera.callMethod<void>("stopAndClose");
325 m_jniCamera.callMethod<void>("clearSurfaces");
326 setState(State::Closed);
327 return;
328 }
329
330 QWriteLocker locker(rwLock);
331 QString cameraId = QString::fromUtf8(m_cameraDevice.id());
332 int width = m_cameraFormat.resolution().width();
333 int height = m_cameraFormat.resolution().height();
334
335 if (width < 0 || height < 0) {
336 m_cameraFormat = getDefaultCameraFormat(m_cameraDevice);
337 width = m_cameraFormat.resolution().width();
338 height = m_cameraFormat.resolution().height();
339 }
340
341 m_videoResolution = { FFALIGN(width, 16), FFALIGN(height, 16) };
342 m_stillReaderSize = stillResolution();
343
344 setState(State::WaitingOpen);
345 g_qcameras->insert(cameraId, this);
346
347 // Create frameFactory when ImageReader is created;
348 m_frameFactory = QAndroidVideoFrameFactory::create();
349
350 // Most supported format combination for ImageReaders in a session is YUV_420_888 for
351 // preview/video, and JPEG for stills
352 // TODO: Set session combinations based on device capabilities:
353 // https://developer.android.com/reference/android/hardware/camera2/CameraDevice#regular-capture
354 m_jniCamera.callMethod<void>("prepareCamera", static_cast<jint>(m_videoResolution.width()),
355 static_cast<jint>(m_videoResolution.height()),
356 static_cast<jint>(m_cameraFormat.minFrameRate()),
357 static_cast<jint>(m_cameraFormat.maxFrameRate()),
358 static_cast<jint>(m_stillReaderSize.width()),
359 static_cast<jint>(m_stillReaderSize.height()));
360
361 bool canOpen = m_jniCamera.callMethod<jboolean>("open", cameraId);
362
363 if (!canOpen) {
364 g_qcameras->remove(cameraId);
365 setState(State::Closed);
366 updateError(QCamera::CameraError,
367 QString(u"Failed to start camera: ").append(m_cameraDevice.description()));
368 }
369}
370
371// TODO: setState is currently being used by the C++ thread owning
372// the QCamera object, and the Java-side capture-processing background thread.
373// This can lead to race conditions and the m_state ending up in inconsistent states.
374// We should have a synchronization strategy in the future.
375void QAndroidCamera::setState(QAndroidCamera::State newState)
376{
377 if (newState == m_state)
378 return;
379
380 bool wasActive = isActive();
381
382 if (newState == State::Started)
383 m_state = State::Started;
384
385 if (m_state == State::Started && newState == State::Closed)
386 m_state = State::Closed;
387
388 if ((m_state == State::WaitingOpen || m_state == State::WaitingStart)
389 && newState == State::Closed) {
390
391 m_state = State::Closed;
392
393 updateError(QCamera::CameraError,
394 QString(u"Failed to start Camera %1").arg(m_cameraDevice.description()));
395 }
396
397 if (m_state == State::Closed && newState == State::WaitingOpen)
398 m_state = State::WaitingOpen;
399
400 if (m_state == State::WaitingOpen && newState == State::WaitingStart)
401 m_state = State::WaitingStart;
402
403 if (wasActive != isActive())
404 Q_EMIT activeChanged(isActive());
405}
406
407bool QAndroidCamera::setCameraFormat(const QCameraFormat &format)
408{
409 const auto chosenFormat = format.isNull() ? getDefaultCameraFormat(m_cameraDevice) : format;
410
411 if (chosenFormat == m_cameraFormat)
412 return true;
413 if (!m_cameraDevice.videoFormats().contains(chosenFormat))
414 return false;
415
416 m_cameraFormat = chosenFormat;
417
418 restartCamera();
419
420 return true;
421}
422
423void QAndroidCamera::updateCameraCharacteristics()
424{
425 if (m_cameraDevice.id().isEmpty()) {
426 cleanCameraCharacteristics();
427 return;
428 }
429
430 QJniObject deviceManager(QtJniTypes::Traits<QtJniTypes::QtVideoDeviceManager>::className(),
431 QNativeInterface::QAndroidApplication::context());
432
433 if (!deviceManager.isValid()) {
434 qCWarning(qLCAndroidCamera) << "Failed to connect to Qt Video Device Manager.";
435 cleanCameraCharacteristics();
436 return;
437 }
438
439
440 // Gather capabilities.
441 QCamera::Features newSupportedFeatures = {};
442
443 float newMaxZoom = 1.f;
444 float newMinZoom = 1.f;
445 const auto newZoomRange = deviceManager.callMethod<jfloat[]>(
446 "getZoomRange",
447 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
448 if (newZoomRange.isValid() && newZoomRange.size() == 2) {
449 newMinZoom = newZoomRange[0];
450 newMaxZoom = newZoomRange[1];
451 } else {
452 qCDebug(qLCAndroidCamera) <<
453 "received invalid float array when querying zoomRange from Android Camera2. "
454 "Likely Qt developer bug";
455 }
456
457 m_supportedFlashModes.clear();
458 m_supportedFlashModes.append(QCamera::FlashOff);
459 const QStringList flashModes = deviceManager.callMethod<QStringList>(
460 "getSupportedFlashModes",
461 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
462 for (const auto &flashMode : flashModes) {
463 if (flashMode == QLatin1String("auto"))
464 m_supportedFlashModes.append(QCamera::FlashAuto);
465 else if (flashMode == QLatin1String("on"))
466 m_supportedFlashModes.append(QCamera::FlashOn);
467 }
468
469 m_TorchModeSupported = deviceManager.callMethod<jboolean>(
470 "isTorchModeSupported",
471 QJniObject::fromString(QString::fromUtf8(m_cameraDevice.id())).object<jstring>());
472
473 m_supportedFocusModes = qGetSupportedFocusModesFromAndroidCamera(
474 deviceManager,
475 m_cameraDevice);
476
477 if (m_supportedFocusModes.contains(QCamera::FocusMode::FocusModeManual))
478 newSupportedFeatures |= QCamera::Feature::FocusDistance;
479
480
481 // Signal capability changes
482 minimumZoomFactorChanged(newMinZoom);
483 maximumZoomFactorChanged(newMaxZoom);
484 supportedFeaturesChanged(newSupportedFeatures);
485
486
487 // Apply properties
488 if (minZoomFactor() < maxZoomFactor()) {
489 // New device supports zooming. Clamp it and apply it to new camera device.
490 const float newZoomFactor = qBound(minZoomFactor(), zoomFactor(), maxZoomFactor());
491 zoomTo(newZoomFactor, -1);
492 }
493
494 if (isFlashModeSupported(flashMode()))
495 setFlashMode(flashMode());
496
497 if (isTorchModeSupported(torchMode()))
498 setTorchMode(torchMode());
499
500 if (isFocusModeSupported(focusMode()))
501 setFocusMode(focusMode());
502
503
504 // Reset properties if needed.
505 if (minZoomFactor() >= maxZoomFactor())
506 zoomFactorChanged(defaultZoomFactor());
507
508 if (!isFlashModeSupported(flashMode()))
509 flashModeChanged(defaultFlashMode());
510
511 if (!isTorchModeSupported(torchMode()))
512 torchModeChanged(defaultTorchMode());
513
514 if (!isFocusModeSupported(focusMode()))
515 focusModeChanged(defaultFocusMode());
516}
517
518// Should only be called when the camera device is set to null.
519void QAndroidCamera::cleanCameraCharacteristics()
520{
521 maximumZoomFactorChanged(1.0);
522 if (zoomFactor() != 1.0) {
523 zoomTo(1.0, -1.0);
524 }
525 if (torchMode() != QCamera::TorchOff) {
526 setTorchMode(QCamera::TorchOff);
527 }
528 m_TorchModeSupported = false;
529
530 if (flashMode() != QCamera::FlashOff) {
531 setFlashMode(QCamera::FlashOff);
532 }
533 m_supportedFlashModes.clear();
534 m_supportedFlashModes.append(QCamera::FlashOff);
535
536
537 // Reset focus mode.
538 if (focusMode() != QCamera::FocusModeAuto)
539 setFocusMode(QCamera::FocusModeAuto);
540 m_supportedFocusModes.clear();
541
542 supportedFeaturesChanged({});
543}
544
545QSize QAndroidCamera::stillResolution() const
546{
547 if (m_requestedStillResolution.isEmpty())
548 return m_videoResolution; // Not set, will use video resolution
549
550 const QList<QSize> photoResolutions = m_cameraDevice.photoResolutions();
551
552 // Use as-is with empty list, might fail
553 if (photoResolutions.isEmpty() || photoResolutions.contains(m_requestedStillResolution))
554 return m_requestedStillResolution;
555
556 return qClosestSupportedResolution(m_requestedStillResolution, photoResolutions);
557}
558
559// Restart camera now, must be called from owning thread
560void QAndroidCamera::restartCamera()
561{
562 if (!isActive())
563 return;
564
565 setActive(false);
566 setActive(true);
567}
568
569// Queue a camera restart, can be called from a background Java thread
570void QAndroidCamera::queueCameraRestart()
571{
572 if (m_restartPending.exchange(true))
573 return;
574
575 // NOTE: Queued on event loop to avoid re-entering onVideoSourceChanged
576 QMetaObject::invokeMethod(
577 this,
578 [this]() {
579 m_restartPending = false;
580 restartCamera(); // No-op if inactive
581 },
582 Qt::QueuedConnection);
583}
584
585void QAndroidCamera::setFocusDistance(float distance)
586{
587 if (QtPrivate::fuzzyCompare(focusDistance(), distance))
588 return;
589
590 if (!(supportedFeatures() & QCamera::Feature::FocusDistance)) {
591 qCWarning(qLCAndroidCamera) <<
592 Q_FUNC_INFO <<
593 "attmpted to set focus-distance on camera without support for FocusDistance feature";
594 return;
595 }
596
597 if (distance < 0 || distance > 1) {
598 qCWarning(qLCAndroidCamera) <<
599 Q_FUNC_INFO <<
600 "attempted to set camera focus-distance with out-of-bounds value";
601 return;
602 }
603
604 // focusDistance should only be applied if the focusMode is currently set to Manual.
605 // The Java function will handle this behavior. Either way, the value should be accepted so
606 // we can apply it if FocusModeManual is activated
607 m_jniCamera.callMethod<void>("setFocusDistance", distance);
608
609 focusDistanceChanged(distance);
610}
611
612void QAndroidCamera::setFocusMode(QCamera::FocusMode mode)
613{
614 if (focusMode() == mode)
615 return;
616
617 if (!isFocusModeSupported(mode)) {
618 qCWarning(qLCAndroidCamera) <<
619 Q_FUNC_INFO <<
620 QLatin1String("attempted to set focus-mode '%1' on camera where it is unsupported.")
621 .arg(QMetaEnum::fromType<QCamera::FocusMode>().valueToKey(mode));
622 return;
623 }
624
625 // If the new focus-mode is Manual, then the focusDistance
626 // needs to be applied as well. The Java function handles this.
627 m_jniCamera.callMethod<void>(
628 "setFocusMode",
629 static_cast<jint>(mode));
630
631 focusModeChanged(mode);
632}
633
634void QAndroidCamera::setFlashMode(QCamera::FlashMode mode)
635{
636 if (!isFlashModeSupported(mode))
637 return;
638
639 QString flashMode;
640 switch (mode) {
641 case QCamera::FlashAuto:
642 flashMode = QLatin1String("auto");
643 break;
644 case QCamera::FlashOn:
645 flashMode = QLatin1String("on");
646 break;
647 case QCamera::FlashOff:
648 default:
649 flashMode = QLatin1String("off");
650 break;
651 }
652
653 m_jniCamera.callMethod<void>("setFlashMode", QJniObject::fromString(flashMode).object<jstring>());
654 flashModeChanged(mode);
655}
656
657bool QAndroidCamera::isFlashModeSupported(QCamera::FlashMode mode) const
658{
659 return m_supportedFlashModes.contains(mode);
660}
661
662bool QAndroidCamera::isFlashReady() const
663{
664 // Android doesn't have an API for that.
665 // Only check if device supports more flash modes than just FlashOff.
666 return m_supportedFlashModes.size() > 1;
667}
668
669bool QAndroidCamera::isFocusModeSupported(QCamera::FocusMode mode) const
670{
671 return QPlatformCamera::isFocusModeSupported(mode) || m_supportedFocusModes.contains(mode);
672}
673
674bool QAndroidCamera::isTorchModeSupported(QCamera::TorchMode mode) const
675{
676 if (mode == QCamera::TorchOff)
677 return true;
678 else if (mode == QCamera::TorchOn)
679 return m_TorchModeSupported;
680
681 return false;
682}
683
684void QAndroidCamera::setTorchMode(QCamera::TorchMode mode)
685{
686 bool torchMode;
687 if (mode == QCamera::TorchOff) {
688 torchMode = false;
689 } else if (mode == QCamera::TorchOn) {
690 torchMode = true;
691 } else {
692 qWarning() << "Unknown Torch mode";
693 return;
694 }
695 m_jniCamera.callMethod<void>("setTorchMode", jboolean(torchMode));
696 torchModeChanged(mode);
697}
698
699void QAndroidCamera::zoomTo(float factor, float rate)
700{
701 Q_UNUSED(rate);
702
703 if (!m_cameraDevice.id().isEmpty()) {
704 m_jniCamera.callMethod<void>("zoomTo", factor);
705 }
706 zoomFactorChanged(factor);
707}
708
709void QAndroidCamera::onApplicationStateChanged()
710{
711 switch (QGuiApplication::applicationState()) {
712 case Qt::ApplicationInactive:
713 if (isActive()) {
714 setActive(false);
715 m_wasActive = true;
716 }
717 break;
718 case Qt::ApplicationActive:
719 if (m_wasActive) {
720 setActive(true);
721 m_wasActive = false;
722 }
723 break;
724 default:
725 break;
726 }
727}
728
729// Called by Java-side processing background thread.
730void QAndroidCamera::onCaptureSessionConfigured()
731{
732 bool canStart = m_jniCamera.callMethod<jboolean>("start");
733 setState(canStart ? State::WaitingStart : State::Closed);
734}
735
736// Called by Java-side processing background thread.
737void QAndroidCamera::onCaptureSessionConfigureFailed()
738{
739 setState(State::Closed);
740}
741
742// Called by Java-side processing background thread.
743// The session rebuilt for a new still reader size failed. Fall back to a full restart.
744void QAndroidCamera::onStillReaderSwapFailed()
745{
746 queueCameraRestart();
747}
748
749// Called by Java-side processing background thread.
750void QAndroidCamera::onCameraOpened()
751{
752 bool canStart = m_jniCamera.callMethod<jboolean>("createSession");
753 setState(canStart ? State::WaitingStart : State::Closed);
754}
755
756// Called by Java-side processing background thread.
757void QAndroidCamera::onCameraDisconnect()
758{
759 setState(State::Closed);
760}
761
762// Called by Java-side processing background thread.
763void QAndroidCamera::onCameraError(int reason)
764{
765 updateError(QCamera::CameraError,
766 QString(u"Capture error with Camera %1. Camera2 Api error code: %2")
767 .arg(m_cameraDevice.description())
768 .arg(reason));
769}
770
771// Called by Java-side processing background thread.
772void QAndroidCamera::onSessionActive()
773{
774 m_waitingForFirstFrame = true;
775}
776
777// Called by Java-side processing background thread.
778void QAndroidCamera::onSessionClosed()
779{
780 m_waitingForFirstFrame = false;
781 setState(State::Closed);
782}
783
784void QAndroidCamera::capture()
785{
786 m_jniCamera.callMethod<void>("beginStillPhotoCapture");
787}
788
789void QAndroidCamera::updateExif(const QString &filename)
790{
791 m_jniCamera.callMethod<void>("saveExifToFile", QJniObject::fromString(filename).object<jstring>());
792}
793
794void QAndroidCamera::setStillCaptureResolution(QSize resolution)
795{
796 Q_ASSERT(thread()->isCurrentThread());
797
798 if (resolution != m_requestedStillResolution && !resolution.isEmpty()
799 && m_cameraDevice.photoResolutions().isEmpty()) {
800 qCWarning(qLCAndroidCamera) << "Image capture resolution set to" << resolution
801 << "while list of valid photo resolutions is empty. Image "
802 "capture can fail if resolution is not supported by device.";
803 }
804 m_requestedStillResolution = resolution;
805 applyStillResolution();
806}
807
808void QAndroidCamera::applyStillResolution()
809{
810 Q_ASSERT(thread()->isCurrentThread());
811
812 const QSize newSize = stillResolution();
813 // Not started: the next start uses the new size or re-runs this, see frameAvailable()
814 if (!isActive() || m_stillReaderSize == newSize)
815 return;
816
817 if (m_jniCamera.callMethod<jboolean>("updateStillImageReader", newSize.width(),
818 newSize.height())) {
819 m_stillReaderSize = newSize;
820 return;
821 }
822
823 queueCameraRestart();
824}
825
826void QAndroidCamera::onCaptureSessionFailed(int reason, long frameNumber)
827{
828 Q_UNUSED(frameNumber);
829
830 updateError(QCamera::CameraError,
831 QStringLiteral("Capture session failure with Camera %1. Camera2 Api error code: %2")
832 .arg(m_cameraDevice.description())
833 .arg(reason));
834}
835
836// Called by Java background thread if the on-going still-photo capture fails.
837void QAndroidCamera::onStillPhotoCaptureFailed()
838{
839 // TODO: Emit a more descriptive error signal. At the time of writing, there is no
840 // suitable QImageCapture::Error enumerator for this scenario.
841 Q_EMIT onImageCaptureFailed(
842 QImageCapture::Error::ResourceError,
843 QStringLiteral("Unknown error"));
844}
845
846} // namespace QFFmpeg
847
848// JNI logic
849// The following static functions can only be called by the Java-side processing background
850// thread.
851
852#define GET_CAMERA(cameraId)
853 QString key = QJniObject(cameraId).toString();
854 QReadLocker locker(rwLock);
855 if (!g_qcameras->contains(key)) {
856 qCWarning(qLCAndroidCamera) << "Calling back a QtCamera2 after being destroyed.";
857 return;
858 }
859 QFFmpeg::QAndroidCamera *camera = g_qcameras->find(key).value();
860
861static void onPreviewFrameAvailable(JNIEnv *env, jobject obj, jstring cameraId,
862 QtJniTypes::Image image)
863{
864 Q_UNUSED(env);
865 Q_UNUSED(obj);
866 GET_CAMERA(cameraId);
867
868 camera->frameAvailable(QJniObject(image));
869}
870Q_DECLARE_JNI_NATIVE_METHOD(onPreviewFrameAvailable)
871
872static void onStillPhotoAvailable(JNIEnv *env, jobject obj, jstring cameraId,
873 QtJniTypes::Image image)
874{
875 Q_UNUSED(env);
876 Q_UNUSED(obj);
877 GET_CAMERA(cameraId);
878
879 camera->frameAvailable(QJniObject(image), true);
880}
881Q_DECLARE_JNI_NATIVE_METHOD(onStillPhotoAvailable)
882
883
884static void onCameraOpened(JNIEnv *env, jobject obj, jstring cameraId)
885{
886 Q_UNUSED(env);
887 Q_UNUSED(obj);
888 GET_CAMERA(cameraId);
889
890 camera->onCameraOpened();
891}
892Q_DECLARE_JNI_NATIVE_METHOD(onCameraOpened)
893
894static void onCameraDisconnect(JNIEnv *env, jobject obj, jstring cameraId)
895{
896 Q_UNUSED(env);
897 Q_UNUSED(obj);
898 GET_CAMERA(cameraId);
899
900 camera->onCameraDisconnect();
901}
902Q_DECLARE_JNI_NATIVE_METHOD(onCameraDisconnect)
903
904static void onCameraError(JNIEnv *env, jobject obj, jstring cameraId, jint error)
905{
906 Q_UNUSED(env);
907 Q_UNUSED(obj);
908 GET_CAMERA(cameraId);
909
910 camera->onCameraError(error);
911}
912Q_DECLARE_JNI_NATIVE_METHOD(onCameraError)
913
914static void onCaptureSessionConfigured(JNIEnv *env, jobject obj, jstring cameraId)
915{
916 Q_UNUSED(env);
917 Q_UNUSED(obj);
918 GET_CAMERA(cameraId);
919
920 camera->onCaptureSessionConfigured();
921}
922Q_DECLARE_JNI_NATIVE_METHOD(onCaptureSessionConfigured)
923
924static void onCaptureSessionConfigureFailed(JNIEnv *env, jobject obj, jstring cameraId)
925{
926 Q_UNUSED(env);
927 Q_UNUSED(obj);
928 GET_CAMERA(cameraId);
929
930 camera->onCaptureSessionConfigureFailed();
931}
932Q_DECLARE_JNI_NATIVE_METHOD(onCaptureSessionConfigureFailed)
933
934static void onStillReaderSwapFailed(JNIEnv *env, jobject obj, jstring cameraId)
935{
936 Q_UNUSED(env);
937 Q_UNUSED(obj);
938 GET_CAMERA(cameraId);
939
940 camera->onStillReaderSwapFailed();
941}
942Q_DECLARE_JNI_NATIVE_METHOD(onStillReaderSwapFailed)
943
944static void onSessionActive(JNIEnv *env, jobject obj, jstring cameraId)
945{
946 Q_UNUSED(env);
947 Q_UNUSED(obj);
948 GET_CAMERA(cameraId);
949
950 camera->onSessionActive();
951}
952Q_DECLARE_JNI_NATIVE_METHOD(onSessionActive)
953
954static void onSessionClosed(JNIEnv *env, jobject obj, jstring cameraId)
955{
956 Q_UNUSED(env);
957 Q_UNUSED(obj);
958 GET_CAMERA(cameraId);
959
960 camera->onSessionClosed();
961}
962Q_DECLARE_JNI_NATIVE_METHOD(onSessionClosed)
963
964static void onCaptureSessionFailed(JNIEnv *env, jobject obj, jstring cameraId, jint reason,
965 jlong framenumber)
966{
967 Q_UNUSED(env);
968 Q_UNUSED(obj);
969 GET_CAMERA(cameraId);
970
971 camera->onCaptureSessionFailed(reason, framenumber);
972}
973Q_DECLARE_JNI_NATIVE_METHOD(onCaptureSessionFailed)
974
975static void onStillPhotoCaptureFailed(JNIEnv *env, jobject obj, jstring cameraId)
976{
977 Q_UNUSED(env);
978 Q_UNUSED(obj);
979 GET_CAMERA(cameraId);
980
981 camera->onStillPhotoCaptureFailed();
982}
983Q_DECLARE_JNI_NATIVE_METHOD(onStillPhotoCaptureFailed)
984
985bool QFFmpeg::QAndroidCamera::registerNativeMethods()
986{
987 static const bool registered = []() {
988 return QtJniCamera2::registerNativeMethods({
989 Q_JNI_NATIVE_METHOD(onCameraOpened),
990 Q_JNI_NATIVE_METHOD(onCameraDisconnect),
991 Q_JNI_NATIVE_METHOD(onCameraError),
992 Q_JNI_NATIVE_METHOD(onCaptureSessionConfigured),
993 Q_JNI_NATIVE_METHOD(onCaptureSessionConfigureFailed),
994 Q_JNI_NATIVE_METHOD(onStillReaderSwapFailed),
995 Q_JNI_NATIVE_METHOD(onCaptureSessionFailed),
996 Q_JNI_NATIVE_METHOD(onPreviewFrameAvailable),
997 Q_JNI_NATIVE_METHOD(onStillPhotoAvailable),
998 Q_JNI_NATIVE_METHOD(onSessionActive),
999 Q_JNI_NATIVE_METHOD(onSessionClosed),
1000 Q_JNI_NATIVE_METHOD(onStillPhotoCaptureFailed),
1001 });
1002 }();
1003 return registered;
1004}
1005
1006QT_END_NAMESPACE
#define GET_CAMERA(cameraId)
QMap< QString, QFFmpeg::QAndroidCamera * > QAndroidCameraMap
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)