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