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