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