Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qandroidcamera_p.h"
++ #include "qandroidcamerasession_p.h"
++ #include "qandroidcapturesession_p.h"
++ #include "qandroidmediacapturesession_p.h"
++ #include <qmediadevices.h>
++ #include <qcameradevice.h>
++ #include <qtimer.h>
++ #include "qandroidmultimediautils_p.h"
++
++ QT_BEGIN_NAMESPACE
++
++ QAndroidCamera::QAndroidCamera(QCamera *camera)
++ : QPlatformCamera(camera)
++ {
++ Q_ASSERT(camera);
++ }
++
++ QAndroidCamera::~QAndroidCamera()
++ {
++ }
++
++ void QAndroidCamera::setActive(bool active)
++ {
++ if (m_cameraSession) {
++ m_cameraSession->setActive(active);
++ } else {
++ isPendingSetActive = active;
++ }
++ }
++
++ bool QAndroidCamera::isActive() const
++ {
++ return m_cameraSession ? m_cameraSession->isActive() : false;
++ }
++
++ void QAndroidCamera::setCamera(const QCameraDevice &camera)
++ {
++ m_cameraDev = camera;
++
++ if (m_cameraSession) {
++ int id = 0;
++ auto cameras = QMediaDevices::videoInputs();
++ for (int i = 0; i < cameras.size(); ++i) {
++ if (cameras.at(i) == camera) {
++ id = i;
++ break;
++ }
++ }
++ if (id != m_cameraSession->getSelectedCameraId()) {
++ m_cameraSession->setSelectedCameraId(id);
++ reactivateCameraSession();
++ }
++ }
++ }
++
++ void QAndroidCamera::reactivateCameraSession()
++ {
++ if (m_cameraSession->isActive()) {
++ if (m_service->captureSession() &&
++ m_service->captureSession()->state() == QMediaRecorder::RecordingState) {
++ m_service->captureSession()->stop();
++ qWarning() << "Changing camera during recording not supported";
++ }
++ m_cameraSession->setActive(false);
++ m_cameraSession->setActive(true);
++ }
++ }
++
++ bool QAndroidCamera::setCameraFormat(const QCameraFormat &format)
++ {
++ m_cameraFormat = format;
++
++ if (m_cameraSession)
++ m_cameraSession->setCameraFormat(m_cameraFormat);
++
++ return true;
++ }
++
++ void QAndroidCamera::setCaptureSession(QPlatformMediaCaptureSession *session)
++ {
++ QAndroidMediaCaptureSession *captureSession = static_cast<QAndroidMediaCaptureSession *>(session);
++ if (m_service == captureSession)
++ return;
++
++ m_service = captureSession;
++ if (!m_service) {
++ disconnect(m_cameraSession,nullptr,this,nullptr);
++ m_cameraSession = nullptr;
++ return;
++ }
++
++ m_cameraSession = m_service->cameraSession();
++ Q_ASSERT(m_cameraSession);
++ if (!m_cameraFormat.isNull())
++ m_cameraSession->setCameraFormat(m_cameraFormat);
++
++ setCamera(m_cameraDev);
++
++ connect(m_cameraSession, &QAndroidCameraSession::activeChanged, this, &QAndroidCamera::activeChanged);
++ connect(m_cameraSession, &QAndroidCameraSession::error, this, &QAndroidCamera::error);
++ connect(m_cameraSession, &QAndroidCameraSession::opened, this, &QAndroidCamera::onCameraOpened);
++
++ if (isPendingSetActive) {
++ setActive(true);
++ isPendingSetActive = false;
++ }
++ }
++
++ void QAndroidCamera::setFocusMode(QCamera::FocusMode mode)
++ {
++ if (!m_cameraSession || !m_cameraSession->camera())
++ return;
++
++ if (isFocusModeSupported(mode)) {
++ QString focusMode;
++
++ switch (mode) {
++ case QCamera::FocusModeHyperfocal:
++ focusMode = QLatin1String("edof");
++ break;
++ case QCamera::FocusModeInfinity: // not 100%, but close
++ focusMode = QLatin1String("infinity");
++ break;
++ case QCamera::FocusModeManual:
++ focusMode = QLatin1String("fixed");
++ break;
++ case QCamera::FocusModeAutoNear:
++ focusMode = QLatin1String("macro");
++ break;
++ case QCamera::FocusModeAuto:
++ case QCamera::FocusModeAutoFar:
++ if (1) { // ###?
++ focusMode = QLatin1String("continuous-video");
++ } else {
++ focusMode = QLatin1String("continuous-picture");
++ }
++ break;
++ }
++
++ m_cameraSession->camera()->setFocusMode(focusMode);
++
++ // reset focus position
++ m_cameraSession->camera()->cancelAutoFocus();
++
++ focusModeChanged(mode);
++ }
++ }
++
++ bool QAndroidCamera::isFocusModeSupported(QCamera::FocusMode mode) const
++ {
++ return (m_cameraSession && m_cameraSession->camera()) ? m_supportedFocusModes.contains(mode) : false;
++ }
++
++ void QAndroidCamera::onCameraOpened()
++ {
++ Q_ASSERT(m_cameraSession);
++ connect(m_cameraSession->camera(), &AndroidCamera::previewSizeChanged, this, &QAndroidCamera::setCameraFocusArea);
++
++ m_supportedFocusModes.clear();
++ m_continuousPictureFocusSupported = false;
++ m_continuousVideoFocusSupported = false;
++ m_focusPointSupported = false;
++
++ QStringList focusModes = m_cameraSession->camera()->getSupportedFocusModes();
++ for (int i = 0; i < focusModes.size(); ++i) {
++ const QString &focusMode = focusModes.at(i);
++ if (focusMode == QLatin1String("continuous-picture")) {
++ m_supportedFocusModes << QCamera::FocusModeAuto;
++ m_continuousPictureFocusSupported = true;
++ } else if (focusMode == QLatin1String("continuous-video")) {
++ m_supportedFocusModes << QCamera::FocusModeAuto;
++ m_continuousVideoFocusSupported = true;
++ } else if (focusMode == QLatin1String("edof")) {
++ m_supportedFocusModes << QCamera::FocusModeHyperfocal;
++ } else if (focusMode == QLatin1String("fixed")) {
++ m_supportedFocusModes << QCamera::FocusModeManual;
++ } else if (focusMode == QLatin1String("infinity")) {
++ m_supportedFocusModes << QCamera::FocusModeInfinity;
++ } else if (focusMode == QLatin1String("macro")) {
++ m_supportedFocusModes << QCamera::FocusModeAutoNear;
++ }
++ }
++
++ if (m_cameraSession->camera()->getMaxNumFocusAreas() > 0)
++ m_focusPointSupported = true;
++
++ auto m = focusMode();
++ if (!m_supportedFocusModes.contains(m))
++ m = QCamera::FocusModeAuto;
++
++ setFocusMode(m);
++ setCustomFocusPoint(focusPoint());
++
++ if (m_cameraSession->camera()->isZoomSupported()) {
++ m_zoomRatios = m_cameraSession->camera()->getZoomRatios();
++ qreal maxZoom = m_zoomRatios.last() / qreal(100);
++ maximumZoomFactorChanged(maxZoom);
++ zoomTo(1, -1);
++ } else {
++ m_zoomRatios.clear();
++ maximumZoomFactorChanged(1.0);
++ }
++
++ m_minExposureCompensationIndex = m_cameraSession->camera()->getMinExposureCompensation();
++ m_maxExposureCompensationIndex = m_cameraSession->camera()->getMaxExposureCompensation();
++ m_exposureCompensationStep = m_cameraSession->camera()->getExposureCompensationStep();
++ exposureCompensationRangeChanged(m_minExposureCompensationIndex*m_exposureCompensationStep,
++ m_maxExposureCompensationIndex*m_exposureCompensationStep);
++
++ m_supportedExposureModes.clear();
++ QStringList sceneModes = m_cameraSession->camera()->getSupportedSceneModes();
++ if (!sceneModes.isEmpty()) {
++ for (int i = 0; i < sceneModes.size(); ++i) {
++ const QString &sceneMode = sceneModes.at(i);
++ if (sceneMode == QLatin1String("auto"))
++ m_supportedExposureModes << QCamera::ExposureAuto;
++ else if (sceneMode == QLatin1String("beach"))
++ m_supportedExposureModes << QCamera::ExposureBeach;
++ else if (sceneMode == QLatin1String("night"))
++ m_supportedExposureModes << QCamera::ExposureNight;
++ else if (sceneMode == QLatin1String("portrait"))
++ m_supportedExposureModes << QCamera::ExposurePortrait;
++ else if (sceneMode == QLatin1String("snow"))
++ m_supportedExposureModes << QCamera::ExposureSnow;
++ else if (sceneMode == QLatin1String("sports"))
++ m_supportedExposureModes << QCamera::ExposureSports;
++ else if (sceneMode == QLatin1String("action"))
++ m_supportedExposureModes << QCamera::ExposureAction;
++ else if (sceneMode == QLatin1String("landscape"))
++ m_supportedExposureModes << QCamera::ExposureLandscape;
++ else if (sceneMode == QLatin1String("night-portrait"))
++ m_supportedExposureModes << QCamera::ExposureNightPortrait;
++ else if (sceneMode == QLatin1String("theatre"))
++ m_supportedExposureModes << QCamera::ExposureTheatre;
++ else if (sceneMode == QLatin1String("sunset"))
++ m_supportedExposureModes << QCamera::ExposureSunset;
++ else if (sceneMode == QLatin1String("steadyphoto"))
++ m_supportedExposureModes << QCamera::ExposureSteadyPhoto;
++ else if (sceneMode == QLatin1String("fireworks"))
++ m_supportedExposureModes << QCamera::ExposureFireworks;
++ else if (sceneMode == QLatin1String("party"))
++ m_supportedExposureModes << QCamera::ExposureParty;
++ else if (sceneMode == QLatin1String("candlelight"))
++ m_supportedExposureModes << QCamera::ExposureCandlelight;
++ else if (sceneMode == QLatin1String("barcode"))
++ m_supportedExposureModes << QCamera::ExposureBarcode;
++ }
++ }
++
++ setExposureCompensation(exposureCompensation());
++ setExposureMode(exposureMode());
++
++ isFlashSupported = false;
++ isFlashAutoSupported = false;
++ isTorchSupported = false;
++
++ QStringList flashModes = m_cameraSession->camera()->getSupportedFlashModes();
++ for (int i = 0; i < flashModes.size(); ++i) {
++ const QString &flashMode = flashModes.at(i);
++ if (flashMode == QLatin1String("auto"))
++ isFlashAutoSupported = true;
++ else if (flashMode == QLatin1String("on"))
++ isFlashSupported = true;
++ else if (flashMode == QLatin1String("torch"))
++ isTorchSupported = true;
++ }
++
++ setFlashMode(flashMode());
++
++ m_supportedWhiteBalanceModes.clear();
++ QStringList whiteBalanceModes = m_cameraSession->camera()->getSupportedWhiteBalance();
++ for (int i = 0; i < whiteBalanceModes.size(); ++i) {
++ const QString &wb = whiteBalanceModes.at(i);
++ if (wb == QLatin1String("auto")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceAuto,
++ QStringLiteral("auto"));
++ } else if (wb == QLatin1String("cloudy-daylight")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceCloudy,
++ QStringLiteral("cloudy-daylight"));
++ } else if (wb == QLatin1String("daylight")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceSunlight,
++ QStringLiteral("daylight"));
++ } else if (wb == QLatin1String("fluorescent")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceFluorescent,
++ QStringLiteral("fluorescent"));
++ } else if (wb == QLatin1String("incandescent")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceTungsten,
++ QStringLiteral("incandescent"));
++ } else if (wb == QLatin1String("shade")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceShade,
++ QStringLiteral("shade"));
++ } else if (wb == QLatin1String("twilight")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceSunset,
++ QStringLiteral("twilight"));
++ } else if (wb == QLatin1String("warm-fluorescent")) {
++ m_supportedWhiteBalanceModes.insert(QCamera::WhiteBalanceFlash,
++ QStringLiteral("warm-fluorescent"));
++ }
++ }
++
++ }
++
++ //void QAndroidCameraFocusControl::onCameraCaptureModeChanged()
++ //{
++ // if (m_cameraSession->camera() && m_focusMode == QCamera::FocusModeAudio) {
++ // QString focusMode;
++ // if ((m_cameraSession->captureMode().testFlag(QCamera::CaptureVideo) && m_continuousVideoFocusSupported)
++ // || !m_continuousPictureFocusSupported) {
++ // focusMode = QLatin1String("continuous-video");
++ // } else {
++ // focusMode = QLatin1String("continuous-picture");
++ // }
++ // m_cameraSession->camera()->setFocusMode(focusMode);
++ // m_cameraSession->camera()->cancelAutoFocus();
++ // }
++ //}
++
++ static QRect adjustedArea(const QRectF &area)
++ {
++ // Qt maps focus points in the range (0.0, 0.0) -> (1.0, 1.0)
++ // Android maps focus points in the range (-1000, -1000) -> (1000, 1000)
++ // Converts an area in Qt coordinates to Android coordinates
++ return QRect(-1000 + qRound(area.x() * 2000),
++ -1000 + qRound(area.y() * 2000),
++ qRound(area.width() * 2000),
++ qRound(area.height() * 2000))
++ .intersected(QRect(-1000, -1000, 2000, 2000));
++ }
++
++ void QAndroidCamera::setCameraFocusArea()
++ {
++ if (!m_cameraSession)
++ return;
++
++ QList<QRect> areas;
++ auto focusPoint = customFocusPoint();
++ if (QRectF(0., 0., 1., 1.).contains(focusPoint)) {
++ // in FocusPointAuto mode, leave the area list empty
++ // to let the driver choose the focus point.
++ QSize viewportSize = m_cameraSession->camera()->previewSize();
++
++ if (!viewportSize.isValid())
++ return;
++
++ // Set up a 50x50 pixel focus area around the focal point
++ QSizeF focusSize(50.f / viewportSize.width(), 50.f / viewportSize.height());
++ float x = qBound(qreal(0),
++ focusPoint.x() - (focusSize.width() / 2),
++ 1.f - focusSize.width());
++ float y = qBound(qreal(0),
++ focusPoint.y() - (focusSize.height() / 2),
++ 1.f - focusSize.height());
++
++ QRectF area(QPointF(x, y), focusSize);
++
++ areas.append(adjustedArea(area));
++ }
++ m_cameraSession->camera()->setFocusAreas(areas);
++ }
++
++ void QAndroidCamera::zoomTo(float factor, float rate)
++ {
++ Q_UNUSED(rate);
++
++ if (zoomFactor() == factor)
++ return;
++
++ if (!m_cameraSession || !m_cameraSession->camera())
++ return;
++
++ factor = qBound(qreal(1), factor, maxZoomFactor());
++ int validZoomIndex = qt_findClosestValue(m_zoomRatios, qRound(factor * 100));
++ float newZoom = m_zoomRatios.at(validZoomIndex) / qreal(100);
++ m_cameraSession->camera()->setZoom(validZoomIndex);
++ zoomFactorChanged(newZoom);
++ }
++
++ void QAndroidCamera::setFlashMode(QCamera::FlashMode mode)
++ {
++ if (!m_cameraSession || !m_cameraSession->camera())
++ return;
++
++ if (!isFlashModeSupported(mode))
++ return;
++
++ QString flashMode;
++ if (mode == QCamera::FlashAuto)
++ flashMode = QLatin1String("auto");
++ else if (mode == QCamera::FlashOn)
++ flashMode = QLatin1String("on");
++ else // FlashOff
++ flashMode = QLatin1String("off");
++
++ m_cameraSession->camera()->setFlashMode(flashMode);
++ flashModeChanged(mode);
++ }
++
++ bool QAndroidCamera::isFlashModeSupported(QCamera::FlashMode mode) const
++ {
++ if (!m_cameraSession || !m_cameraSession->camera())
++ return false;
++ switch (mode) {
++ case QCamera::FlashOff:
++ return true;
++ case QCamera::FlashOn:
++ return isFlashSupported;
++ case QCamera::FlashAuto:
++ return isFlashAutoSupported;
++ }
++ }
++
++ bool QAndroidCamera::isFlashReady() const
++ {
++ // Android doesn't have an API for that
++ return true;
++ }
++
++ void QAndroidCamera::setTorchMode(QCamera::TorchMode mode)
++ {
++ if (!m_cameraSession)
++ return;
++ auto *camera = m_cameraSession->camera();
++ if (!camera || !isTorchSupported || mode == QCamera::TorchAuto)
++ return;
++
++ if (mode == QCamera::TorchOn) {
++ camera->setFlashMode(QLatin1String("torch"));
++ } else if (mode == QCamera::TorchOff) {
++ // if torch was enabled, it first needs to be turned off before restoring the flash mode
++ camera->setFlashMode(QLatin1String("off"));
++ setFlashMode(flashMode());
++ }
++ torchModeChanged(mode);
++ }
++
++ bool QAndroidCamera::isTorchModeSupported(QCamera::TorchMode mode) const
++ {
++ if (!m_cameraSession || !m_cameraSession->camera())
++ return false;
++ switch (mode) {
++ case QCamera::TorchOff:
++ return true;
++ case QCamera::TorchOn:
++ return isTorchSupported;
++ case QCamera::TorchAuto:
++ return false;
++ }
++ }
++
++ void QAndroidCamera::setExposureMode(QCamera::ExposureMode mode)
++ {
++ if (exposureMode() == mode)
++ return;
++
++ if (!m_cameraSession || !m_cameraSession->camera())
++ return;
++
++ if (!m_supportedExposureModes.contains(mode))
++ return;
++
++ QString sceneMode;
++ switch (mode) {
++ case QCamera::ExposureAuto:
++ sceneMode = QLatin1String("auto");
++ break;
++ case QCamera::ExposureSports:
++ sceneMode = QLatin1String("sports");
++ break;
++ case QCamera::ExposurePortrait:
++ sceneMode = QLatin1String("portrait");
++ break;
++ case QCamera::ExposureBeach:
++ sceneMode = QLatin1String("beach");
++ break;
++ case QCamera::ExposureSnow:
++ sceneMode = QLatin1String("snow");
++ break;
++ case QCamera::ExposureNight:
++ sceneMode = QLatin1String("night");
++ break;
++ case QCamera::ExposureAction:
++ sceneMode = QLatin1String("action");
++ break;
++ case QCamera::ExposureLandscape:
++ sceneMode = QLatin1String("landscape");
++ break;
++ case QCamera::ExposureNightPortrait:
++ sceneMode = QLatin1String("night-portrait");
++ break;
++ case QCamera::ExposureTheatre:
++ sceneMode = QLatin1String("theatre");
++ break;
++ case QCamera::ExposureSunset:
++ sceneMode = QLatin1String("sunset");
++ break;
++ case QCamera::ExposureSteadyPhoto:
++ sceneMode = QLatin1String("steadyphoto");
++ break;
++ case QCamera::ExposureFireworks:
++ sceneMode = QLatin1String("fireworks");
++ break;
++ case QCamera::ExposureParty:
++ sceneMode = QLatin1String("party");
++ break;
++ case QCamera::ExposureCandlelight:
++ sceneMode = QLatin1String("candlelight");
++ break;
++ case QCamera::ExposureBarcode:
++ sceneMode = QLatin1String("barcode");
++ break;
++ default:
++ sceneMode = QLatin1String("auto");
++ mode = QCamera::ExposureAuto;
++ break;
++ }
++
++ m_cameraSession->camera()->setSceneMode(sceneMode);
++ exposureModeChanged(mode);
++ }
++
++ bool QAndroidCamera::isExposureModeSupported(QCamera::ExposureMode mode) const
++ {
++ return m_supportedExposureModes.contains(mode);
++ }
++
++ void QAndroidCamera::setExposureCompensation(float bias)
++ {
++ if (exposureCompensation() == bias || !m_cameraSession || !m_cameraSession->camera())
++ return;
++
++ int biasIndex = qRound(bias / m_exposureCompensationStep);
++ biasIndex = qBound(m_minExposureCompensationIndex, biasIndex, m_maxExposureCompensationIndex);
++ float comp = biasIndex * m_exposureCompensationStep;
++ m_cameraSession->camera()->setExposureCompensation(biasIndex);
++ exposureCompensationChanged(comp);
++ }
++
++ bool QAndroidCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const
++ {
++ return m_supportedWhiteBalanceModes.contains(mode);
++ }
++
++ void QAndroidCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode)
++ {
++ if (!m_cameraSession)
++ return;
++ auto *camera = m_cameraSession->camera();
++ if (!camera)
++ return;
++ QString wb = m_supportedWhiteBalanceModes.value(mode, QString());
++ if (!wb.isEmpty()) {
++ camera->setWhiteBalance(wb);
++ whiteBalanceModeChanged(mode);
++ }
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qandroidcamera_p.cpp"