Revision control

Copy as Markdown

// Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qavfcamerabase_p.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qmetaobject.h>
#include <QtCore/qpermissions.h>
#include <QtCore/qset.h>
#include <QtCore/qsystemdetection.h>
#include <QtCore/private/qcore_mac_p.h>
#include <QtMultimedia/private/qavfcameradebug_p.h>
#include <QtMultimedia/private/qavfcamerautility_p.h>
#include <QtMultimedia/private/qavfhelpers_p.h>
#include <QtMultimedia/private/qcameradevice_p.h>
#include <QtMultimedia/private/qplatformmediaintegration_p.h>
QT_USE_NAMESPACE
using namespace Qt::Literals;
namespace {
// All these methods to work with exposure/ISO/SS in custom mode do not support macOS.
#ifdef Q_OS_IOS
// Misc. helpers to check values/ranges:
bool qt_check_exposure_duration(AVCaptureDevice *captureDevice, CMTime duration)
{
Q_ASSERT(captureDevice);
AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
if (!activeFormat) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to obtain capture device format";
return false;
}
return CMTimeCompare(duration, activeFormat.minExposureDuration) != -1
&& CMTimeCompare(activeFormat.maxExposureDuration, duration) != -1;
}
bool qt_check_ISO_value(AVCaptureDevice *captureDevice, int newISO)
{
Q_ASSERT(captureDevice);
AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
if (!activeFormat) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to obtain capture device format";
return false;
}
return !(newISO < activeFormat.minISO || newISO > activeFormat.maxISO);
}
bool qt_exposure_duration_equal(AVCaptureDevice *captureDevice, qreal qDuration)
{
Q_ASSERT(captureDevice);
const CMTime avDuration = CMTimeMakeWithSeconds(qDuration, captureDevice.exposureDuration.timescale);
return !CMTimeCompare(avDuration, captureDevice.exposureDuration);
}
bool qt_iso_equal(AVCaptureDevice *captureDevice, int iso)
{
Q_ASSERT(captureDevice);
return QtPrivate::fuzzyCompare(float(iso), captureDevice.ISO);
}
bool qt_exposure_bias_equal(AVCaptureDevice *captureDevice, qreal bias)
{
Q_ASSERT(captureDevice);
return QtPrivate::fuzzyCompare(bias, qreal(captureDevice.exposureTargetBias));
}
// Converters:
bool qt_convert_exposure_mode(AVCaptureDevice *captureDevice, QCamera::ExposureMode mode,
AVCaptureExposureMode &avMode)
{
// Test if mode supported and convert.
Q_ASSERT(captureDevice);
if (mode == QCamera::ExposureAuto) {
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeContinuousAutoExposure]) {
avMode = AVCaptureExposureModeContinuousAutoExposure;
return true;
}
}
if (mode == QCamera::ExposureManual) {
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom]) {
avMode = AVCaptureExposureModeCustom;
return true;
}
}
return false;
}
#endif // defined(Q_OS_IOS)
} // Unnamed namespace.
QAVFCameraBase::QAVFCameraBase(QCamera *camera)
: QPlatformCamera(camera)
{
Q_ASSERT(camera);
}
QAVFCameraBase::~QAVFCameraBase()
{
}
bool QAVFCameraBase::isActive() const
{
return m_active;
}
void QAVFCameraBase::setActive(bool active)
{
if (m_active == active)
return;
if (m_cameraDevice.isNull() && active)
return;
if (!checkCameraPermission())
return;
m_active = active;
onActiveChanged(active);
Q_EMIT activeChanged(m_active);
}
void QAVFCameraBase::setCamera(const QCameraDevice &camera)
{
if (m_cameraDevice == camera)
return;
// TODO: At the time of writing, a quirk in the QCamera code is that
// device-changes are implemented as setCamera() + setCameraFormat({}).
// This means that camera-changes always involve resetting the format
// to what is default for the new device. In the future, we should
// ideally pass down the new format as a parameter, and perform
// the two operations as a single atomic one.
//
// onCameraDeviceChanged() is allowed to shut down the camera session
// and clean up resources if configuration fails for the new device.
// This causes QTBUG-141376. Until we improve the architecture, this
// code will apply camera-device and format as one atomic operation.
QCameraFormat newFormat = findBestCameraFormat(camera);
Q_ASSERT(!newFormat.isNull());
// TODO: onCameraDeviceChanged can fail to keep the camera-session active
// if we are switching to a camera-device that is not available. In the
// future this should return a q23::expected and let us handle the error.
onCameraDeviceChanged(camera, newFormat);
m_cameraDevice = camera;
m_cameraFormat = newFormat;
updateSupportedFeatures(camera);
updateCameraConfiguration(camera);
}
bool QAVFCameraBase::setCameraFormat(const QCameraFormat &newFormat)
{
if (!newFormat.isNull() && !m_cameraDevice.videoFormats().contains(newFormat))
return false;
const QCameraFormat resolvedFormat = newFormat.isNull() ? findBestCameraFormat(m_cameraDevice) : newFormat;
// If we still couldn't find a suitable default format (such as when none are available)
// we don't accept the value.
if (resolvedFormat.isNull())
return false;
const bool applySuccess = tryApplyCameraFormat(resolvedFormat);
if (!applySuccess)
return false;
m_cameraFormat = resolvedFormat;
return true;
}
AVCaptureDevice *QAVFCameraBase::device() const
{
return tryGetAvCaptureDevice(m_cameraDevice);
}
AVCaptureDevice *QAVFCameraBase::tryGetAvCaptureDevice(const QCameraDevice &device)
{
QByteArray deviceId = device.id();
if (deviceId.isEmpty())
return nullptr;
QMacAutoReleasePool autoReleasePool;
NSString *nsString = [NSString stringWithUTF8String:deviceId.constData()];
if (nsString == nullptr)
return nullptr;
return [AVCaptureDevice deviceWithUniqueID:nsString];
}
namespace
{
// Only used for setting focus-mode when we have no device attached
// to this QCamera.
bool qt_focus_mode_supported(QCamera::FocusMode mode)
{
return mode == QCamera::FocusModeAuto;
}
}
void QAVFCameraBase::setFocusMode(QCamera::FocusMode mode)
{
if (mode == focusMode())
return;
forceSetFocusMode(mode);
}
// Does not check if new QCamera::FocusMode is same as old one.
void QAVFCameraBase::forceSetFocusMode(QCamera::FocusMode mode)
{
if (!isFocusModeSupported(mode)) {
qCDebug(qLcCamera)
<< Q_FUNC_INFO
<< u"attempted to set focus-mode '%1' on camera where it is unsupported."_s.arg(
QMetaEnum::fromType<QCamera::FocusMode>().valueToKey(mode));
return;
}
AVCaptureDevice *captureDevice = device();
if (!captureDevice) {
if (qt_focus_mode_supported(mode)) {
focusModeChanged(mode);
} else {
qCDebug(qLcCamera) << Q_FUNC_INFO
<< "focus mode not supported";
}
return;
}
if (mode == QCamera::FocusModeManual) {
// If we the new focus-mode is 'Manual', then we need to lock
// lens focus position according to focusDistance().
//
// At this point, all relevant settings should have valid values
// by handling input in setFocusDistance.
applyFocusDistanceToAVCaptureDevice(focusDistance());
} else {
// Apply the focus mode to the AVCaptureDevice.
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) <<
Q_FUNC_INFO <<
"failed to lock for configuration";
return;
}
// Note: We have to set the focus-mode even if capture-device reports
// the existing value as being the same. For example, this is the case
// when using the 'setFocusModeLockedWithLensPosition' functionality.
captureDevice.focusMode = AVCaptureFocusModeContinuousAutoFocus;
}
focusModeChanged(mode);
}
bool QAVFCameraBase::isFocusModeSupported(QCamera::FocusMode mode) const
{
AVCaptureDevice *captureDevice = device();
if (captureDevice) {
switch (mode) {
case QCamera::FocusModeAuto:
return [captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];
#ifdef Q_OS_IOS
case QCamera::FocusModeManual:
return captureDevice.lockingFocusWithCustomLensPositionSupported;
#endif // Q_OS_IOS
case QCamera::FocusModeAutoNear:
Q_FALLTHROUGH();
case QCamera::FocusModeAutoFar:
#ifdef Q_OS_IOS
return captureDevice.autoFocusRangeRestrictionSupported
&& [captureDevice isFocusModeSupported:AVCaptureFocusModeContinuousAutoFocus];
#else
break;
#endif // Q_OS_IOS
default:
break;
}
}
return mode == QCamera::FocusModeAuto;
}
void QAVFCameraBase::setCustomFocusPoint(const QPointF &point)
{
if (customFocusPoint() == point)
return;
if (!QRectF(0.f, 0.f, 1.f, 1.f).contains(point)) {
// ### release custom focus point, tell the camera to focus where it wants...
qCDebug(qLcCamera) << Q_FUNC_INFO << "invalid focus point (out of range)";
return;
}
AVCaptureDevice *captureDevice = device();
if (!captureDevice)
return;
if ([captureDevice isFocusPointOfInterestSupported]) {
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
return;
}
const CGPoint focusPOI = CGPointMake(point.x(), point.y());
[captureDevice setFocusPointOfInterest:focusPOI];
if (focusMode() != QCamera::FocusModeAuto)
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
customFocusPointChanged(point);
}
}
void QAVFCameraBase::applyFocusDistanceToAVCaptureDevice(float distance)
{
#ifdef Q_OS_IOS
AVCaptureDevice *captureDevice = device();
if (!captureDevice)
return;
// This should generally always return true assuming the check
// for supportedFeatures succeeds, but the consequence of it not being
// the case is a thrown exception that we don't handle.
// So it can be useful to keep it here just in case.
if (!captureDevice.lockingFocusWithCustomLensPositionSupported) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to apply focusDistance to AVCaptureDevice.";
return;
}
{
AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock camera for configuration";
return;
}
[captureDevice setFocusModeLockedWithLensPosition:distance completionHandler:nil];
}
#else
Q_UNUSED(distance);
#endif
}
void QAVFCameraBase::setFocusDistance(float distance)
{
if (QtPrivate::fuzzyCompare(focusDistance(), distance))
return;
if (!(supportedFeatures() & QCamera::Feature::FocusDistance)) {
qCWarning(qLcCamera) <<
Q_FUNC_INFO <<
"attmpted to set focus-distance on camera without support for FocusDistance feature";
return;
}
if (distance < 0 || distance > 1) {
qCWarning(qLcCamera) <<
Q_FUNC_INFO <<
"attempted to set camera focus-distance with out-of-bounds value";
return;
}
// If we are not currently in FocusModeManual, just accept the
// value and apply it sometime later during setFocusMode(Manual).
if (focusMode() == QCamera::FocusModeManual) {
applyFocusDistanceToAVCaptureDevice(distance);
}
focusDistanceChanged(distance);
}
void QAVFCameraBase::updateCameraConfiguration(const QCameraDevice &cameraDevice)
{
// TODO: Several of these properties should only be applied to the physical
// device if we are currently active.
AVCaptureDevice *captureDevice = tryGetAvCaptureDevice(cameraDevice);
// TODO: If the new incoming device is not connected, should reset
// existing capabilities.
if (!captureDevice) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "capture device is nil when trying to update QAVFCamera";
return;
}
// We require an active format to update several of the valid ranges. I.e max zoom factor.
AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
if (!activeFormat) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "capture device has no active format when trying to update QAVFCamera";
return;
}
// First we gather new capabilities
// Handle flash/torch capabilities.
isFlashSupported = isFlashAutoSupported = false;
isTorchSupported = isTorchAutoSupported = false;
if (captureDevice.hasFlash) {
if ([captureDevice isFlashModeSupported:AVCaptureFlashModeOn])
isFlashSupported = true;
if ([captureDevice isFlashModeSupported:AVCaptureFlashModeAuto])
isFlashAutoSupported = true;
}
if (captureDevice.hasTorch) {
if ([captureDevice isTorchModeSupported:AVCaptureTorchModeOn])
isTorchSupported = true;
if ([captureDevice isTorchModeSupported:AVCaptureTorchModeAuto])
isTorchAutoSupported = true;
}
flashReadyChanged(isFlashSupported);
#ifdef Q_OS_IOS
minimumZoomFactorChanged(captureDevice.minAvailableVideoZoomFactor);
maximumZoomFactorChanged(activeFormat.videoMaxZoomFactor);
#endif // Q_OS_IOS
// The we apply properties to the camera if they are supported.
if (minZoomFactor() < maxZoomFactor()) {
// Zoom is supported, clamp it and apply it.
//
// TODO: zoom has no public API to allow instant zooming, only smooth transitions
// but the Darwin implementation of zoomTo uses rate < 0 to allow this.
forceZoomTo(qBound(zoomFactor(), minZoomFactor(), maxZoomFactor()), -1);
}
applyFlashSettings();
// Focus mode properties
// This will also take care of applying FocusDistance if applicable.
if (isFocusModeSupported(focusMode()))
forceSetFocusMode(focusMode());
// Reset properties that are not supported on the new camera device.
if (minZoomFactor() >= maxZoomFactor())
zoomFactorChanged(defaultZoomFactor());
if (!(supportedFeatures() & QCamera::Feature::FocusDistance))
focusDistanceChanged(defaultFocusDistance());
if (!isFocusModeSupported(focusMode()))
focusModeChanged(defaultFocusMode());
// TODO: Several of the following features have inconsistent behavior
// and currently do not have any Android implementation. These features
// should be revised.
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
return;
}
if ([captureDevice isFocusPointOfInterestSupported]) {
auto point = customFocusPoint();
const CGPoint focusPOI = CGPointMake(point.x(), point.y());
[captureDevice setFocusPointOfInterest:focusPOI];
}
#ifdef Q_OS_IOS
CMTime newDuration = AVCaptureExposureDurationCurrent;
bool setCustomMode = false;
float exposureTime = manualExposureTime();
if (exposureTime > 0
&& !qt_exposure_duration_equal(captureDevice, exposureTime)) {
newDuration = CMTimeMakeWithSeconds(exposureTime, captureDevice.exposureDuration.timescale);
if (!qt_check_exposure_duration(captureDevice, newDuration)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "requested exposure duration is out of range";
return;
}
setCustomMode = true;
}
float newISO = AVCaptureISOCurrent;
int iso = manualIsoSensitivity();
if (iso > 0 && !qt_iso_equal(captureDevice, iso)) {
newISO = iso;
if (!qt_check_ISO_value(captureDevice, newISO)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "requested ISO value is out of range";
return;
}
setCustomMode = true;
}
float bias = exposureCompensation();
if (bias != 0 && !qt_exposure_bias_equal(captureDevice, bias)) {
// TODO: mixed fpns.
if (bias < captureDevice.minExposureTargetBias || bias > captureDevice.maxExposureTargetBias) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "exposure compensation value is"
<< "out of range";
return;
}
[captureDevice setExposureTargetBias:bias completionHandler:nil];
}
// Setting shutter speed (exposure duration) or ISO values
// also reset exposure mode into Custom. With this settings
// we ignore any attempts to set exposure mode.
if (setCustomMode) {
[captureDevice setExposureModeCustomWithDuration:newDuration
ISO:newISO
completionHandler:nil];
return;
}
QCamera::ExposureMode qtMode = exposureMode();
AVCaptureExposureMode avMode = AVCaptureExposureModeContinuousAutoExposure;
if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "requested exposure mode is not supported";
return;
}
captureDevice.exposureMode = avMode;
#endif // Q_OS_IOS
}
void QAVFCameraBase::updateSupportedFeatures(const QCameraDevice &cameraDevice)
{
QCamera::Features features;
AVCaptureDevice *captureDevice = tryGetAvCaptureDevice(cameraDevice);
if (captureDevice) {
if ([captureDevice isFocusPointOfInterestSupported])
features |= QCamera::Feature::CustomFocusPoint;
#ifdef Q_OS_IOS
AVCaptureDeviceFormat *activeFormat = captureDevice.activeFormat;
// IsoSensitivity
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom] &&
activeFormat.minISO < activeFormat.maxISO)
features |= QCamera::Feature::IsoSensitivity;
// ColorTemperature
if (captureDevice.lockingWhiteBalanceWithCustomDeviceGainsSupported)
features |= QCamera::Feature::ColorTemperature;
// Exposure compensation
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom] &&
captureDevice.minExposureTargetBias < captureDevice.maxExposureTargetBias)
features |= QCamera::Feature::ExposureCompensation;
// Manual exposure time
if ([captureDevice isExposureModeSupported:AVCaptureExposureModeCustom] &&
CMTimeCompare(activeFormat.minExposureDuration, activeFormat.maxExposureDuration) == -1)
features |= QCamera::Feature::ManualExposureTime;
// No point in reporting the feature as supported if we don't also
// report the corresponding focus-mode as supported.
if (isFocusModeSupported(QCamera::FocusModeManual) &&
[captureDevice isLockingFocusWithCustomLensPositionSupported])
features |= QCamera::Feature::FocusDistance;
#endif
}
supportedFeaturesChanged(features);
}
void QAVFCameraBase::zoomTo(float factor, float rate)
{
if (zoomFactor() == factor)
return;
forceZoomTo(factor, rate);
}
// Internal function that gives us the option to run zoomTo
// without skipping early return when factor == QCamera::zoomFactor()
// Useful when switching camera-device.
void QAVFCameraBase::forceZoomTo(float factor, float rate)
{
#ifdef Q_OS_IOS
AVCaptureDevice *captureDevice = device();
if (!captureDevice || !captureDevice.activeFormat)
return;
factor = qBound(captureDevice.minAvailableVideoZoomFactor, factor,
captureDevice.activeFormat.videoMaxZoomFactor);
{
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
return;
}
if (rate <= 0)
captureDevice.videoZoomFactor = factor;
else
[captureDevice rampToVideoZoomFactor:factor withRate:rate];
}
zoomFactorChanged(factor);
#else
Q_UNUSED(rate);
Q_UNUSED(factor);
#endif
}
void QAVFCameraBase::setFlashMode(QCamera::FlashMode mode)
{
if (flashMode() == mode)
return;
if (!isFlashModeSupported(mode)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported mode" << mode;
return;
}
flashModeChanged(mode);
if (!isActive())
return;
applyFlashSettings();
}
bool QAVFCameraBase::isFlashModeSupported(QCamera::FlashMode mode) const
{
if (mode == QCamera::FlashOff)
return true;
else if (mode == QCamera::FlashOn)
return isFlashSupported;
else //if (mode == QCamera::FlashAuto)
return isFlashAutoSupported;
}
bool QAVFCameraBase::isFlashReady() const
{
if (!isActive())
return false;
AVCaptureDevice *captureDevice = device();
if (!captureDevice)
return false;
if (!captureDevice.hasFlash)
return false;
if (!isFlashModeSupported(flashMode()))
return false;
// AVCaptureDevice's docs:
// "The flash may become unavailable if, for example,
// the device overheats and needs to cool off."
return [captureDevice isFlashAvailable];
}
void QAVFCameraBase::setTorchMode(QCamera::TorchMode mode)
{
if (torchMode() == mode)
return;
if (isActive() && !isTorchModeSupported(mode)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "unsupported torch mode" << mode;
return;
}
torchModeChanged(mode);
if (!isActive())
return;
applyFlashSettings();
}
bool QAVFCameraBase::isTorchModeSupported(QCamera::TorchMode mode) const
{
if (mode == QCamera::TorchOff)
return true;
else if (mode == QCamera::TorchOn)
return isTorchSupported;
else //if (mode == QCamera::TorchAuto)
return isTorchAutoSupported;
}
void QAVFCameraBase::setExposureMode(QCamera::ExposureMode qtMode)
{
#ifdef Q_OS_IOS
if (qtMode != QCamera::ExposureAuto && qtMode != QCamera::ExposureManual) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "exposure mode not supported";
return;
}
AVCaptureDevice *captureDevice = device();
if (!captureDevice) {
exposureModeChanged(qtMode);
return;
}
AVCaptureExposureMode avMode = AVCaptureExposureModeContinuousAutoExposure;
if (!qt_convert_exposure_mode(captureDevice, qtMode, avMode)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "exposure mode not supported";
return;
}
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
<< "for configuration";
return;
}
[captureDevice setExposureMode:avMode];
exposureModeChanged(qtMode);
#else
Q_UNUSED(qtMode);
#endif
}
bool QAVFCameraBase::isExposureModeSupported(QCamera::ExposureMode mode) const
{
if (mode == QCamera::ExposureAuto)
return true;
if (mode != QCamera::ExposureManual)
return false;
AVCaptureDevice *captureDevice = device();
return captureDevice && [captureDevice isExposureModeSupported:AVCaptureExposureModeCustom];
}
void QAVFCameraBase::applyFlashSettings()
{
AVCaptureDevice *captureDevice = device();
if (!captureDevice) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "no capture device found";
return;
}
const AVFConfigurationLock lock(captureDevice);
if (captureDevice.hasFlash) {
const auto mode = flashMode();
auto setAvFlashModeSafe = [&captureDevice](AVCaptureFlashMode avFlashMode) {
// Note, in some cases captureDevice.hasFlash == false even though
// no there're no supported flash modes.
if ([captureDevice isFlashModeSupported:avFlashMode])
captureDevice.flashMode = avFlashMode;
else
qCDebug(qLcCamera) << "Attempt to setup unsupported flash mode " << avFlashMode;
};
if (mode == QCamera::FlashOff) {
setAvFlashModeSafe(AVCaptureFlashModeOff);
} else {
if ([captureDevice isFlashAvailable]) {
if (mode == QCamera::FlashOn)
setAvFlashModeSafe(AVCaptureFlashModeOn);
else if (mode == QCamera::FlashAuto)
setAvFlashModeSafe(AVCaptureFlashModeAuto);
} else {
qCDebug(qLcCamera) << Q_FUNC_INFO << "flash is not available at the moment";
}
}
}
if (captureDevice.hasTorch) {
const auto mode = torchMode();
auto setAvTorchModeSafe = [&captureDevice](AVCaptureTorchMode avTorchMode) {
if ([captureDevice isTorchModeSupported:avTorchMode])
captureDevice.torchMode = avTorchMode;
else
qCDebug(qLcCamera) << "Attempt to setup unsupported torch mode " << avTorchMode;
};
if (mode == QCamera::TorchOff) {
setAvTorchModeSafe(AVCaptureTorchModeOff);
} else {
if ([captureDevice isTorchAvailable]) {
if (mode == QCamera::TorchOn)
setAvTorchModeSafe(AVCaptureTorchModeOn);
else if (mode == QCamera::TorchAuto)
setAvTorchModeSafe(AVCaptureTorchModeAuto);
} else {
qCDebug(qLcCamera) << Q_FUNC_INFO << "torch is not available at the moment";
}
}
}
}
void QAVFCameraBase::setExposureCompensation(float bias)
{
#ifdef Q_OS_IOS
AVCaptureDevice *captureDevice = device();
if (!captureDevice) {
exposureCompensationChanged(bias);
return;
}
bias = qBound(captureDevice.minExposureTargetBias, bias, captureDevice.maxExposureTargetBias);
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
return;
}
[captureDevice setExposureTargetBias:bias completionHandler:nil];
exposureCompensationChanged(bias);
#else
Q_UNUSED(bias);
#endif
}
void QAVFCameraBase::setManualExposureTime(float value)
{
#ifdef Q_OS_IOS
if (value < 0) {
setExposureMode(QCamera::ExposureAuto);
return;
}
AVCaptureDevice *captureDevice = device();
if (!captureDevice) {
exposureTimeChanged(value);
return;
}
const CMTime newDuration = CMTimeMakeWithSeconds(value, captureDevice.exposureDuration.timescale);
if (!qt_check_exposure_duration(captureDevice, newDuration)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "shutter speed value is out of range";
return;
}
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock for configuration";
return;
}
// Setting the shutter speed (exposure duration in Apple's terms,
// since there is no shutter actually) will also reset
// exposure mode into custom mode.
[captureDevice setExposureModeCustomWithDuration:newDuration
ISO:AVCaptureISOCurrent
completionHandler:nil];
exposureTimeChanged(value);
#else
Q_UNUSED(value);
#endif
}
float QAVFCameraBase::exposureTime() const
{
#ifdef Q_OS_IOS
AVCaptureDevice *captureDevice = device();
if (!captureDevice)
return -1.;
auto duration = captureDevice.exposureDuration;
return CMTimeGetSeconds(duration);
#else
return -1;
#endif
}
#ifdef Q_OS_IOS
namespace {
void avf_convert_white_balance_mode(QCamera::WhiteBalanceMode qtMode,
AVCaptureWhiteBalanceMode &avMode)
{
if (qtMode == QCamera::WhiteBalanceAuto)
avMode = AVCaptureWhiteBalanceModeContinuousAutoWhiteBalance;
else
avMode = AVCaptureWhiteBalanceModeLocked;
}
bool avf_set_white_balance_mode(AVCaptureDevice *captureDevice,
AVCaptureWhiteBalanceMode avMode)
{
Q_ASSERT(captureDevice);
const bool lock = [captureDevice lockForConfiguration:nil];
if (!lock) {
qDebug() << "Failed to lock a capture device for configuration\n";
return false;
}
captureDevice.whiteBalanceMode = avMode;
[captureDevice unlockForConfiguration];
return true;
}
bool avf_convert_temp_and_tint_to_wb_gains(AVCaptureDevice *captureDevice,
float temp, float tint, AVCaptureWhiteBalanceGains &wbGains)
{
Q_ASSERT(captureDevice);
AVCaptureWhiteBalanceTemperatureAndTintValues wbTTValues = {
.temperature = temp,
.tint = tint
};
wbGains = [captureDevice deviceWhiteBalanceGainsForTemperatureAndTintValues:wbTTValues];
if (wbGains.redGain >= 1.0 && wbGains.redGain <= captureDevice.maxWhiteBalanceGain
&& wbGains.greenGain >= 1.0 && wbGains.greenGain <= captureDevice.maxWhiteBalanceGain
&& wbGains.blueGain >= 1.0 && wbGains.blueGain <= captureDevice.maxWhiteBalanceGain)
return true;
return false;
}
bool avf_set_white_balance_gains(AVCaptureDevice *captureDevice,
AVCaptureWhiteBalanceGains wbGains)
{
const bool lock = [captureDevice lockForConfiguration:nil];
if (!lock) {
qDebug() << "Failed to lock a capture device for configuration\n";
return false;
}
[captureDevice setWhiteBalanceModeLockedWithDeviceWhiteBalanceGains:wbGains
completionHandler:nil];
[captureDevice unlockForConfiguration];
return true;
}
}
bool QAVFCameraBase::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
{
if (mode == QCamera::WhiteBalanceAuto)
return true;
AVCaptureDevice *captureDevice = device();
if (!captureDevice)
return false;
return [captureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked];
}
void QAVFCameraBase::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
{
if (!isWhiteBalanceModeSupported(mode))
return;
AVCaptureDevice *captureDevice = device();
Q_ASSERT(captureDevice);
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
<< "for configuration";
return;
}
AVCaptureWhiteBalanceMode avMode;
avf_convert_white_balance_mode(mode, avMode);
avf_set_white_balance_mode(captureDevice, avMode);
if (mode == QCamera::WhiteBalanceAuto || mode == QCamera::WhiteBalanceManual) {
whiteBalanceModeChanged(mode);
return;
}
const int colorTemp = colorTemperatureForWhiteBalance(mode);
AVCaptureWhiteBalanceGains wbGains;
if (avf_convert_temp_and_tint_to_wb_gains(captureDevice, colorTemp, 0., wbGains)
&& avf_set_white_balance_gains(captureDevice, wbGains))
whiteBalanceModeChanged(mode);
}
void QAVFCameraBase::setColorTemperature(int colorTemp)
{
if (colorTemp == 0) {
colorTemperatureChanged(colorTemp);
return;
}
AVCaptureDevice *captureDevice = device();
if (!captureDevice || ![captureDevice isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeLocked])
return;
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
<< "for configuration";
return;
}
AVCaptureWhiteBalanceGains wbGains;
if (avf_convert_temp_and_tint_to_wb_gains(captureDevice, colorTemp, 0., wbGains)
&& avf_set_white_balance_gains(captureDevice, wbGains))
colorTemperatureChanged(colorTemp);
}
#endif
void QAVFCameraBase::setManualIsoSensitivity(int value)
{
#ifdef Q_OS_IOS
if (value < 0) {
setExposureMode(QCamera::ExposureAuto);
return;
}
AVCaptureDevice *captureDevice = device();
if (!captureDevice) {
isoSensitivityChanged(value);
return;
}
if (!qt_check_ISO_value(captureDevice, value)) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "ISO value is out of range";
return;
}
const AVFConfigurationLock lock(captureDevice);
if (!lock) {
qCDebug(qLcCamera) << Q_FUNC_INFO << "failed to lock a capture device"
<< "for configuration";
return;
}
// Setting the ISO will also reset
// exposure mode to the custom mode.
[captureDevice setExposureModeCustomWithDuration:AVCaptureExposureDurationCurrent
ISO:value
completionHandler:nil];
isoSensitivityChanged(value);
#else
Q_UNUSED(value);
#endif
}
int QAVFCameraBase::isoSensitivity() const
{
return manualIsoSensitivity();
}
// Returns true if the application currently has camera permissions.
bool QAVFCameraBase::checkCameraPermission()
{
const QCameraPermission permission;
const bool granted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
if (!granted)
qWarning() << "Access to camera not granted";
return granted;
}
#include "moc_qavfcamerabase_p.cpp"