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