summaryrefslogtreecommitdiffstats
path: root/src/plugins/multimedia/android/mediacapture
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/multimedia/android/mediacapture')
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcamera.cpp562
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcamera_p.h99
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcamerasession.cpp808
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcamerasession_p.h166
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp473
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h158
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidimagecapture.cpp73
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidimagecapture_p.h48
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession.cpp115
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession_p.h66
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidmediaencoder.cpp72
-rw-r--r--src/plugins/multimedia/android/mediacapture/qandroidmediaencoder_p.h50
12 files changed, 2690 insertions, 0 deletions
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcamera.cpp b/src/plugins/multimedia/android/mediacapture/qandroidcamera.cpp
new file mode 100644
index 000000000..52d2e00f6
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcamera.cpp
@@ -0,0 +1,562 @@
+// 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"
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcamera_p.h b/src/plugins/multimedia/android/mediacapture/qandroidcamera_p.h
new file mode 100644
index 000000000..77bbc3133
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcamera_p.h
@@ -0,0 +1,99 @@
+// 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
+
+
+#ifndef QANDROIDCAMERACONTROL_H
+#define QANDROIDCAMERACONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformcamera_p.h>
+
+#include <qmap.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidCameraSession;
+class QAndroidCameraVideoRendererControl;
+class QAndroidMediaCaptureSession;
+
+class QAndroidCamera : public QPlatformCamera
+{
+ Q_OBJECT
+public:
+ explicit QAndroidCamera(QCamera *camera);
+ virtual ~QAndroidCamera();
+
+ bool isActive() const override;
+ void setActive(bool active) override;
+
+ void setCamera(const QCameraDevice &camera) override;
+ bool setCameraFormat(const QCameraFormat &format) override;
+
+ void setCaptureSession(QPlatformMediaCaptureSession *session) override;
+
+ void setFocusMode(QCamera::FocusMode mode) override;
+ bool isFocusModeSupported(QCamera::FocusMode mode) const override;
+
+ void zoomTo(float factor, float rate) override;
+
+ void setFlashMode(QCamera::FlashMode mode) override;
+ bool isFlashModeSupported(QCamera::FlashMode mode) const override;
+ bool isFlashReady() const override;
+
+ void setTorchMode(QCamera::TorchMode mode) override;
+ bool isTorchModeSupported(QCamera::TorchMode mode) const override;
+
+ void setExposureMode(QCamera::ExposureMode mode) override;
+ bool isExposureModeSupported(QCamera::ExposureMode mode) const override;
+
+ void setExposureCompensation(float bias) override;
+
+ bool isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const override;
+ void setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) override;
+
+private Q_SLOTS:
+ void onCameraOpened();
+ void setCameraFocusArea();
+
+private:
+ void reactivateCameraSession();
+
+ QAndroidCameraSession *m_cameraSession = nullptr;
+ QAndroidMediaCaptureSession *m_service = nullptr;
+
+ QList<QCamera::FocusMode> m_supportedFocusModes;
+ bool m_continuousPictureFocusSupported = false;
+ bool m_continuousVideoFocusSupported = false;
+ bool m_focusPointSupported = false;
+
+ QList<int> m_zoomRatios;
+
+ QList<QCamera::ExposureMode> m_supportedExposureModes;
+ int m_minExposureCompensationIndex;
+ int m_maxExposureCompensationIndex;
+ qreal m_exposureCompensationStep;
+
+ bool isFlashSupported = false;
+ bool isFlashAutoSupported = false;
+ bool isTorchSupported = false;
+ bool isPendingSetActive = false;
+ QCameraDevice m_cameraDev;
+
+ QMap<QCamera::WhiteBalanceMode, QString> m_supportedWhiteBalanceModes;
+ QCameraFormat m_cameraFormat;
+};
+
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDCAMERACONTROL_H
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcamerasession.cpp b/src/plugins/multimedia/android/mediacapture/qandroidcamerasession.cpp
new file mode 100644
index 000000000..7eda1175f
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcamerasession.cpp
@@ -0,0 +1,808 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2016 Ruslan Baratov
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qandroidcamerasession_p.h"
+
+#include "androidcamera_p.h"
+#include "androidmultimediautils_p.h"
+#include "qandroidvideooutput_p.h"
+#include "qandroidmultimediautils_p.h"
+#include "androidmediarecorder_p.h"
+#include <qvideosink.h>
+#include <QtConcurrent/qtconcurrentrun.h>
+#include <qfile.h>
+#include <qguiapplication.h>
+#include <qscreen.h>
+#include <qdebug.h>
+#include <qvideoframe.h>
+#include <private/qplatformimagecapture_p.h>
+#include <private/qplatformvideosink_p.h>
+#include <private/qmemoryvideobuffer_p.h>
+#include <private/qcameradevice_p.h>
+#include <private/qmediastoragelocation_p.h>
+#include <private/qvideoframe_p.h>
+#include <QImageWriter>
+
+QT_BEGIN_NAMESPACE
+
+Q_GLOBAL_STATIC(QList<QCameraDevice>, g_availableCameras)
+
+QAndroidCameraSession::QAndroidCameraSession(QObject *parent)
+ : QObject(parent)
+ , m_selectedCamera(0)
+ , m_camera(0)
+ , m_videoOutput(0)
+ , m_savedState(-1)
+ , m_previewStarted(false)
+ , m_readyForCapture(false)
+ , m_currentImageCaptureId(-1)
+ , m_previewCallback(0)
+ , m_keepActive(false)
+{
+ if (qApp) {
+ connect(qApp, &QGuiApplication::applicationStateChanged,
+ this, &QAndroidCameraSession::onApplicationStateChanged);
+
+ auto screen = qApp->primaryScreen();
+ if (screen) {
+ connect(screen, &QScreen::orientationChanged, this,
+ &QAndroidCameraSession::updateOrientation);
+ enableRotation();
+ }
+ }
+}
+
+QAndroidCameraSession::~QAndroidCameraSession()
+{
+ if (m_sink)
+ disconnect(m_retryPreviewConnection);
+ close();
+}
+
+//void QAndroidCameraSession::setCaptureMode(QCamera::CaptureModes mode)
+//{
+// if (m_captureMode == mode || !isCaptureModeSupported(mode))
+// return;
+
+// m_captureMode = mode;
+// emit captureModeChanged(m_captureMode);
+
+// if (m_previewStarted && m_captureMode.testFlag(QCamera::CaptureStillImage))
+// applyResolution(m_actualImageSettings.resolution());
+//}
+
+void QAndroidCameraSession::setActive(bool active)
+{
+ if (m_active == active)
+ return;
+
+ // If the application is inactive, the camera shouldn't be started. Save the desired state
+ // instead and it will be set when the application becomes active.
+ if (active && qApp->applicationState() == Qt::ApplicationInactive) {
+ m_isStateSaved = true;
+ m_savedState = active;
+ return;
+ }
+
+ m_isStateSaved = false;
+ m_active = active;
+ setActiveHelper(m_active);
+ emit activeChanged(m_active);
+}
+
+void QAndroidCameraSession::setActiveHelper(bool active)
+{
+ if (!active) {
+ stopPreview();
+ close();
+ } else {
+ if (!m_camera && !open()) {
+ emit error(QCamera::CameraError, QStringLiteral("Failed to open camera"));
+ return;
+ }
+ startPreview();
+ }
+}
+
+void QAndroidCameraSession::updateAvailableCameras()
+{
+ g_availableCameras->clear();
+
+ const int numCameras = AndroidCamera::getNumberOfCameras();
+ for (int i = 0; i < numCameras; ++i) {
+ QCameraDevicePrivate *info = new QCameraDevicePrivate;
+ AndroidCamera::getCameraInfo(i, info);
+
+ if (!info->id.isEmpty()) {
+ // Add supported picture and video sizes to the camera info
+ AndroidCamera *camera = AndroidCamera::open(i);
+
+ if (camera) {
+ info->videoFormats = camera->getSupportedFormats();
+ info->photoResolutions = camera->getSupportedPictureSizes();
+ }
+
+ delete camera;
+ g_availableCameras->append(info->create());
+ }
+ }
+}
+
+const QList<QCameraDevice> &QAndroidCameraSession::availableCameras()
+{
+ if (g_availableCameras->isEmpty())
+ updateAvailableCameras();
+
+ return *g_availableCameras;
+}
+
+bool QAndroidCameraSession::open()
+{
+ close();
+
+ m_camera = AndroidCamera::open(m_selectedCamera);
+
+ if (m_camera) {
+ connect(m_camera, &AndroidCamera::pictureExposed,
+ this, &QAndroidCameraSession::onCameraPictureExposed);
+ connect(m_camera, &AndroidCamera::lastPreviewFrameFetched,
+ this, &QAndroidCameraSession::onLastPreviewFrameFetched,
+ Qt::DirectConnection);
+ connect(m_camera, &AndroidCamera::newPreviewFrame,
+ this, &QAndroidCameraSession::onNewPreviewFrame,
+ Qt::DirectConnection);
+ connect(m_camera, &AndroidCamera::pictureCaptured,
+ this, &QAndroidCameraSession::onCameraPictureCaptured);
+ connect(m_camera, &AndroidCamera::previewStarted,
+ this, &QAndroidCameraSession::onCameraPreviewStarted);
+ connect(m_camera, &AndroidCamera::previewStopped,
+ this, &QAndroidCameraSession::onCameraPreviewStopped);
+ connect(m_camera, &AndroidCamera::previewFailedToStart,
+ this, &QAndroidCameraSession::onCameraPreviewFailedToStart);
+ connect(m_camera, &AndroidCamera::takePictureFailed,
+ this, &QAndroidCameraSession::onCameraTakePictureFailed);
+
+ if (m_camera->getPreviewFormat() != AndroidCamera::NV21)
+ m_camera->setPreviewFormat(AndroidCamera::NV21);
+
+ m_camera->notifyNewFrames(m_previewCallback);
+
+ emit opened();
+ setActive(true);
+ }
+
+ return m_camera != 0;
+}
+
+void QAndroidCameraSession::close()
+{
+ if (!m_camera)
+ return;
+
+ stopPreview();
+
+ m_readyForCapture = false;
+ m_currentImageCaptureId = -1;
+ m_currentImageCaptureFileName.clear();
+ m_actualImageSettings = m_requestedImageSettings;
+
+ m_camera->release();
+ delete m_camera;
+ m_camera = 0;
+
+ setActive(false);
+}
+
+void QAndroidCameraSession::setVideoOutput(QAndroidVideoOutput *output)
+{
+ if (m_videoOutput) {
+ m_videoOutput->stop();
+ m_videoOutput->reset();
+ }
+
+ if (output) {
+ m_videoOutput = output;
+ if (m_videoOutput->isReady()) {
+ onVideoOutputReady(true);
+ } else {
+ connect(m_videoOutput, &QAndroidVideoOutput::readyChanged,
+ this, &QAndroidCameraSession::onVideoOutputReady);
+ }
+ } else {
+ m_videoOutput = 0;
+ }
+}
+
+void QAndroidCameraSession::setCameraFormat(const QCameraFormat &format)
+{
+ m_requestedFpsRange.min = format.minFrameRate();
+ m_requestedFpsRange.max = format.maxFrameRate();
+ m_requestedPixelFromat = AndroidCamera::AndroidImageFormatFromQtPixelFormat(format.pixelFormat());
+
+ m_requestedImageSettings.setResolution(format.resolution());
+ m_actualImageSettings.setResolution(format.resolution());
+ if (m_readyForCapture)
+ applyResolution(m_actualImageSettings.resolution());
+}
+
+void QAndroidCameraSession::applyResolution(const QSize &captureSize, bool restartPreview)
+{
+ if (!m_camera)
+ return;
+
+ const QSize currentViewfinderResolution = m_camera->previewSize();
+ const AndroidCamera::ImageFormat currentPreviewFormat = m_camera->getPreviewFormat();
+ const AndroidCamera::FpsRange currentFpsRange = m_camera->getPreviewFpsRange();
+
+ // -- adjust resolution
+ QSize adjustedViewfinderResolution;
+ const QList<QSize> previewSizes = m_camera->getSupportedPreviewSizes();
+
+ const bool validCaptureSize = captureSize.width() > 0 && captureSize.height() > 0;
+ if (validCaptureSize
+ && m_camera->getPreferredPreviewSizeForVideo().isEmpty()) {
+ // According to the Android doc, if getPreferredPreviewSizeForVideo() returns null, it means
+ // the preview size cannot be different from the capture size
+ adjustedViewfinderResolution = captureSize;
+ } else {
+ qreal captureAspectRatio = 0;
+ if (validCaptureSize)
+ captureAspectRatio = qreal(captureSize.width()) / qreal(captureSize.height());
+
+ if (validCaptureSize) {
+ // search for viewfinder resolution with the same aspect ratio
+ qreal minAspectDiff = 1;
+ QSize closestResolution;
+ for (int i = previewSizes.count() - 1; i >= 0; --i) {
+ const QSize &size = previewSizes.at(i);
+ const qreal sizeAspect = qreal(size.width()) / size.height();
+ if (qFuzzyCompare(captureAspectRatio, sizeAspect)) {
+ adjustedViewfinderResolution = size;
+ break;
+ } else if (minAspectDiff > qAbs(sizeAspect - captureAspectRatio)) {
+ closestResolution = size;
+ minAspectDiff = qAbs(sizeAspect - captureAspectRatio);
+ }
+ }
+ if (!adjustedViewfinderResolution.isValid()) {
+ qWarning("Cannot find a viewfinder resolution matching the capture aspect ratio.");
+ if (closestResolution.isValid()) {
+ adjustedViewfinderResolution = closestResolution;
+ qWarning("Using closest viewfinder resolution.");
+ } else {
+ return;
+ }
+ }
+ } else {
+ adjustedViewfinderResolution = previewSizes.last();
+ }
+ }
+
+ // -- adjust pixel format
+
+ AndroidCamera::ImageFormat adjustedPreviewFormat = m_requestedPixelFromat;
+ if (adjustedPreviewFormat == AndroidCamera::UnknownImageFormat)
+ adjustedPreviewFormat = AndroidCamera::NV21;
+
+ // -- adjust FPS
+
+ AndroidCamera::FpsRange adjustedFps = m_requestedFpsRange;
+ if (adjustedFps.min == 0 || adjustedFps.max == 0)
+ adjustedFps = currentFpsRange;
+
+ // -- Set values on camera
+
+ // fix the resolution of output based on the orientation
+ QSize cameraOutputResolution = adjustedViewfinderResolution;
+ QSize videoOutputResolution = adjustedViewfinderResolution;
+ QSize currentVideoOutputResolution = m_videoOutput ? m_videoOutput->getVideoSize() : QSize(0, 0);
+ const int rotation = currentCameraRotation();
+ // only transpose if it's valid for the preview
+ if (rotation == 90 || rotation == 270) {
+ videoOutputResolution.transpose();
+ if (previewSizes.contains(cameraOutputResolution.transposed()))
+ cameraOutputResolution.transpose();
+ }
+
+ if (currentViewfinderResolution != cameraOutputResolution
+ || (m_videoOutput && currentVideoOutputResolution != videoOutputResolution)
+ || currentPreviewFormat != adjustedPreviewFormat || currentFpsRange.min != adjustedFps.min
+ || currentFpsRange.max != adjustedFps.max) {
+ if (m_videoOutput) {
+ m_videoOutput->setVideoSize(videoOutputResolution);
+ }
+
+ // if preview is started, we have to stop it first before changing its size
+ if (m_previewStarted && restartPreview)
+ m_camera->stopPreview();
+
+ m_camera->setPreviewSize(cameraOutputResolution);
+ m_camera->setPreviewFormat(adjustedPreviewFormat);
+ m_camera->setPreviewFpsRange(adjustedFps);
+
+ // restart preview
+ if (m_previewStarted && restartPreview)
+ m_camera->startPreview();
+ }
+}
+
+QList<QSize> QAndroidCameraSession::getSupportedPreviewSizes() const
+{
+ return m_camera ? m_camera->getSupportedPreviewSizes() : QList<QSize>();
+}
+
+QList<QVideoFrameFormat::PixelFormat> QAndroidCameraSession::getSupportedPixelFormats() const
+{
+ QList<QVideoFrameFormat::PixelFormat> formats;
+
+ if (!m_camera)
+ return formats;
+
+ const QList<AndroidCamera::ImageFormat> nativeFormats = m_camera->getSupportedPreviewFormats();
+
+ formats.reserve(nativeFormats.size());
+
+ for (AndroidCamera::ImageFormat nativeFormat : nativeFormats) {
+ QVideoFrameFormat::PixelFormat format = AndroidCamera::QtPixelFormatFromAndroidImageFormat(nativeFormat);
+ if (format != QVideoFrameFormat::Format_Invalid)
+ formats.append(format);
+ }
+
+ return formats;
+}
+
+QList<AndroidCamera::FpsRange> QAndroidCameraSession::getSupportedPreviewFpsRange() const
+{
+ return m_camera ? m_camera->getSupportedPreviewFpsRange() : QList<AndroidCamera::FpsRange>();
+}
+
+
+bool QAndroidCameraSession::startPreview()
+{
+ if (!m_camera || !m_videoOutput)
+ return false;
+
+ if (m_previewStarted)
+ return true;
+
+ if (!m_videoOutput->isReady())
+ return true; // delay starting until the video output is ready
+
+ Q_ASSERT(m_videoOutput->surfaceTexture() || m_videoOutput->surfaceHolder());
+
+ if ((m_videoOutput->surfaceTexture() && !m_camera->setPreviewTexture(m_videoOutput->surfaceTexture()))
+ || (m_videoOutput->surfaceHolder() && !m_camera->setPreviewDisplay(m_videoOutput->surfaceHolder())))
+ return false;
+
+ applyResolution(m_actualImageSettings.resolution());
+
+ AndroidMultimediaUtils::enableOrientationListener(true);
+
+ updateOrientation();
+ m_camera->startPreview();
+ m_previewStarted = true;
+ m_videoOutput->start();
+
+ return true;
+}
+
+QSize QAndroidCameraSession::getDefaultResolution() const
+{
+ const bool hasHighQualityProfile = AndroidCamcorderProfile::hasProfile(
+ m_camera->cameraId(),
+ AndroidCamcorderProfile::Quality(AndroidCamcorderProfile::QUALITY_HIGH));
+
+ if (hasHighQualityProfile) {
+ const AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(
+ m_camera->cameraId(),
+ AndroidCamcorderProfile::Quality(AndroidCamcorderProfile::QUALITY_HIGH));
+
+ return QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth),
+ camProfile.getValue(AndroidCamcorderProfile::videoFrameHeight));
+ }
+ return QSize();
+}
+
+void QAndroidCameraSession::stopPreview()
+{
+ if (!m_camera || !m_previewStarted)
+ return;
+
+ AndroidMultimediaUtils::enableOrientationListener(false);
+
+ m_camera->stopPreview();
+ m_camera->setPreviewSize(QSize());
+ m_camera->setPreviewTexture(0);
+ m_camera->setPreviewDisplay(0);
+
+ if (m_videoOutput) {
+ m_videoOutput->stop();
+ }
+ m_previewStarted = false;
+}
+
+void QAndroidCameraSession::setImageSettings(const QImageEncoderSettings &settings)
+{
+ if (m_requestedImageSettings == settings)
+ return;
+
+ m_requestedImageSettings = m_actualImageSettings = settings;
+
+ applyImageSettings();
+
+ if (m_readyForCapture)
+ applyResolution(m_actualImageSettings.resolution());
+}
+
+void QAndroidCameraSession::enableRotation()
+{
+ m_rotationEnabled = true;
+}
+
+void QAndroidCameraSession::disableRotation()
+{
+ m_rotationEnabled = false;
+}
+
+void QAndroidCameraSession::updateOrientation()
+{
+ if (!m_camera || !m_rotationEnabled)
+ return;
+
+ m_camera->setDisplayOrientation(currentCameraRotation());
+ applyResolution(m_actualImageSettings.resolution());
+}
+
+
+int QAndroidCameraSession::currentCameraRotation() const
+{
+ if (!m_camera)
+ return 0;
+
+ auto screen = QGuiApplication::primaryScreen();
+ auto screenOrientation = screen->orientation();
+ if (screenOrientation == Qt::PrimaryOrientation)
+ screenOrientation = screen->primaryOrientation();
+
+ int deviceOrientation = 0;
+ switch (screenOrientation) {
+ case Qt::PrimaryOrientation:
+ case Qt::PortraitOrientation:
+ break;
+ case Qt::LandscapeOrientation:
+ deviceOrientation = 90;
+ break;
+ case Qt::InvertedPortraitOrientation:
+ deviceOrientation = 180;
+ break;
+ case Qt::InvertedLandscapeOrientation:
+ deviceOrientation = 270;
+ break;
+ }
+
+ int nativeCameraOrientation = m_camera->getNativeOrientation();
+
+ int rotation;
+ // subtract natural camera orientation and physical device orientation
+ if (m_camera->getFacing() == AndroidCamera::CameraFacingFront) {
+ rotation = (nativeCameraOrientation + deviceOrientation) % 360;
+ rotation = (360 - rotation) % 360; // compensate the mirror
+ } else { // back-facing camera
+ rotation = (nativeCameraOrientation - deviceOrientation + 360) % 360;
+ }
+ return rotation;
+}
+
+void QAndroidCameraSession::setPreviewFormat(AndroidCamera::ImageFormat format)
+{
+ if (format == AndroidCamera::UnknownImageFormat)
+ return;
+
+ m_camera->setPreviewFormat(format);
+}
+
+void QAndroidCameraSession::setPreviewCallback(PreviewCallback *callback)
+{
+ m_videoFrameCallbackMutex.lock();
+ m_previewCallback = callback;
+ if (m_camera)
+ m_camera->notifyNewFrames(m_previewCallback);
+ m_videoFrameCallbackMutex.unlock();
+}
+
+void QAndroidCameraSession::applyImageSettings()
+{
+ if (!m_camera)
+ return;
+
+ // only supported format right now.
+ m_actualImageSettings.setFormat(QImageCapture::JPEG);
+
+ const QSize requestedResolution = m_requestedImageSettings.resolution();
+ const QList<QSize> supportedResolutions = m_camera->getSupportedPictureSizes();
+ if (!requestedResolution.isValid()) {
+ m_actualImageSettings.setResolution(getDefaultResolution());
+ } else if (!supportedResolutions.contains(requestedResolution)) {
+ // if the requested resolution is not supported, find the closest one
+ int reqPixelCount = requestedResolution.width() * requestedResolution.height();
+ QList<int> supportedPixelCounts;
+ for (int i = 0; i < supportedResolutions.size(); ++i) {
+ const QSize &s = supportedResolutions.at(i);
+ supportedPixelCounts.append(s.width() * s.height());
+ }
+ int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount);
+ m_actualImageSettings.setResolution(supportedResolutions.at(closestIndex));
+ }
+ m_camera->setPictureSize(m_actualImageSettings.resolution());
+
+ int jpegQuality = 100;
+ switch (m_requestedImageSettings.quality()) {
+ case QImageCapture::VeryLowQuality:
+ jpegQuality = 20;
+ break;
+ case QImageCapture::LowQuality:
+ jpegQuality = 40;
+ break;
+ case QImageCapture::NormalQuality:
+ jpegQuality = 60;
+ break;
+ case QImageCapture::HighQuality:
+ jpegQuality = 80;
+ break;
+ case QImageCapture::VeryHighQuality:
+ jpegQuality = 100;
+ break;
+ }
+ m_camera->setJpegQuality(jpegQuality);
+}
+
+bool QAndroidCameraSession::isReadyForCapture() const
+{
+ return isActive() && m_readyForCapture;
+}
+
+void QAndroidCameraSession::setReadyForCapture(bool ready)
+{
+ if (m_readyForCapture == ready)
+ return;
+
+ m_readyForCapture = ready;
+ emit readyForCaptureChanged(ready);
+}
+
+int QAndroidCameraSession::captureImage()
+{
+ const int newImageCaptureId = m_currentImageCaptureId + 1;
+
+ if (!isReadyForCapture()) {
+ emit imageCaptureError(newImageCaptureId, QImageCapture::NotReadyError,
+ QPlatformImageCapture::msgCameraNotReady());
+ return newImageCaptureId;
+ }
+
+ setReadyForCapture(false);
+
+ m_currentImageCaptureId = newImageCaptureId;
+
+ applyResolution(m_actualImageSettings.resolution());
+ m_camera->takePicture();
+
+ return m_currentImageCaptureId;
+}
+
+int QAndroidCameraSession::capture(const QString &fileName)
+{
+ m_currentImageCaptureFileName = fileName;
+ m_imageCaptureToBuffer = false;
+ return captureImage();
+}
+
+int QAndroidCameraSession::captureToBuffer()
+{
+ m_currentImageCaptureFileName.clear();
+ m_imageCaptureToBuffer = true;
+ return captureImage();
+}
+
+void QAndroidCameraSession::onCameraTakePictureFailed()
+{
+ emit imageCaptureError(m_currentImageCaptureId, QImageCapture::ResourceError,
+ tr("Failed to capture image"));
+
+ // Preview needs to be restarted and the preview call back must be setup again
+ m_camera->startPreview();
+}
+
+void QAndroidCameraSession::onCameraPictureExposed()
+{
+ if (!m_camera)
+ return;
+
+ emit imageExposed(m_currentImageCaptureId);
+ m_camera->fetchLastPreviewFrame();
+}
+
+void QAndroidCameraSession::onLastPreviewFrameFetched(const QVideoFrame &frame)
+{
+ if (!m_camera)
+ return;
+
+ updateOrientation();
+
+ (void)QtConcurrent::run(&QAndroidCameraSession::processPreviewImage, this,
+ m_currentImageCaptureId, frame, currentCameraRotation());
+}
+
+void QAndroidCameraSession::processPreviewImage(int id, const QVideoFrame &frame, int rotation)
+{
+ // Preview display of front-facing cameras is flipped horizontally, but the frame data
+ // we get here is not. Flip it ourselves if the camera is front-facing to match what the user
+ // sees on the viewfinder.
+ QTransform transform;
+ transform.rotate(rotation);
+
+ if (m_camera->getFacing() == AndroidCamera::CameraFacingFront)
+ transform.scale(-1, 1);
+
+ emit imageCaptured(id, frame.toImage().transformed(transform));
+}
+
+void QAndroidCameraSession::onNewPreviewFrame(const QVideoFrame &frame)
+{
+ if (!m_camera)
+ return;
+
+ m_videoFrameCallbackMutex.lock();
+
+ if (m_previewCallback)
+ m_previewCallback->onFrameAvailable(frame);
+
+ m_videoFrameCallbackMutex.unlock();
+}
+
+void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &bytes,
+ QVideoFrameFormat::PixelFormat format, QSize size,int bytesPerLine)
+{
+ if (m_imageCaptureToBuffer) {
+ processCapturedImageToBuffer(m_currentImageCaptureId, bytes, format, size, bytesPerLine);
+ } else {
+ // Loading and saving the captured image can be slow, do it in a separate thread
+ (void)QtConcurrent::run(&QAndroidCameraSession::processCapturedImage, this,
+ m_currentImageCaptureId, bytes, m_currentImageCaptureFileName);
+ }
+
+ // Preview needs to be restarted after taking a picture
+ if (m_camera)
+ m_camera->startPreview();
+}
+
+void QAndroidCameraSession::onCameraPreviewStarted()
+{
+ setReadyForCapture(true);
+}
+
+void QAndroidCameraSession::onCameraPreviewFailedToStart()
+{
+ if (isActive()) {
+ Q_EMIT error(QCamera::CameraError, tr("Camera preview failed to start."));
+
+ AndroidMultimediaUtils::enableOrientationListener(false);
+ m_camera->setPreviewSize(QSize());
+ m_camera->setPreviewTexture(0);
+ if (m_videoOutput) {
+ m_videoOutput->stop();
+ m_videoOutput->reset();
+ }
+ m_previewStarted = false;
+
+ setActive(false);
+ setReadyForCapture(false);
+ }
+}
+
+void QAndroidCameraSession::onCameraPreviewStopped()
+{
+ if (!m_previewStarted)
+ setActive(false);
+ setReadyForCapture(false);
+}
+
+void QAndroidCameraSession::processCapturedImage(int id, const QByteArray &bytes, const QString &fileName)
+{
+ const QString actualFileName = QMediaStorageLocation::generateFileName(
+ fileName, QStandardPaths::PicturesLocation, QLatin1String("jpg"));
+ QFile writer(actualFileName);
+ if (!writer.open(QIODeviceBase::WriteOnly)) {
+ const QString errorMessage = tr("File is not available: %1").arg(writer.errorString());
+ emit imageCaptureError(id, QImageCapture::Error::ResourceError, errorMessage);
+ return;
+ }
+
+ if (writer.write(bytes) < 0) {
+ const QString errorMessage = tr("Could not save to file: %1").arg(writer.errorString());
+ emit imageCaptureError(id, QImageCapture::Error::ResourceError, errorMessage);
+ return;
+ }
+
+ writer.close();
+ if (fileName.isEmpty() || QFileInfo(fileName).isRelative())
+ AndroidMultimediaUtils::registerMediaFile(actualFileName);
+
+ emit imageSaved(id, actualFileName);
+}
+
+void QAndroidCameraSession::processCapturedImageToBuffer(int id, const QByteArray &bytes,
+ QVideoFrameFormat::PixelFormat format, QSize size, int bytesPerLine)
+{
+ QVideoFrame frame = QVideoFramePrivate::createFrame(
+ std::make_unique<QMemoryVideoBuffer>(bytes, bytesPerLine),
+ QVideoFrameFormat(size, format));
+ emit imageAvailable(id, frame);
+}
+
+void QAndroidCameraSession::onVideoOutputReady(bool ready)
+{
+ if (ready && m_active)
+ startPreview();
+}
+
+void QAndroidCameraSession::onApplicationStateChanged()
+{
+
+ switch (QGuiApplication::applicationState()) {
+ case Qt::ApplicationInactive:
+ if (!m_keepActive && m_active) {
+ m_savedState = m_active;
+ setActive(false);
+ m_isStateSaved = true;
+ }
+ break;
+ case Qt::ApplicationActive:
+ if (m_isStateSaved) {
+ setActive(m_savedState);
+ m_isStateSaved = false;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void QAndroidCameraSession::setKeepAlive(bool keepAlive)
+{
+ m_keepActive = keepAlive;
+}
+
+void QAndroidCameraSession::setVideoSink(QVideoSink *sink)
+{
+ if (m_sink == sink)
+ return;
+
+ if (m_sink)
+ disconnect(m_retryPreviewConnection);
+
+ m_sink = sink;
+
+ if (m_sink)
+ m_retryPreviewConnection =
+ connect(m_sink->platformVideoSink(), &QPlatformVideoSink::rhiChanged, this, [&]()
+ {
+ if (m_active) {
+ setActive(false);
+ setActive(true);
+ }
+ }, Qt::DirectConnection);
+ if (m_sink) {
+ delete m_textureOutput;
+ m_textureOutput = nullptr;
+
+ m_textureOutput = new QAndroidTextureVideoOutput(m_sink, this);
+ }
+
+ setVideoOutput(m_textureOutput);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qandroidcamerasession_p.cpp"
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcamerasession_p.h b/src/plugins/multimedia/android/mediacapture/qandroidcamerasession_p.h
new file mode 100644
index 000000000..3b56d9c3b
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcamerasession_p.h
@@ -0,0 +1,166 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2016 Ruslan Baratov
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDCAMERASESSION_H
+#define QANDROIDCAMERASESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qcamera.h>
+#include <QImageCapture>
+#include <QSet>
+#include <QMutex>
+#include <private/qplatformimagecapture_p.h>
+#include "androidcamera_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidVideoOutput;
+class QAndroidTextureVideoOutput ;
+class QVideoSink;
+
+class QAndroidCameraSession : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QAndroidCameraSession(QObject *parent = 0);
+ ~QAndroidCameraSession();
+
+ static const QList<QCameraDevice> &availableCameras();
+
+ void setSelectedCameraId(int cameraId) { m_selectedCamera = cameraId; }
+ int getSelectedCameraId() { return m_selectedCamera; }
+ AndroidCamera *camera() const { return m_camera; }
+
+ bool isActive() const { return m_active; }
+ void setActive(bool active);
+
+ void applyResolution(const QSize &captureSize = QSize(), bool restartPreview = true);
+
+ QAndroidVideoOutput *videoOutput() const { return m_videoOutput; }
+ void setVideoOutput(QAndroidVideoOutput *output);
+
+ void setCameraFormat(const QCameraFormat &format);
+
+ QList<QSize> getSupportedPreviewSizes() const;
+ QList<QVideoFrameFormat::PixelFormat> getSupportedPixelFormats() const;
+ QList<AndroidCamera::FpsRange> getSupportedPreviewFpsRange() const;
+
+ QImageEncoderSettings imageSettings() const { return m_actualImageSettings; }
+ void setImageSettings(const QImageEncoderSettings &settings);
+
+ bool isReadyForCapture() const;
+ void setReadyForCapture(bool ready);
+ int capture(const QString &fileName);
+ int captureToBuffer();
+
+ int currentCameraRotation() const;
+
+ void setPreviewFormat(AndroidCamera::ImageFormat format);
+
+ struct PreviewCallback
+ {
+ virtual void onFrameAvailable(const QVideoFrame &frame) = 0;
+ };
+ void setPreviewCallback(PreviewCallback *callback);
+
+ void setVideoSink(QVideoSink *surface);
+
+ void disableRotation();
+ void enableRotation();
+
+ void setKeepAlive(bool keepAlive);
+
+Q_SIGNALS:
+ void activeChanged(bool);
+ void error(int error, const QString &errorString);
+ void opened();
+
+ void readyForCaptureChanged(bool);
+ void imageExposed(int id);
+ void imageCaptured(int id, const QImage &preview);
+ void imageMetadataAvailable(int id, const QMediaMetaData &key);
+ void imageAvailable(int id, const QVideoFrame &buffer);
+ void imageSaved(int id, const QString &fileName);
+ void imageCaptureError(int id, int error, const QString &errorString);
+
+private Q_SLOTS:
+ void onVideoOutputReady(bool ready);
+ void updateOrientation();
+
+ void onApplicationStateChanged();
+
+ void onCameraTakePictureFailed();
+ void onCameraPictureExposed();
+ void onCameraPictureCaptured(const QByteArray &bytes, QVideoFrameFormat::PixelFormat format, QSize size, int bytesPerLine);
+ void onLastPreviewFrameFetched(const QVideoFrame &frame);
+ void onNewPreviewFrame(const QVideoFrame &frame);
+ void onCameraPreviewStarted();
+ void onCameraPreviewFailedToStart();
+ void onCameraPreviewStopped();
+
+private:
+ static void updateAvailableCameras();
+
+ bool open();
+ void close();
+
+ bool startPreview();
+ void stopPreview();
+
+ void applyImageSettings();
+
+ void processPreviewImage(int id, const QVideoFrame &frame, int rotation);
+ void processCapturedImage(int id, const QByteArray &bytes, const QString &fileName);
+ void processCapturedImageToBuffer(int id, const QByteArray &bytes,
+ QVideoFrameFormat::PixelFormat format, QSize size, int bytesPerLine);
+
+ void setActiveHelper(bool active);
+
+ int captureImage();
+
+ QSize getDefaultResolution() const;
+
+ int m_selectedCamera;
+ AndroidCamera *m_camera;
+ QAndroidVideoOutput *m_videoOutput;
+
+ bool m_active = false;
+ bool m_isStateSaved = false;
+ bool m_savedState = false;
+ bool m_previewStarted;
+
+ bool m_rotationEnabled = false;
+
+ QVideoSink *m_sink = nullptr;
+ QAndroidTextureVideoOutput *m_textureOutput = nullptr;
+
+ QImageEncoderSettings m_requestedImageSettings;
+ QImageEncoderSettings m_actualImageSettings;
+ AndroidCamera::FpsRange m_requestedFpsRange;
+ AndroidCamera::ImageFormat m_requestedPixelFromat = AndroidCamera::ImageFormat::NV21;
+
+ bool m_readyForCapture;
+ int m_currentImageCaptureId;
+ QString m_currentImageCaptureFileName;
+ bool m_imageCaptureToBuffer;
+
+ QMutex m_videoFrameCallbackMutex;
+ PreviewCallback *m_previewCallback;
+ bool m_keepActive;
+ QMetaObject::Connection m_retryPreviewConnection;
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDCAMERASESSION_H
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp
new file mode 100644
index 000000000..3b005e4a5
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession.cpp
@@ -0,0 +1,473 @@
+// 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 "qandroidcapturesession_p.h"
+
+#include "androidcamera_p.h"
+#include "qandroidcamerasession_p.h"
+#include "qaudioinput.h"
+#include "qaudiooutput.h"
+#include "androidmediaplayer_p.h"
+#include "androidmultimediautils_p.h"
+#include "qandroidmultimediautils_p.h"
+#include "qandroidvideooutput_p.h"
+#include "qandroidglobal_p.h"
+#include <private/qplatformaudioinput_p.h>
+#include <private/qplatformaudiooutput_p.h>
+#include <private/qmediarecorder_p.h>
+#include <private/qmediastoragelocation_p.h>
+#include <QtCore/qmimetype.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+QAndroidCaptureSession::QAndroidCaptureSession()
+ : QObject()
+ , m_mediaRecorder(0)
+ , m_cameraSession(0)
+ , m_duration(0)
+ , m_state(QMediaRecorder::StoppedState)
+ , m_outputFormat(AndroidMediaRecorder::DefaultOutputFormat)
+ , m_audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder)
+ , m_videoEncoder(AndroidMediaRecorder::DefaultVideoEncoder)
+{
+ m_notifyTimer.setInterval(1000);
+ connect(&m_notifyTimer, &QTimer::timeout, this, &QAndroidCaptureSession::updateDuration);
+}
+
+QAndroidCaptureSession::~QAndroidCaptureSession()
+{
+ stop();
+ m_mediaRecorder = nullptr;
+ if (m_audioInput && m_audioOutput)
+ AndroidMediaPlayer::stopSoundStreaming();
+}
+
+void QAndroidCaptureSession::setCameraSession(QAndroidCameraSession *cameraSession)
+{
+ if (m_cameraSession) {
+ disconnect(m_connOpenCamera);
+ disconnect(m_connActiveChangedCamera);
+ }
+
+ m_cameraSession = cameraSession;
+ if (m_cameraSession) {
+ m_connOpenCamera = connect(cameraSession, &QAndroidCameraSession::opened,
+ this, &QAndroidCaptureSession::onCameraOpened);
+ m_connActiveChangedCamera = connect(cameraSession, &QAndroidCameraSession::activeChanged,
+ this, [this](bool isActive) {
+ if (!isActive)
+ stop();
+ });
+ }
+}
+
+void QAndroidCaptureSession::setAudioInput(QPlatformAudioInput *input)
+{
+ if (m_audioInput == input)
+ return;
+
+ if (m_audioInput) {
+ disconnect(m_audioInputChanged);
+ }
+
+ m_audioInput = input;
+
+ if (m_audioInput) {
+ m_audioInputChanged = connect(m_audioInput->q, &QAudioInput::deviceChanged, this, [this]() {
+ if (m_state == QMediaRecorder::RecordingState)
+ m_mediaRecorder->setAudioInput(m_audioInput->device.id());
+ updateStreamingState();
+ });
+ }
+ updateStreamingState();
+}
+
+void QAndroidCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
+{
+ if (m_audioOutput == output)
+ return;
+
+ if (m_audioOutput)
+ disconnect(m_audioOutputChanged);
+
+ m_audioOutput = output;
+
+ if (m_audioOutput) {
+ m_audioOutputChanged = connect(m_audioOutput->q, &QAudioOutput::deviceChanged, this,
+ [this] () {
+ AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
+ updateStreamingState();
+ });
+ AndroidMediaPlayer::setAudioOutput(m_audioOutput->device.id());
+ }
+ updateStreamingState();
+}
+
+void QAndroidCaptureSession::updateStreamingState()
+{
+ if (m_audioInput && m_audioOutput) {
+ AndroidMediaPlayer::startSoundStreaming(m_audioInput->device.id().toInt(),
+ m_audioOutput->device.id().toInt());
+ } else {
+ AndroidMediaPlayer::stopSoundStreaming();
+ }
+}
+
+QMediaRecorder::RecorderState QAndroidCaptureSession::state() const
+{
+ return m_state;
+}
+
+void QAndroidCaptureSession::setKeepAlive(bool keepAlive)
+{
+ if (m_cameraSession)
+ m_cameraSession->setKeepAlive(keepAlive);
+}
+
+
+void QAndroidCaptureSession::start(QMediaEncoderSettings &settings, const QUrl &outputLocation)
+{
+ if (m_state == QMediaRecorder::RecordingState)
+ return;
+
+ if (!m_cameraSession && !m_audioInput) {
+ updateError(QMediaRecorder::ResourceError, QLatin1String("No devices are set"));
+ return;
+ }
+
+ setKeepAlive(true);
+
+ const bool validCameraSession = m_cameraSession && m_cameraSession->camera();
+
+ if (validCameraSession && !qt_androidCheckCameraPermission()) {
+ updateError(QMediaRecorder::ResourceError, QLatin1String("Camera permission denied."));
+ setKeepAlive(false);
+ return;
+ }
+
+ if (m_audioInput && !qt_androidCheckMicrophonePermission()) {
+ updateError(QMediaRecorder::ResourceError, QLatin1String("Microphone permission denied."));
+ setKeepAlive(false);
+ return;
+ }
+
+ m_mediaRecorder = std::make_shared<AndroidMediaRecorder>();
+ connect(m_mediaRecorder.get(), &AndroidMediaRecorder::error, this,
+ &QAndroidCaptureSession::onError);
+ connect(m_mediaRecorder.get(), &AndroidMediaRecorder::info, this,
+ &QAndroidCaptureSession::onInfo);
+
+ applySettings(settings);
+
+ // Set audio/video sources
+ if (validCameraSession) {
+ m_cameraSession->camera()->stopPreviewSynchronous();
+ m_cameraSession->camera()->unlock();
+
+ m_mediaRecorder->setCamera(m_cameraSession->camera());
+ m_mediaRecorder->setVideoSource(AndroidMediaRecorder::Camera);
+ }
+
+ if (m_audioInput) {
+ m_mediaRecorder->setAudioInput(m_audioInput->device.id());
+ if (!m_mediaRecorder->isAudioSourceSet())
+ m_mediaRecorder->setAudioSource(AndroidMediaRecorder::DefaultAudioSource);
+ }
+
+ // Set output format
+ m_mediaRecorder->setOutputFormat(m_outputFormat);
+
+ // Set video encoder settings
+ if (validCameraSession) {
+ m_mediaRecorder->setVideoSize(settings.videoResolution());
+ m_mediaRecorder->setVideoFrameRate(qRound(settings.videoFrameRate()));
+ m_mediaRecorder->setVideoEncodingBitRate(settings.videoBitRate());
+ m_mediaRecorder->setVideoEncoder(m_videoEncoder);
+
+ // media recorder is also compensanting the mirror on front camera
+ auto rotation = m_cameraSession->currentCameraRotation();
+ if (m_cameraSession->camera()->getFacing() == AndroidCamera::CameraFacingFront)
+ rotation = (360 - rotation) % 360; // remove mirror compensation
+
+ m_mediaRecorder->setOrientationHint(rotation);
+ }
+
+ // Set audio encoder settings
+ if (m_audioInput) {
+ m_mediaRecorder->setAudioChannels(settings.audioChannelCount());
+ m_mediaRecorder->setAudioEncodingBitRate(settings.audioBitRate());
+ m_mediaRecorder->setAudioSamplingRate(settings.audioSampleRate());
+ m_mediaRecorder->setAudioEncoder(m_audioEncoder);
+ }
+
+ QString extension = settings.mimeType().preferredSuffix();
+ // Set output file
+ auto location = outputLocation.toString(QUrl::PreferLocalFile);
+ QString filePath = location;
+ if (QUrl(filePath).scheme() != QLatin1String("content")) {
+ filePath = QMediaStorageLocation::generateFileName(
+ location, m_cameraSession ? QStandardPaths::MoviesLocation
+ : QStandardPaths::MusicLocation, extension);
+ }
+
+ m_usedOutputLocation = QUrl::fromLocalFile(filePath);
+ m_outputLocationIsStandard = location.isEmpty() || QFileInfo(location).isRelative();
+ m_mediaRecorder->setOutputFile(filePath);
+
+ if (validCameraSession) {
+ m_cameraSession->disableRotation();
+ }
+
+ if (!m_mediaRecorder->prepare()) {
+ updateError(QMediaRecorder::FormatError,
+ QLatin1String("Unable to prepare the media recorder."));
+ restartViewfinder();
+
+ return;
+ }
+
+ if (!m_mediaRecorder->start()) {
+ updateError(QMediaRecorder::FormatError, QMediaRecorderPrivate::msgFailedStartRecording());
+ restartViewfinder();
+
+ return;
+ }
+
+ m_elapsedTime.start();
+ m_notifyTimer.start();
+ updateDuration();
+
+ if (validCameraSession) {
+ m_cameraSession->setReadyForCapture(false);
+
+ // Preview frame callback is cleared when setting up the camera with the media recorder.
+ // We need to reset it.
+ m_cameraSession->camera()->setupPreviewFrameCallback();
+ }
+
+ m_state = QMediaRecorder::RecordingState;
+ emit stateChanged(m_state);
+}
+
+void QAndroidCaptureSession::stop(bool error)
+{
+ if (m_state == QMediaRecorder::StoppedState || m_mediaRecorder == nullptr)
+ return;
+
+ m_mediaRecorder->stop();
+ m_notifyTimer.stop();
+ updateDuration();
+ m_elapsedTime.invalidate();
+
+ m_mediaRecorder = nullptr;
+
+ if (m_cameraSession && m_cameraSession->isActive()) {
+ // Viewport needs to be restarted after recording
+ restartViewfinder();
+ }
+
+ if (!error) {
+ // if the media is saved into the standard media location, register it
+ // with the Android media scanner so it appears immediately in apps
+ // such as the gallery.
+ if (m_outputLocationIsStandard)
+ AndroidMultimediaUtils::registerMediaFile(m_usedOutputLocation.toLocalFile());
+
+ emit actualLocationChanged(m_usedOutputLocation);
+ }
+
+ m_state = QMediaRecorder::StoppedState;
+ emit stateChanged(m_state);
+}
+
+qint64 QAndroidCaptureSession::duration() const
+{
+ return m_duration;
+}
+
+void QAndroidCaptureSession::applySettings(QMediaEncoderSettings &settings)
+{
+ // container settings
+ auto fileFormat = settings.mediaFormat().fileFormat();
+ if (!m_cameraSession && fileFormat == QMediaFormat::AAC) {
+ m_outputFormat = AndroidMediaRecorder::AAC_ADTS;
+ } else if (fileFormat == QMediaFormat::Ogg) {
+ m_outputFormat = AndroidMediaRecorder::OGG;
+ } else if (fileFormat == QMediaFormat::WebM) {
+ m_outputFormat = AndroidMediaRecorder::WEBM;
+// } else if (fileFormat == QLatin1String("3gp")) {
+// m_outputFormat = AndroidMediaRecorder::THREE_GPP;
+ } else {
+ // fallback to MP4
+ m_outputFormat = AndroidMediaRecorder::MPEG_4;
+ }
+
+ // audio settings
+ if (settings.audioChannelCount() <= 0)
+ settings.setAudioChannelCount(m_defaultSettings.audioChannels);
+ if (settings.audioBitRate() <= 0)
+ settings.setAudioBitRate(m_defaultSettings.audioBitRate);
+ if (settings.audioSampleRate() <= 0)
+ settings.setAudioSampleRate(m_defaultSettings.audioSampleRate);
+
+ if (settings.audioCodec() == QMediaFormat::AudioCodec::AAC)
+ m_audioEncoder = AndroidMediaRecorder::AAC;
+ else if (settings.audioCodec() == QMediaFormat::AudioCodec::Opus)
+ m_audioEncoder = AndroidMediaRecorder::OPUS;
+ else if (settings.audioCodec() == QMediaFormat::AudioCodec::Vorbis)
+ m_audioEncoder = AndroidMediaRecorder::VORBIS;
+ else
+ m_audioEncoder = m_defaultSettings.audioEncoder;
+
+
+ // video settings
+ if (m_cameraSession && m_cameraSession->camera()) {
+ if (settings.videoResolution().isEmpty()) {
+ settings.setVideoResolution(m_defaultSettings.videoResolution);
+ } else if (!m_supportedResolutions.contains(settings.videoResolution())) {
+ // if the requested resolution is not supported, find the closest one
+ QSize reqSize = settings.videoResolution();
+ int reqPixelCount = reqSize.width() * reqSize.height();
+ QList<int> supportedPixelCounts;
+ for (int i = 0; i < m_supportedResolutions.size(); ++i) {
+ const QSize &s = m_supportedResolutions.at(i);
+ supportedPixelCounts.append(s.width() * s.height());
+ }
+ int closestIndex = qt_findClosestValue(supportedPixelCounts, reqPixelCount);
+ settings.setVideoResolution(m_supportedResolutions.at(closestIndex));
+ }
+
+ if (settings.videoFrameRate() <= 0)
+ settings.setVideoFrameRate(m_defaultSettings.videoFrameRate);
+ if (settings.videoBitRate() <= 0)
+ settings.setVideoBitRate(m_defaultSettings.videoBitRate);
+
+ if (settings.videoCodec() == QMediaFormat::VideoCodec::H264)
+ m_videoEncoder = AndroidMediaRecorder::H264;
+ else if (settings.videoCodec() == QMediaFormat::VideoCodec::H265)
+ m_videoEncoder = AndroidMediaRecorder::HEVC;
+ else if (settings.videoCodec() == QMediaFormat::VideoCodec::MPEG4)
+ m_videoEncoder = AndroidMediaRecorder::MPEG_4_SP;
+ else
+ m_videoEncoder = m_defaultSettings.videoEncoder;
+
+ }
+}
+
+void QAndroidCaptureSession::restartViewfinder()
+{
+
+ setKeepAlive(false);
+
+ if (!m_cameraSession)
+ return;
+
+ if (m_cameraSession && m_cameraSession->camera()) {
+ m_cameraSession->camera()->reconnect();
+ m_cameraSession->camera()->stopPreviewSynchronous();
+ m_cameraSession->camera()->startPreview();
+ m_cameraSession->setReadyForCapture(true);
+ m_cameraSession->enableRotation();
+ }
+
+ m_mediaRecorder = nullptr;
+}
+
+void QAndroidCaptureSession::updateDuration()
+{
+ if (m_elapsedTime.isValid())
+ m_duration = m_elapsedTime.elapsed();
+
+ emit durationChanged(m_duration);
+}
+
+void QAndroidCaptureSession::onCameraOpened()
+{
+ m_supportedResolutions.clear();
+ m_supportedFramerates.clear();
+
+ // get supported resolutions from predefined profiles
+ for (int i = 0; i < 8; ++i) {
+ CaptureProfile profile = getProfile(i);
+ if (!profile.isNull) {
+ if (i == AndroidCamcorderProfile::QUALITY_HIGH)
+ m_defaultSettings = profile;
+
+ if (!m_supportedResolutions.contains(profile.videoResolution))
+ m_supportedResolutions.append(profile.videoResolution);
+ if (!m_supportedFramerates.contains(profile.videoFrameRate))
+ m_supportedFramerates.append(profile.videoFrameRate);
+ }
+ }
+
+ std::sort(m_supportedResolutions.begin(), m_supportedResolutions.end(), qt_sizeLessThan);
+ std::sort(m_supportedFramerates.begin(), m_supportedFramerates.end());
+
+ QMediaEncoderSettings defaultSettings;
+ applySettings(defaultSettings);
+ m_cameraSession->applyResolution(defaultSettings.videoResolution());
+}
+
+QAndroidCaptureSession::CaptureProfile QAndroidCaptureSession::getProfile(int id)
+{
+ CaptureProfile profile;
+ const bool hasProfile = AndroidCamcorderProfile::hasProfile(m_cameraSession->camera()->cameraId(),
+ AndroidCamcorderProfile::Quality(id));
+
+ if (hasProfile) {
+ AndroidCamcorderProfile camProfile = AndroidCamcorderProfile::get(m_cameraSession->camera()->cameraId(),
+ AndroidCamcorderProfile::Quality(id));
+
+ profile.outputFormat = AndroidMediaRecorder::OutputFormat(camProfile.getValue(AndroidCamcorderProfile::fileFormat));
+ profile.audioEncoder = AndroidMediaRecorder::AudioEncoder(camProfile.getValue(AndroidCamcorderProfile::audioCodec));
+ profile.audioBitRate = camProfile.getValue(AndroidCamcorderProfile::audioBitRate);
+ profile.audioChannels = camProfile.getValue(AndroidCamcorderProfile::audioChannels);
+ profile.audioSampleRate = camProfile.getValue(AndroidCamcorderProfile::audioSampleRate);
+ profile.videoEncoder = AndroidMediaRecorder::VideoEncoder(camProfile.getValue(AndroidCamcorderProfile::videoCodec));
+ profile.videoBitRate = camProfile.getValue(AndroidCamcorderProfile::videoBitRate);
+ profile.videoFrameRate = camProfile.getValue(AndroidCamcorderProfile::videoFrameRate);
+ profile.videoResolution = QSize(camProfile.getValue(AndroidCamcorderProfile::videoFrameWidth),
+ camProfile.getValue(AndroidCamcorderProfile::videoFrameHeight));
+
+ if (profile.outputFormat == AndroidMediaRecorder::MPEG_4)
+ profile.outputFileExtension = QStringLiteral("mp4");
+ else if (profile.outputFormat == AndroidMediaRecorder::THREE_GPP)
+ profile.outputFileExtension = QStringLiteral("3gp");
+ else if (profile.outputFormat == AndroidMediaRecorder::AMR_NB_Format)
+ profile.outputFileExtension = QStringLiteral("amr");
+ else if (profile.outputFormat == AndroidMediaRecorder::AMR_WB_Format)
+ profile.outputFileExtension = QStringLiteral("awb");
+
+ profile.isNull = false;
+ }
+
+ return profile;
+}
+
+void QAndroidCaptureSession::onError(int what, int extra)
+{
+ Q_UNUSED(what);
+ Q_UNUSED(extra);
+ stop(true);
+ updateError(QMediaRecorder::ResourceError, QLatin1String("Unknown error."));
+}
+
+void QAndroidCaptureSession::onInfo(int what, int extra)
+{
+ Q_UNUSED(extra);
+ if (what == 800) {
+ // MEDIA_RECORDER_INFO_MAX_DURATION_REACHED
+ stop();
+ updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum duration reached."));
+ } else if (what == 801) {
+ // MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED
+ stop();
+ updateError(QMediaRecorder::OutOfSpaceError, QLatin1String("Maximum file size reached."));
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qandroidcapturesession_p.cpp"
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h
new file mode 100644
index 000000000..161d47994
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidcapturesession_p.h
@@ -0,0 +1,158 @@
+// 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
+
+#ifndef QANDROIDCAPTURESESSION_H
+#define QANDROIDCAPTURESESSION_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qobject.h>
+#include <qmediarecorder.h>
+#include <qurl.h>
+#include <qelapsedtimer.h>
+#include <qtimer.h>
+#include "androidmediarecorder_p.h"
+#include "qandroidmediaencoder_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QAudioInput;
+class QAndroidCameraSession;
+
+class QAndroidCaptureSession : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QAndroidCaptureSession();
+ ~QAndroidCaptureSession();
+
+ QList<QSize> supportedResolutions() const { return m_supportedResolutions; }
+ QList<qreal> supportedFrameRates() const { return m_supportedFramerates; }
+
+ void setCameraSession(QAndroidCameraSession *cameraSession = 0);
+ void setAudioInput(QPlatformAudioInput *input);
+ void setAudioOutput(QPlatformAudioOutput *output);
+
+ QMediaRecorder::RecorderState state() const;
+
+ void start(QMediaEncoderSettings &settings, const QUrl &outputLocation);
+ void stop(bool error = false);
+
+ qint64 duration() const;
+
+ QMediaEncoderSettings encoderSettings() { return m_encoderSettings; }
+
+ void setMediaEncoder(QAndroidMediaEncoder *encoder) { m_mediaEncoder = encoder; }
+
+ void stateChanged(QMediaRecorder::RecorderState state) {
+ if (m_mediaEncoder)
+ m_mediaEncoder->stateChanged(state);
+ }
+ void durationChanged(qint64 position)
+ {
+ if (m_mediaEncoder)
+ m_mediaEncoder->durationChanged(position);
+ }
+ void actualLocationChanged(const QUrl &location)
+ {
+ if (m_mediaEncoder)
+ m_mediaEncoder->actualLocationChanged(location);
+ }
+ void updateError(int error, const QString &errorString)
+ {
+ if (m_mediaEncoder)
+ m_mediaEncoder->updateError(QMediaRecorder::Error(error), errorString);
+ }
+
+private Q_SLOTS:
+ void updateDuration();
+ void onCameraOpened();
+
+ void onError(int what, int extra);
+ void onInfo(int what, int extra);
+
+private:
+ void applySettings(QMediaEncoderSettings &settings);
+
+ struct CaptureProfile {
+ AndroidMediaRecorder::OutputFormat outputFormat;
+ QString outputFileExtension;
+
+ AndroidMediaRecorder::AudioEncoder audioEncoder;
+ int audioBitRate;
+ int audioChannels;
+ int audioSampleRate;
+
+ AndroidMediaRecorder::VideoEncoder videoEncoder;
+ int videoBitRate;
+ int videoFrameRate;
+ QSize videoResolution;
+
+ bool isNull;
+
+ CaptureProfile()
+ : outputFormat(AndroidMediaRecorder::MPEG_4)
+ , outputFileExtension(QLatin1String("mp4"))
+ , audioEncoder(AndroidMediaRecorder::DefaultAudioEncoder)
+ , audioBitRate(128000)
+ , audioChannels(2)
+ , audioSampleRate(44100)
+ , videoEncoder(AndroidMediaRecorder::DefaultVideoEncoder)
+ , videoBitRate(1)
+ , videoFrameRate(-1)
+ , videoResolution(1280, 720)
+ , isNull(true)
+ { }
+ };
+
+ CaptureProfile getProfile(int id);
+
+ void restartViewfinder();
+ void updateStreamingState();
+
+ QAndroidMediaEncoder *m_mediaEncoder = nullptr;
+ std::shared_ptr<AndroidMediaRecorder> m_mediaRecorder;
+ QAndroidCameraSession *m_cameraSession;
+
+ QPlatformAudioInput *m_audioInput = nullptr;
+ QPlatformAudioOutput *m_audioOutput = nullptr;
+
+ QElapsedTimer m_elapsedTime;
+ QTimer m_notifyTimer;
+ qint64 m_duration;
+
+ QMediaRecorder::RecorderState m_state;
+ QUrl m_usedOutputLocation;
+ bool m_outputLocationIsStandard = false;
+
+ CaptureProfile m_defaultSettings;
+
+ QMediaEncoderSettings m_encoderSettings;
+ AndroidMediaRecorder::OutputFormat m_outputFormat;
+ AndroidMediaRecorder::AudioEncoder m_audioEncoder;
+ AndroidMediaRecorder::VideoEncoder m_videoEncoder;
+
+ QList<QSize> m_supportedResolutions;
+ QList<qreal> m_supportedFramerates;
+
+ QMetaObject::Connection m_audioInputChanged;
+ QMetaObject::Connection m_audioOutputChanged;
+ QMetaObject::Connection m_connOpenCamera;
+ QMetaObject::Connection m_connActiveChangedCamera;
+
+ void setKeepAlive(bool keepAlive);
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDCAPTURESESSION_H
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidimagecapture.cpp b/src/plugins/multimedia/android/mediacapture/qandroidimagecapture.cpp
new file mode 100644
index 000000000..4105851ed
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidimagecapture.cpp
@@ -0,0 +1,73 @@
+// 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 "qandroidimagecapture_p.h"
+
+#include "qandroidcamerasession_p.h"
+#include "qandroidmediacapturesession_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAndroidImageCapture::QAndroidImageCapture(QImageCapture *parent)
+ : QPlatformImageCapture(parent)
+{
+}
+
+bool QAndroidImageCapture::isReadyForCapture() const
+{
+ return m_session->isReadyForCapture();
+}
+
+int QAndroidImageCapture::capture(const QString &fileName)
+{
+ return m_session->capture(fileName);
+}
+
+int QAndroidImageCapture::captureToBuffer()
+{
+ return m_session->captureToBuffer();
+}
+
+QImageEncoderSettings QAndroidImageCapture::imageSettings() const
+{
+ return m_session->imageSettings();
+}
+
+void QAndroidImageCapture::setImageSettings(const QImageEncoderSettings &settings)
+{
+ m_session->setImageSettings(settings);
+}
+
+void QAndroidImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
+{
+ QAndroidMediaCaptureSession *captureSession = static_cast<QAndroidMediaCaptureSession *>(session);
+ if (m_service == captureSession)
+ return;
+
+ m_service = captureSession;
+ if (!m_service) {
+ disconnect(m_session, nullptr, this, nullptr);
+ return;
+ }
+
+ m_session = m_service->cameraSession();
+ Q_ASSERT(m_session);
+
+ connect(m_session, &QAndroidCameraSession::readyForCaptureChanged,
+ this, &QAndroidImageCapture::readyForCaptureChanged);
+ connect(m_session, &QAndroidCameraSession::imageExposed,
+ this, &QAndroidImageCapture::imageExposed);
+ connect(m_session, &QAndroidCameraSession::imageCaptured,
+ this, &QAndroidImageCapture::imageCaptured);
+ connect(m_session, &QAndroidCameraSession::imageMetadataAvailable,
+ this, &QAndroidImageCapture::imageMetadataAvailable);
+ connect(m_session, &QAndroidCameraSession::imageAvailable,
+ this, &QAndroidImageCapture::imageAvailable);
+ connect(m_session, &QAndroidCameraSession::imageSaved,
+ this, &QAndroidImageCapture::imageSaved);
+ connect(m_session, &QAndroidCameraSession::imageCaptureError,
+ this, &QAndroidImageCapture::error);
+}
+QT_END_NAMESPACE
+
+#include "moc_qandroidimagecapture_p.cpp"
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidimagecapture_p.h b/src/plugins/multimedia/android/mediacapture/qandroidimagecapture_p.h
new file mode 100644
index 000000000..ac273c195
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidimagecapture_p.h
@@ -0,0 +1,48 @@
+// 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
+
+#ifndef QANDROIDCAMERAIMAGECAPTURECONTROL_H
+#define QANDROIDCAMERAIMAGECAPTURECONTROL_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformimagecapture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidCameraSession;
+class QAndroidMediaCaptureSession;
+
+class QAndroidImageCapture : public QPlatformImageCapture
+{
+ Q_OBJECT
+public:
+ explicit QAndroidImageCapture(QImageCapture *parent = nullptr);
+
+ bool isReadyForCapture() const override;
+
+ int capture(const QString &fileName) override;
+ int captureToBuffer() override;
+
+ QImageEncoderSettings imageSettings() const override;
+ void setImageSettings(const QImageEncoderSettings &settings) override;
+
+ void setCaptureSession(QPlatformMediaCaptureSession *session);
+
+private:
+ QAndroidCameraSession *m_session;
+ QAndroidMediaCaptureSession *m_service;
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDCAMERAIMAGECAPTURECONTROL_H
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession.cpp b/src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession.cpp
new file mode 100644
index 000000000..e2b551d35
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession.cpp
@@ -0,0 +1,115 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2016 Ruslan Baratov
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qandroidmediacapturesession_p.h"
+
+#include "qandroidmediaencoder_p.h"
+#include "qandroidcapturesession_p.h"
+#include "qandroidcamera_p.h"
+#include "qandroidcamerasession_p.h"
+#include "qandroidimagecapture_p.h"
+#include "qmediadevices.h"
+#include "qaudiodevice.h"
+
+QT_BEGIN_NAMESPACE
+
+QAndroidMediaCaptureSession::QAndroidMediaCaptureSession()
+ : m_captureSession(new QAndroidCaptureSession())
+ , m_cameraSession(new QAndroidCameraSession())
+{
+}
+
+QAndroidMediaCaptureSession::~QAndroidMediaCaptureSession()
+{
+ delete m_captureSession;
+ delete m_cameraSession;
+}
+
+QPlatformCamera *QAndroidMediaCaptureSession::camera()
+{
+ return m_cameraControl;
+}
+
+void QAndroidMediaCaptureSession::setCamera(QPlatformCamera *camera)
+{
+ if (camera) {
+ m_captureSession->setCameraSession(m_cameraSession);
+ } else {
+ m_captureSession->setCameraSession(nullptr);
+ }
+
+ QAndroidCamera *control = static_cast<QAndroidCamera *>(camera);
+ if (m_cameraControl == control)
+ return;
+
+ if (m_cameraControl)
+ m_cameraControl->setCaptureSession(nullptr);
+
+ m_cameraControl = control;
+ if (m_cameraControl)
+ m_cameraControl->setCaptureSession(this);
+
+ emit cameraChanged();
+}
+
+QPlatformImageCapture *QAndroidMediaCaptureSession::imageCapture()
+{
+ return m_imageCaptureControl;
+}
+
+void QAndroidMediaCaptureSession::setImageCapture(QPlatformImageCapture *imageCapture)
+{
+ QAndroidImageCapture *control = static_cast<QAndroidImageCapture *>(imageCapture);
+ if (m_imageCaptureControl == control)
+ return;
+
+ if (m_imageCaptureControl)
+ m_imageCaptureControl->setCaptureSession(nullptr);
+
+ m_imageCaptureControl = control;
+ if (m_imageCaptureControl)
+ m_imageCaptureControl->setCaptureSession(this);
+}
+
+QPlatformMediaRecorder *QAndroidMediaCaptureSession::mediaRecorder()
+{
+ return m_encoder;
+}
+
+void QAndroidMediaCaptureSession::setMediaRecorder(QPlatformMediaRecorder *recorder)
+{
+ QAndroidMediaEncoder *control = static_cast<QAndroidMediaEncoder *>(recorder);
+
+ if (m_encoder == control)
+ return;
+
+ if (m_encoder)
+ m_encoder->setCaptureSession(nullptr);
+
+ m_encoder = control;
+ if (m_encoder)
+ m_encoder->setCaptureSession(this);
+
+ emit encoderChanged();
+
+}
+
+void QAndroidMediaCaptureSession::setAudioInput(QPlatformAudioInput *input)
+{
+ m_captureSession->setAudioInput(input);
+}
+
+void QAndroidMediaCaptureSession::setAudioOutput(QPlatformAudioOutput *output)
+{
+ m_captureSession->setAudioOutput(output);
+}
+
+void QAndroidMediaCaptureSession::setVideoPreview(QVideoSink *sink)
+{
+ m_cameraSession->setVideoSink(sink);
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qandroidmediacapturesession_p.cpp"
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession_p.h b/src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession_p.h
new file mode 100644
index 000000000..90c792c32
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidmediacapturesession_p.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2016 Ruslan Baratov
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDCAPTURESERVICE_H
+#define QANDROIDCAPTURESERVICE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformmediacapture_p.h>
+#include <private/qplatformmediaintegration_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidMediaEncoder;
+class QAndroidCaptureSession;
+class QAndroidCamera;
+class QAndroidCameraSession;
+class QAndroidImageCapture;
+
+class QAndroidMediaCaptureSession : public QPlatformMediaCaptureSession
+{
+ Q_OBJECT
+
+public:
+ explicit QAndroidMediaCaptureSession();
+ virtual ~QAndroidMediaCaptureSession();
+
+ QPlatformCamera *camera() override;
+ void setCamera(QPlatformCamera *camera) override;
+
+ QPlatformImageCapture *imageCapture() override;
+ void setImageCapture(QPlatformImageCapture *imageCapture) override;
+
+ QPlatformMediaRecorder *mediaRecorder() override;
+ void setMediaRecorder(QPlatformMediaRecorder *recorder) override;
+
+ void setAudioInput(QPlatformAudioInput *input) override;
+
+ void setVideoPreview(QVideoSink *sink) override;
+
+ void setAudioOutput(QPlatformAudioOutput *output) override;
+
+ QAndroidCaptureSession *captureSession() const { return m_captureSession; }
+ QAndroidCameraSession *cameraSession() const { return m_cameraSession; }
+
+private:
+ QAndroidMediaEncoder *m_encoder = nullptr;
+ QAndroidCaptureSession *m_captureSession = nullptr;
+ QAndroidCamera *m_cameraControl = nullptr;
+ QAndroidCameraSession *m_cameraSession = nullptr;
+ QAndroidImageCapture *m_imageCaptureControl = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDCAPTURESERVICE_H
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidmediaencoder.cpp b/src/plugins/multimedia/android/mediacapture/qandroidmediaencoder.cpp
new file mode 100644
index 000000000..d3449312d
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidmediaencoder.cpp
@@ -0,0 +1,72 @@
+// 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 "qandroidmediaencoder_p.h"
+#include "qandroidmultimediautils_p.h"
+#include "qandroidcapturesession_p.h"
+#include "qandroidmediacapturesession_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QAndroidMediaEncoder::QAndroidMediaEncoder(QMediaRecorder *parent)
+ : QPlatformMediaRecorder(parent)
+{
+}
+
+bool QAndroidMediaEncoder::isLocationWritable(const QUrl &location) const
+{
+ return location.isValid()
+ && (location.isLocalFile() || location.isRelative());
+}
+
+QMediaRecorder::RecorderState QAndroidMediaEncoder::state() const
+{
+ return m_session ? m_session->state() : QMediaRecorder::StoppedState;
+}
+
+qint64 QAndroidMediaEncoder::duration() const
+{
+ return m_session ? m_session->duration() : 0;
+
+}
+
+void QAndroidMediaEncoder::record(QMediaEncoderSettings &settings)
+{
+ if (m_session)
+ m_session->start(settings, outputLocation());
+}
+
+void QAndroidMediaEncoder::stop()
+{
+ if (m_session)
+ m_session->stop();
+}
+
+void QAndroidMediaEncoder::setOutputLocation(const QUrl &location)
+{
+ if (location.isLocalFile()) {
+ qt_androidRequestWriteStoragePermission();
+ }
+ QPlatformMediaRecorder::setOutputLocation(location);
+}
+
+void QAndroidMediaEncoder::setCaptureSession(QPlatformMediaCaptureSession *session)
+{
+ QAndroidMediaCaptureSession *captureSession = static_cast<QAndroidMediaCaptureSession *>(session);
+ if (m_service == captureSession)
+ return;
+
+ if (m_service)
+ stop();
+ if (m_session)
+ m_session->setMediaEncoder(nullptr);
+
+ m_service = captureSession;
+ if (!m_service)
+ return;
+ m_session = m_service->captureSession();
+ Q_ASSERT(m_session);
+ m_session->setMediaEncoder(this);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/multimedia/android/mediacapture/qandroidmediaencoder_p.h b/src/plugins/multimedia/android/mediacapture/qandroidmediaencoder_p.h
new file mode 100644
index 000000000..b46268449
--- /dev/null
+++ b/src/plugins/multimedia/android/mediacapture/qandroidmediaencoder_p.h
@@ -0,0 +1,50 @@
+// 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
+
+#ifndef QANDROIDMEDIAENCODER_H
+#define QANDROIDMEDIAENCODER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qplatformmediarecorder_p.h>
+#include <private/qplatformmediacapture_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidCaptureSession;
+class QAndroidMediaCaptureSession;
+
+class QAndroidMediaEncoder : public QPlatformMediaRecorder
+{
+public:
+ explicit QAndroidMediaEncoder(QMediaRecorder *parent);
+
+ bool isLocationWritable(const QUrl &location) const override;
+ QMediaRecorder::RecorderState state() const override;
+ qint64 duration() const override;
+
+ void setCaptureSession(QPlatformMediaCaptureSession *session);
+
+ void setOutputLocation(const QUrl &location) override;
+ void record(QMediaEncoderSettings &settings) override;
+ void stop() override;
+
+private:
+ friend class QAndroidCaptureSession;
+
+ QAndroidCaptureSession *m_session = nullptr;
+ QAndroidMediaCaptureSession *m_service = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDMEDIAENCODER_H