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