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
qavfcamerabase.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <QtCore/qcoreapplication.h>
7#include <QtCore/qmetaobject.h>
8#include <QtCore/qpermissions.h>
9#include <QtCore/qset.h>
10#include <QtCore/qsystemdetection.h>
11#include <QtCore/private/qcore_mac_p.h>
12
13#include <QtMultimedia/private/qavfcameradebug_p.h>
14#include <QtMultimedia/private/qavfcamerautility_p.h>
15#include <QtMultimedia/private/qavfhelpers_p.h>
16#include <QtMultimedia/private/qcameradevice_p.h>
17#include <QtMultimedia/private/qplatformmediaintegration_p.h>
18
19QT_USE_NAMESPACE
20
21using namespace Qt::Literals;
22
23namespace {
24
25// All these methods to work with exposure/ISO/SS in custom mode do not support macOS.
26
27#ifdef Q_OS_IOS
28
29// Misc. helpers to check values/ranges:
30
31bool qt_check_exposure_duration(AVCaptureDevice *captureDevice, CMTime duration)
32{
33 Q_ASSERT(captureDevice);
34
35 AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
36 if (!activeFormat) {
37 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to obtain capture device format";
38 return false;
39 }
40
41 return CMTimeCompare(duration, activeFormat.minExposureDuration) != -1
42 && CMTimeCompare(activeFormat.maxExposureDuration, duration) != -1;
43}
44
45bool qt_check_ISO_value(AVCaptureDevice *captureDevice, int newISO)
46{
47 Q_ASSERT(captureDevice);
48
49 AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
50 if (!activeFormat) {
51 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to obtain capture device format";
52 return false;
53 }
54
55 return !(newISO < activeFormat.minISO || newISO > activeFormat.maxISO);
56}
57
58bool qt_exposure_duration_equal(AVCaptureDevice *captureDevice, qreal qDuration)
59{
60 Q_ASSERT(captureDevice);
61 const CMTime avDuration = CMTimeMakeWithSeconds(qDuration, captureDevice.exposureDuration.timescale);
62 return !CMTimeCompare(avDuration, captureDevice.exposureDuration);
63}
64
65bool qt_iso_equal(AVCaptureDevice *captureDevice, int iso)
66{
67 Q_ASSERT(captureDevice);
68 return QtPrivate::fuzzyCompare(float(iso), captureDevice.ISO);
69}
70
71bool qt_exposure_bias_equal(AVCaptureDevice *captureDevice, qreal bias)
72{
73 Q_ASSERT(captureDevice);
74 return QtPrivate::fuzzyCompare(bias, qreal(captureDevice.exposureTargetBias));
75}
76
77// Converters:
78
79bool qt_convert_exposure_mode(AVCaptureDevice *captureDevice, QCamera::ExposureMode mode,
80 AVCaptureExposureMode &avMode)
81{
82 // Test if mode supported and convert.
83 Q_ASSERT(captureDevice);
84
85 if (mode == QCamera::ExposureAuto) {
86 if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
87 avMode = AVCaptureExposureModeContinuousAutoExposure;
88 return true;
89 }
90 }
91
92 if (mode == QCamera::ExposureManual) {
93 if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom]) {
94 avMode = AVCaptureExposureModeCustom;
95 return true;
96 }
97 }
98
99 return false;
100}
101
102#endif // defined(Q_OS_IOS)
103
104} // Unnamed namespace.
105
106QAVFCameraBase::QAVFCameraBase(QCamera *camera)
107 : QPlatformCamera(camera)
108{
109 Q_ASSERT(camera);
110}
111
112QAVFCameraBase::~QAVFCameraBase()
113{
114}
115
116bool QAVFCameraBase::isActive() const
117{
118 return m_active;
119}
120
121void QAVFCameraBase::setActive(bool active)
122{
123 if (m_active == active)
124 return;
125 if (m_cameraDevice.isNull() && active)
126 return;
127 if (!checkCameraPermission())
128 return;
129
130 q23::expected<void, QString> result = onActiveChanged(active);
131 if (!result) {
132 // Assert that implementation does not return error when we are deactivating.
133 Q_ASSERT(active);
134 updateError(QCamera::Error::CameraError, result.error());
135 return;
136 }
137
138 m_active = active;
139 Q_EMIT activeChanged(m_active);
140}
141
142void QAVFCameraBase::setCamera(const QCameraDevice &camera)
143{
144 if (m_cameraDevice == camera)
145 return;
146
147 // TODO: At the time of writing, a quirk in the QCamera code is that
148 // device-changes are implemented as setCamera() + setCameraFormat({}).
149 // This means that camera-changes always involve resetting the format
150 // to what is default for the new device. In the future, we should
151 // ideally pass down the new format as a parameter, and perform
152 // the two operations as a single atomic one.
153 //
154 // onCameraDeviceChanged() is allowed to shut down the camera session
155 // and clean up resources if configuration fails for the new device.
156 // This causes QTBUG-141376. Until we improve the architecture, this
157 // code will apply camera-device and format as one atomic operation.
158 QCameraFormat newFormat = findBestCameraFormat(camera);
159 Q_ASSERT(!newFormat.isNull());
160
161 // TODO: onCameraDeviceChanged can fail to keep the camera-session active
162 // if we are switching to a camera-device that is not available. In the
163 // future this should return a q23::expected and let us handle the error.
164 onCameraDeviceChanged(camera, newFormat);
165
166 m_cameraDevice = camera;
167 m_cameraFormat = newFormat;
168
169 updateSupportedFeatures(camera);
170 updateCameraConfiguration(camera);
171}
172
173bool QAVFCameraBase::setCameraFormat(const QCameraFormat &newFormat)
174{
175 if (!newFormat.isNull() && !m_cameraDevice.videoFormats().contains(newFormat))
176 return false;
177
178 const QCameraFormat resolvedFormat = newFormat.isNull() ? findBestCameraFormat(m_cameraDevice) : newFormat;
179 // If we still couldn't find a suitable default format (such as when none are available)
180 // we don't accept the value.
181 if (resolvedFormat.isNull())
182 return false;
183
184 const bool applySuccess = tryApplyCameraFormat(resolvedFormat);
185 if (!applySuccess)
186 return false;
187
188 m_cameraFormat = resolvedFormat;
189
190 return true;
191}
192
193AVCaptureDevice *QAVFCameraBase::device() const
194{
195 return tryGetAvCaptureDevice(m_cameraDevice);
196}
197
198AVCaptureDevice *QAVFCameraBase::tryGetAvCaptureDevice(const QCameraDevice &device)
199{
200 QByteArray deviceId = device.id();
201 if (deviceId.isEmpty())
202 return nullptr;
203
204 QMacAutoReleasePool autoReleasePool;
205
206 NSString *nsString = [NSString stringWithUTF8String:deviceId.constData()];
207 if (nsString == nullptr)
208 return nullptr;
209 return [AVCaptureDevice deviceWithUniqueID:nsString];
210}
211
212namespace
213{
214
215// Only used for setting focus-mode when we have no device attached
216// to this QCamera.
217bool qt_focus_mode_supported(QCamera::FocusMode mode)
218{
219 return mode == QCamera::FocusModeAuto;
220}
221
222}
223
224void QAVFCameraBase::setFocusMode(QCamera::FocusMode mode)
225{
226 if (mode == focusMode())
227 return;
228 forceSetFocusMode(mode);
229}
230
231// Does not check if new QCamera::FocusMode is same as old one.
232void QAVFCameraBase::forceSetFocusMode(QCamera::FocusMode mode)
233{
234 if (!isFocusModeSupported(mode)) {
235 qCDebug(qLcCamera)
236 << Q_FUNC_INFO
237 << u"attempted to set focus-mode '%1' on camera where it is unsupported."_s.arg(
238 QMetaEnum::fromType<QCamera::FocusMode>().valueToKey(mode));
239 return;
240 }
241
242 AVCaptureDevice *captureDevice = device();
243 if (!captureDevice) {
244 if (qt_focus_mode_supported(mode)) {
245 focusModeChanged(mode);
246 } else {
247 qCDebug(qLcCamera) << Q_FUNC_INFO
248 << "focus mode not supported";
249 }
250 return;
251 }
252
253 if (mode == QCamera::FocusModeManual) {
254 // If we the new focus-mode is 'Manual', then we need to lock
255 // lens focus position according to focusDistance().
256 //
257 // At this point, all relevant settings should have valid values
258 // by handling input in setFocusDistance.
259 applyFocusDistanceToAVCaptureDevice(focusDistance());
260 } else {
261 // Apply the focus mode to the AVCaptureDevice.
262 const AVFConfigurationLock lock(captureDevice);
263 if (!lock) {
264 qCDebug(qLcCamera) <<
265 Q_FUNC_INFO <<
266 "failed to lock for configuration";
267 return;
268 }
269 // Note: We have to set the focus-mode even if capture-device reports
270 // the existing value as being the same. For example, this is the case
271 // when using the 'setFocusModeLockedWithLensPosition' functionality.
272 captureDevice.focusMode = AVCaptureFocusModeContinuousAutoFocus;
273 }
274
275 focusModeChanged(mode);
276}
277
278bool QAVFCameraBase::isFocusModeSupported(QCamera::FocusMode mode) const
279{
280 AVCaptureDevice *captureDevice = device();
281 if (captureDevice) {
282 switch (mode) {
283 case QCamera::FocusModeAuto:
284 return [captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];
285#ifdef Q_OS_IOS
286 case QCamera::FocusModeManual:
287 return captureDevice.lockingFocusWithCustomLensPositionSupported;
288#endif // Q_OS_IOS
289 case QCamera::FocusModeAutoNear:
290 Q_FALLTHROUGH();
291 case QCamera::FocusModeAutoFar:
292#ifdef Q_OS_IOS
293 return captureDevice.autoFocusRangeRestrictionSupported
294 && [captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];
295#else
296 break;
297#endif // Q_OS_IOS
298 default:
299 break;
300 }
301 }
302 return mode == QCamera::FocusModeAuto;
303}
304
305void QAVFCameraBase::setCustomFocusPoint(const QPointF &point)
306{
307 if (customFocusPoint() == point)
308 return;
309
310 if (!QRectF(0.f, 0.f, 1.f, 1.f).contains(point)) {
311 // ### release custom focus point, tell the camera to focus where it wants...
312 qCDebug(qLcCamera) << Q_FUNC_INFO << "invalid focus point (out of range)";
313 return;
314 }
315
316 AVCaptureDevice *captureDevice = device();
317 if (!captureDevice)
318 return;
319
320 if ([captureDevice isFocusPointOfInterestSupported]) {
321 const AVFConfigurationLock lock(captureDevice);
322 if (!lock) {
323 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
324 return;
325 }
326
327 const CGPoint focusPOI = CGPointMake(point.x(), point.y());
328 [captureDevice setFocusPointOfInterest:focusPOI];
329 if (focusMode() != QCamera::FocusModeAuto)
330 [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
331
332 customFocusPointChanged(point);
333 }
334}
335
336void QAVFCameraBase::applyFocusDistanceToAVCaptureDevice(float distance)
337{
338#ifdef Q_OS_IOS
339 AVCaptureDevice *captureDevice = device();
340 if (!captureDevice)
341 return;
342
343 // This should generally always return true assuming the check
344 // for supportedFeatures succeeds, but the consequence of it not being
345 // the case is a thrown exception that we don't handle.
346 // So it can be useful to keep it here just in case.
347 if (!captureDevice.lockingFocusWithCustomLensPositionSupported) {
348 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to apply focusDistance to AVCaptureDevice.";
349 return;
350 }
351
352 {
353 AVFConfigurationLock lock(captureDevice);
354 if (!lock) {
355 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock camera for configuration";
356 return;
357 }
358 [captureDevice setFocusModeLockedWithLensPosition:distance completionHandler:nil];
359 }
360#else
361 Q_UNUSED(distance);
362#endif
363}
364
365void QAVFCameraBase::setFocusDistance(float distance)
366{
367 if (QtPrivate::fuzzyCompare(focusDistance(), distance))
368 return;
369
370 if (!(supportedFeatures() & QCamera::Feature::FocusDistance)) {
371 qCWarning(qLcCamera) <<
372 Q_FUNC_INFO <<
373 "attmpted to set focus-distance on camera without support for FocusDistance feature";
374 return;
375 }
376
377 if (distance < 0 || distance > 1) {
378 qCWarning(qLcCamera) <<
379 Q_FUNC_INFO <<
380 "attempted to set camera focus-distance with out-of-bounds value";
381 return;
382 }
383
384 // If we are not currently in FocusModeManual, just accept the
385 // value and apply it sometime later during setFocusMode(Manual).
386 if (focusMode() == QCamera::FocusModeManual) {
387 applyFocusDistanceToAVCaptureDevice(distance);
388 }
389
390 focusDistanceChanged(distance);
391}
392
393void QAVFCameraBase::updateCameraConfiguration(const QCameraDevice &cameraDevice)
394{
395 // TODO: Several of these properties should only be applied to the physical
396 // device if we are currently active.
397
398 AVCaptureDevice *captureDevice = tryGetAvCaptureDevice(cameraDevice);
399 // TODO: If the new incoming device is not connected, should reset
400 // existing capabilities.
401 if (!captureDevice) {
402 qCDebug(qLcCamera) << Q_FUNC_INFO << "capture device is nil when trying to update QAVFCamera";
403 return;
404 }
405
406 // We require an active format to update several of the valid ranges. I.e max zoom factor.
407 AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
408 if (!activeFormat) {
409 qCDebug(qLcCamera) << Q_FUNC_INFO << "capture device has no active format when trying to update QAVFCamera";
410 return;
411 }
412
413 // First we gather new capabilities
414
415 // Handle flash/torch capabilities.
416 isFlashSupported = isFlashAutoSupported = false;
417 isTorchSupported = isTorchAutoSupported = false;
418 if (captureDevice.hasFlash) {
419 if ([captureDevice isFlashModeSupported:AVCaptureFlashModeOn])
420 isFlashSupported = true;
421 if ([captureDevice isFlashModeSupported:AVCaptureFlashModeAuto])
422 isFlashAutoSupported = true;
423 }
424 if (captureDevice.hasTorch) {
425 if ([captureDevice isTorchModeSupported:AVCaptureTorchModeOn])
426 isTorchSupported = true;
427 if ([captureDevice isTorchModeSupported:AVCaptureTorchModeAuto])
428 isTorchAutoSupported = true;
429 }
430
431 flashReadyChanged(isFlashSupported);
432#ifdef Q_OS_IOS
433 minimumZoomFactorChanged(captureDevice.minAvailableVideoZoomFactor);
434 maximumZoomFactorChanged(activeFormat.videoMaxZoomFactor);
435#endif // Q_OS_IOS
436
437
438 // The we apply properties to the camera if they are supported.
439
440 if (minZoomFactor() < maxZoomFactor()) {
441 // Zoom is supported, clamp it and apply it.
442 //
443 // TODO: zoom has no public API to allow instant zooming, only smooth transitions
444 // but the Darwin implementation of zoomTo uses rate < 0 to allow this.
445 forceZoomTo(qBound(zoomFactor(), minZoomFactor(), maxZoomFactor()), -1);
446 }
447 applyFlashSettings();
448
449 // Focus mode properties
450 // This will also take care of applying FocusDistance if applicable.
451 if (isFocusModeSupported(focusMode()))
452 forceSetFocusMode(focusMode());
453
454
455 // Reset properties that are not supported on the new camera device.
456
457 if (minZoomFactor() >= maxZoomFactor())
458 zoomFactorChanged(defaultZoomFactor());
459
460 if (!(supportedFeatures() & QCamera::Feature::FocusDistance))
461 focusDistanceChanged(defaultFocusDistance());
462
463 if (!isFocusModeSupported(focusMode()))
464 focusModeChanged(defaultFocusMode());
465
466 // TODO: Several of the following features have inconsistent behavior
467 // and currently do not have any Android implementation. These features
468 // should be revised.
469
470 const AVFConfigurationLock lock(captureDevice);
471 if (!lock) {
472 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
473 return;
474 }
475
476 if ([captureDevice isFocusPointOfInterestSupported]) {
477 auto point = customFocusPoint();
478 const CGPoint focusPOI = CGPointMake(point.x(), point.y());
479 [captureDevice setFocusPointOfInterest:focusPOI];
480 }
481
482#ifdef Q_OS_IOS
483 CMTime newDuration = AVCaptureExposureDurationCurrent;
484 bool setCustomMode = false;
485
486 float exposureTime = manualExposureTime();
487 if (exposureTime > 0
488 && !qt_exposure_duration_equal(captureDevice, exposureTime)) {
489 newDuration = CMTimeMakeWithSeconds(exposureTime, captureDevice.exposureDuration.timescale);
490 if (!qt_check_exposure_duration(captureDevice, newDuration)) {
491 qCDebug(qLcCamera) << Q_FUNC_INFO << "requested exposure duration is out of range";
492 return;
493 }
494 setCustomMode = true;
495 }
496
497 float newISO = AVCaptureISOCurrent;
498 int iso = manualIsoSensitivity();
499 if (iso > 0 && !qt_iso_equal(captureDevice, iso)) {
500 newISO = iso;
501 if (!qt_check_ISO_value(captureDevice, newISO)) {
502 qCDebug(qLcCamera) << Q_FUNC_INFO << "requested ISO value is out of range";
503 return;
504 }
505 setCustomMode = true;
506 }
507
508 float bias = exposureCompensation();
509 if (bias != 0 && !qt_exposure_bias_equal(captureDevice, bias)) {
510 // TODO: mixed fpns.
511 if (bias < captureDevice.minExposureTargetBias || bias > captureDevice.maxExposureTargetBias) {
512 qCDebug(qLcCamera) << Q_FUNC_INFO << "exposure compensation value is"
513 << "out of range";
514 return;
515 }
516 [captureDevice setExposureTargetBias:bias completionHandler:nil];
517 }
518
519 // Setting shutter speed (exposure duration) or ISO values
520 // also reset exposure mode into Custom. With this settings
521 // we ignore any attempts to set exposure mode.
522
523 if (setCustomMode) {
524 [captureDevice setExposureModeCustomWithDuration:newDuration
525 ISO:newISO
526 completionHandler:nil];
527 return;
528 }
529
530 QCamera::ExposureMode qtMode = exposureMode();
531 AVCaptureExposureMode avMode = AVCaptureExposureModeContinuousAutoExposure;
532 if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) {
533 qCDebug(qLcCamera) << Q_FUNC_INFO << "requested exposure mode is not supported";
534 return;
535 }
536
537 captureDevice.exposureMode = avMode;
538#endif // Q_OS_IOS
539}
540
541void QAVFCameraBase::updateSupportedFeatures(const QCameraDevice &cameraDevice)
542{
543 QCamera::Features features;
544 AVCaptureDevice *captureDevice = tryGetAvCaptureDevice(cameraDevice);
545
546 if (captureDevice) {
547 if ([captureDevice isFocusPointOfInterestSupported])
548 features |= QCamera::Feature::CustomFocusPoint;
549
550#ifdef Q_OS_IOS
551 AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
552
553 // IsoSensitivity
554 if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom] &&
555 activeFormat.minISO < activeFormat.maxISO)
556 features |= QCamera::Feature::IsoSensitivity;
557
558 // ColorTemperature
559 if (captureDevice.lockingWhiteBalanceWithCustomDeviceGainsSupported)
560 features |= QCamera::Feature::ColorTemperature;
561
562 // Exposure compensation
563 if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom] &&
564 captureDevice.minExposureTargetBias < captureDevice.maxExposureTargetBias)
565 features |= QCamera::Feature::ExposureCompensation;
566
567 // Manual exposure time
568 if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom] &&
569 CMTimeCompare(activeFormat.minExposureDuration, activeFormat.maxExposureDuration) == -1)
570 features |= QCamera::Feature::ManualExposureTime;
571
572 // No point in reporting the feature as supported if we don't also
573 // report the corresponding focus-mode as supported.
574 if (isFocusModeSupported(QCamera::FocusModeManual) &&
575 [captureDevice isLockingFocusWithCustomLensPositionSupported])
576 features |= QCamera::Feature::FocusDistance;
577#endif
578 }
579
580 supportedFeaturesChanged(features);
581}
582
583void QAVFCameraBase::zoomTo(float factor, float rate)
584{
585 if (zoomFactor() == factor)
586 return;
587 forceZoomTo(factor, rate);
588}
589
590// Internal function that gives us the option to run zoomTo
591// without skipping early return when factor == QCamera::zoomFactor()
592// Useful when switching camera-device.
593void QAVFCameraBase::forceZoomTo(float factor, float rate)
594{
595#ifdef Q_OS_IOS
596 AVCaptureDevice *captureDevice = device();
597 if (!captureDevice || !captureDevice.activeFormat)
598 return;
599
600 factor = qBound(captureDevice.minAvailableVideoZoomFactor, factor,
601 captureDevice.activeFormat.videoMaxZoomFactor);
602
603 {
604 const AVFConfigurationLock lock(captureDevice);
605 if (!lock) {
606 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
607 return;
608 }
609
610 if (rate <= 0)
611 captureDevice.videoZoomFactor = factor;
612 else
613 [captureDevice rampToVideoZoomFactor:factor withRate:rate];
614 }
615 zoomFactorChanged(factor);
616#else
617 Q_UNUSED(rate);
618 Q_UNUSED(factor);
619#endif
620}
621
622void QAVFCameraBase::setFlashMode(QCamera::FlashMode mode)
623{
624 if (flashMode() == mode)
625 return;
626
627 if (!isFlashModeSupported(mode)) {
628 qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported mode" << mode;
629 return;
630 }
631
632 flashModeChanged(mode);
633
634 if (!isActive())
635 return;
636
637 applyFlashSettings();
638}
639
640bool QAVFCameraBase::isFlashModeSupported(QCamera::FlashMode mode) const
641{
642 if (mode == QCamera::FlashOff)
643 return true;
644 else if (mode == QCamera::FlashOn)
645 return isFlashSupported;
646 else //if (mode == QCamera::FlashAuto)
647 return isFlashAutoSupported;
648}
649
650bool QAVFCameraBase::isFlashReady() const
651{
652 if (!isActive())
653 return false;
654
655 AVCaptureDevice *captureDevice = device();
656 if (!captureDevice)
657 return false;
658
659 if (!captureDevice.hasFlash)
660 return false;
661
662 if (!isFlashModeSupported(flashMode()))
663 return false;
664
665 // AVCaptureDevice's docs:
666 // "The flash may become unavailable if, for example,
667 // the device overheats and needs to cool off."
668 return [captureDevice isFlashAvailable];
669}
670
671void QAVFCameraBase::setTorchMode(QCamera::TorchMode mode)
672{
673 if (torchMode() == mode)
674 return;
675
676 if (isActive() && !isTorchModeSupported(mode)) {
677 qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported torch mode" << mode;
678 return;
679 }
680
681 torchModeChanged(mode);
682
683 if (!isActive())
684 return;
685
686 applyFlashSettings();
687}
688
689bool QAVFCameraBase::isTorchModeSupported(QCamera::TorchMode mode) const
690{
691 if (mode == QCamera::TorchOff)
692 return true;
693 else if (mode == QCamera::TorchOn)
694 return isTorchSupported;
695 else //if (mode == QCamera::TorchAuto)
696 return isTorchAutoSupported;
697}
698
699void QAVFCameraBase::setExposureMode(QCamera::ExposureMode qtMode)
700{
701#ifdef Q_OS_IOS
702 if (qtMode != QCamera::ExposureAuto && qtMode != QCamera::ExposureManual) {
703 qCDebug(qLcCamera) << Q_FUNC_INFO << "exposure mode not supported";
704 return;
705 }
706
707 AVCaptureDevice *captureDevice = device();
708 if (!captureDevice) {
709 exposureModeChanged(qtMode);
710 return;
711 }
712
713 AVCaptureExposureMode avMode = AVCaptureExposureModeContinuousAutoExposure;
714 if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) {
715 qCDebug(qLcCamera) << Q_FUNC_INFO << "exposure mode not supported";
716 return;
717 }
718
719 const AVFConfigurationLock lock(captureDevice);
720 if (!lock) {
721 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
722 << "for configuration";
723 return;
724 }
725
726 [captureDevice setExposureMode:avMode];
727 exposureModeChanged(qtMode);
728#else
729 Q_UNUSED(qtMode);
730#endif
731}
732
733bool QAVFCameraBase::isExposureModeSupported(QCamera::ExposureMode mode) const
734{
735 if (mode == QCamera::ExposureAuto)
736 return true;
737 if (mode != QCamera::ExposureManual)
738 return false;
739
740 AVCaptureDevice *captureDevice = device();
741 return captureDevice && [captureDevice isExposureModeSupported:AVCaptureExposureModeCustom];
742}
743
744void QAVFCameraBase::applyFlashSettings()
745{
746 AVCaptureDevice *captureDevice = device();
747 if (!captureDevice) {
748 qCDebug(qLcCamera) << Q_FUNC_INFO << "no capture device found";
749 return;
750 }
751
752 const AVFConfigurationLock lock(captureDevice);
753
754 if (captureDevice.hasFlash) {
755 const auto mode = flashMode();
756
757 auto setAvFlashModeSafe = [&captureDevice](AVCaptureFlashMode avFlashMode) {
758 // Note, in some cases captureDevice.hasFlash == false even though
759 // no there're no supported flash modes.
760 if ([captureDevice isFlashModeSupported:avFlashMode])
761 captureDevice.flashMode = avFlashMode;
762 else
763 qCDebug(qLcCamera) << "Attempt to setup unsupported flash mode " << avFlashMode;
764 };
765
766 if (mode == QCamera::FlashOff) {
767 setAvFlashModeSafe(AVCaptureFlashModeOff);
768 } else {
769 if ([captureDevice isFlashAvailable]) {
770 if (mode == QCamera::FlashOn)
771 setAvFlashModeSafe(AVCaptureFlashModeOn);
772 else if (mode == QCamera::FlashAuto)
773 setAvFlashModeSafe(AVCaptureFlashModeAuto);
774 } else {
775 qCDebug(qLcCamera) << Q_FUNC_INFO << "flash is not available at the moment";
776 }
777 }
778 }
779
780 if (captureDevice.hasTorch) {
781 const auto mode = torchMode();
782
783 auto setAvTorchModeSafe = [&captureDevice](AVCaptureTorchMode avTorchMode) {
784 if ([captureDevice isTorchModeSupported:avTorchMode])
785 captureDevice.torchMode = avTorchMode;
786 else
787 qCDebug(qLcCamera) << "Attempt to setup unsupported torch mode " << avTorchMode;
788 };
789
790 if (mode == QCamera::TorchOff) {
791 setAvTorchModeSafe(AVCaptureTorchModeOff);
792 } else {
793 if ([captureDevice isTorchAvailable]) {
794 if (mode == QCamera::TorchOn)
795 setAvTorchModeSafe(AVCaptureTorchModeOn);
796 else if (mode == QCamera::TorchAuto)
797 setAvTorchModeSafe(AVCaptureTorchModeAuto);
798 } else {
799 qCDebug(qLcCamera) << Q_FUNC_INFO << "torch is not available at the moment";
800 }
801 }
802 }
803}
804
805
806void QAVFCameraBase::setExposureCompensation(float bias)
807{
808#ifdef Q_OS_IOS
809 AVCaptureDevice *captureDevice = device();
810 if (!captureDevice) {
811 exposureCompensationChanged(bias);
812 return;
813 }
814
815 bias = qBound(captureDevice.minExposureTargetBias, bias, captureDevice.maxExposureTargetBias);
816
817 const AVFConfigurationLock lock(captureDevice);
818 if (!lock) {
819 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
820 return;
821 }
822
823 [captureDevice setExposureTargetBias:bias completionHandler:nil];
824 exposureCompensationChanged(bias);
825#else
826 Q_UNUSED(bias);
827#endif
828}
829
830void QAVFCameraBase::setManualExposureTime(float value)
831{
832#ifdef Q_OS_IOS
833 if (value < 0) {
834 setExposureMode(QCamera::ExposureAuto);
835 return;
836 }
837
838 AVCaptureDevice *captureDevice = device();
839 if (!captureDevice) {
840 exposureTimeChanged(value);
841 return;
842 }
843
844 const CMTime newDuration = CMTimeMakeWithSeconds(value, captureDevice.exposureDuration.timescale);
845 if (!qt_check_exposure_duration(captureDevice, newDuration)) {
846 qCDebug(qLcCamera) << Q_FUNC_INFO << "shutter speed value is out of range";
847 return;
848 }
849
850 const AVFConfigurationLock lock(captureDevice);
851 if (!lock) {
852 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
853 return;
854 }
855
856 // Setting the shutter speed (exposure duration in Apple's terms,
857 // since there is no shutter actually) will also reset
858 // exposure mode into custom mode.
859 [captureDevice setExposureModeCustomWithDuration:newDuration
860 ISO:AVCaptureISOCurrent
861 completionHandler:nil];
862
863 exposureTimeChanged(value);
864
865#else
866 Q_UNUSED(value);
867#endif
868}
869
870float QAVFCameraBase::exposureTime() const
871{
872#ifdef Q_OS_IOS
873 AVCaptureDevice *captureDevice = device();
874 if (!captureDevice)
875 return -1.;
876 auto duration = captureDevice.exposureDuration;
877 return CMTimeGetSeconds(duration);
878#else
879 return -1;
880#endif
881}
882
883#ifdef Q_OS_IOS
884namespace {
885
886void avf_convert_white_balance_mode(QCamera::WhiteBalanceMode qtMode,
887 AVCaptureWhiteBalanceMode &avMode)
888{
889 if (qtMode == QCamera::WhiteBalanceAuto)
890 avMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;
891 else
892 avMode = AVCaptureWhiteBalanceModeLocked;
893}
894
895bool avf_set_white_balance_mode(AVCaptureDevice *captureDevice,
896 AVCaptureWhiteBalanceMode avMode)
897{
898 Q_ASSERT(captureDevice);
899
900 const bool lock = [captureDevice lockForConfiguration:nil];
901 if (!lock) {
902 qDebug() << "Failed to lock a capture device for configuration\n";
903 return false;
904 }
905
906 captureDevice.whiteBalanceMode = avMode;
907 [captureDevice unlockForConfiguration];
908 return true;
909}
910
911bool avf_convert_temp_and_tint_to_wb_gains(AVCaptureDevice *captureDevice,
912 float temp, float tint, AVCaptureWhiteBalanceGains &wbGains)
913{
914 Q_ASSERT(captureDevice);
915
916 AVCaptureWhiteBalanceTemperatureAndTintValues wbTTValues = {
917 .temperature = temp,
918 .tint = tint
919 };
920 wbGains = [captureDevice deviceWhiteBalanceGainsForTemperatureAndTintValues:wbTTValues];
921
922 if (wbGains.redGain >= 1.0 && wbGains.redGain <= captureDevice.maxWhiteBalanceGain
923 && wbGains.greenGain >= 1.0 && wbGains.greenGain <= captureDevice.maxWhiteBalanceGain
924 && wbGains.blueGain >= 1.0 && wbGains.blueGain <= captureDevice.maxWhiteBalanceGain)
925 return true;
926
927 return false;
928}
929
930bool avf_set_white_balance_gains(AVCaptureDevice *captureDevice,
931 AVCaptureWhiteBalanceGains wbGains)
932{
933 const bool lock = [captureDevice lockForConfiguration:nil];
934 if (!lock) {
935 qDebug() << "Failed to lock a capture device for configuration\n";
936 return false;
937 }
938
939 [captureDevice setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains:wbGains
940 completionHandler:nil];
941 [captureDevice unlockForConfiguration];
942 return true;
943}
944
945}
946
947bool QAVFCameraBase::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
948{
949 if (mode == QCamera::WhiteBalanceAuto)
950 return true;
951 AVCaptureDevice *captureDevice = device();
952 if (!captureDevice)
953 return false;
954 return [captureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked];
955}
956
957void QAVFCameraBase::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
958{
959 if (!isWhiteBalanceModeSupported(mode))
960 return;
961
962 AVCaptureDevice *captureDevice = device();
963 Q_ASSERT(captureDevice);
964
965 const AVFConfigurationLock lock(captureDevice);
966 if (!lock) {
967 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
968 << "for configuration";
969 return;
970 }
971
972 AVCaptureWhiteBalanceMode avMode;
973 avf_convert_white_balance_mode(mode, avMode);
974 avf_set_white_balance_mode(captureDevice, avMode);
975
976 if (mode == QCamera::WhiteBalanceAuto || mode == QCamera::WhiteBalanceManual) {
977 whiteBalanceModeChanged(mode);
978 return;
979 }
980
981 const int colorTemp = colorTemperatureForWhiteBalance(mode);
982 AVCaptureWhiteBalanceGains wbGains;
983 if (avf_convert_temp_and_tint_to_wb_gains(captureDevice, colorTemp, 0., wbGains)
984 && avf_set_white_balance_gains(captureDevice, wbGains))
985 whiteBalanceModeChanged(mode);
986}
987
988void QAVFCameraBase::setColorTemperature(int colorTemp)
989{
990 if (colorTemp == 0) {
991 colorTemperatureChanged(colorTemp);
992 return;
993 }
994
995 AVCaptureDevice *captureDevice = device();
996 if (!captureDevice || ![captureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked])
997 return;
998
999 const AVFConfigurationLock lock(captureDevice);
1000 if (!lock) {
1001 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
1002 << "for configuration";
1003 return;
1004 }
1005
1006 AVCaptureWhiteBalanceGains wbGains;
1007 if (avf_convert_temp_and_tint_to_wb_gains(captureDevice, colorTemp, 0., wbGains)
1008 && avf_set_white_balance_gains(captureDevice, wbGains))
1009 colorTemperatureChanged(colorTemp);
1010}
1011#endif
1012
1013void QAVFCameraBase::setManualIsoSensitivity(int value)
1014{
1015#ifdef Q_OS_IOS
1016 if (value < 0) {
1017 setExposureMode(QCamera::ExposureAuto);
1018 return;
1019 }
1020
1021 AVCaptureDevice *captureDevice = device();
1022 if (!captureDevice) {
1023 isoSensitivityChanged(value);
1024 return;
1025 }
1026
1027 if (!qt_check_ISO_value(captureDevice, value)) {
1028 qCDebug(qLcCamera) << Q_FUNC_INFO << "ISO value is out of range";
1029 return;
1030 }
1031
1032 const AVFConfigurationLock lock(captureDevice);
1033 if (!lock) {
1034 qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
1035 << "for configuration";
1036 return;
1037 }
1038
1039 // Setting the ISO will also reset
1040 // exposure mode to the custom mode.
1041 [captureDevice setExposureModeCustomWithDuration:AVCaptureExposureDurationCurrent
1042 ISO:value
1043 completionHandler:nil];
1044
1045 isoSensitivityChanged(value);
1046#else
1047 Q_UNUSED(value);
1048#endif
1049}
1050
1051int QAVFCameraBase::isoSensitivity() const
1052{
1053 return manualIsoSensitivity();
1054}
1055
1056// Returns true if the application currently has camera permissions.
1057bool QAVFCameraBase::checkCameraPermission()
1058{
1059 const QCameraPermission permission;
1060 const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
1061 if (!granted)
1062 qWarning() << "Access to camera not granted";
1063
1064 return granted;
1065}
1066
1067
1068#include "moc_qavfcamerabase_p.cpp"