diff options
Diffstat (limited to 'src/plugins/android/src/mediacapture/qandroidcamerasession.cpp')
-rw-r--r-- | src/plugins/android/src/mediacapture/qandroidcamerasession.cpp | 553 |
1 files changed, 553 insertions, 0 deletions
diff --git a/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp b/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp new file mode 100644 index 000000000..761b716d1 --- /dev/null +++ b/src/plugins/android/src/mediacapture/qandroidcamerasession.cpp @@ -0,0 +1,553 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qandroidcamerasession.h" + +#include "jcamera.h" +#include "jmultimediautils.h" +#include "qandroidvideooutput.h" +#include "qandroidmultimediautils.h" +#include <QtConcurrent/qtconcurrentrun.h> +#include <qfile.h> +#include <qguiapplication.h> +#include <qdebug.h> + +QT_BEGIN_NAMESPACE + +static void textureReadyCallback(void *context) +{ + if (context) + reinterpret_cast<QAndroidCameraSession *>(context)->onSurfaceTextureReady(); +} + +QAndroidCameraSession::QAndroidCameraSession(QObject *parent) + : QObject(parent) + , m_selectedCamera(0) + , m_camera(0) + , m_nativeOrientation(0) + , m_videoOutput(0) + , m_captureMode(QCamera::CaptureViewfinder) + , m_state(QCamera::UnloadedState) + , m_savedState(-1) + , m_status(QCamera::UnloadedStatus) + , m_previewStarted(false) + , m_imageSettingsDirty(true) + , m_captureDestination(QCameraImageCapture::CaptureToFile) + , m_captureImageDriveMode(QCameraImageCapture::SingleImageCapture) + , m_lastImageCaptureId(0) + , m_readyForCapture(false) + , m_captureCanceled(false) + , m_currentImageCaptureId(-1) +{ + if (qApp) { + connect(qApp, SIGNAL(applicationStateChanged(Qt::ApplicationState)), + this, SLOT(onApplicationStateChanged(Qt::ApplicationState))); + } +} + +QAndroidCameraSession::~QAndroidCameraSession() +{ + 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)) + adjustViewfinderSize(m_imageSettings.resolution()); +} + +bool QAndroidCameraSession::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + if (mode & (QCamera::CaptureStillImage & QCamera::CaptureVideo)) + return false; + + return true; +} + +void QAndroidCameraSession::setState(QCamera::State state) +{ + // 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 (qApp->applicationState() != Qt::ApplicationActive) { + m_savedState = state; + return; + } + + if (m_state == state) + return; + + switch (state) { + case QCamera::UnloadedState: + close(); + break; + case QCamera::LoadedState: + case QCamera::ActiveState: + if (!m_camera && !open()) { + emit error(QCamera::CameraError, QStringLiteral("Failed to open camera")); + return; + } + if (state == QCamera::ActiveState) + startPreview(); + else if (state == QCamera::LoadedState) + stopPreview(); + break; + } + + m_state = state; + emit stateChanged(m_state); +} + +bool QAndroidCameraSession::open() +{ + close(); + + m_status = QCamera::LoadingStatus; + emit statusChanged(m_status); + + m_camera = JCamera::open(m_selectedCamera); + + if (m_camera) { + connect(m_camera, SIGNAL(pictureExposed()), this, SLOT(onCameraPictureExposed())); + connect(m_camera, SIGNAL(pictureCaptured(QByteArray)), this, SLOT(onCameraPictureCaptured(QByteArray))); + m_nativeOrientation = m_camera->getNativeOrientation(); + m_status = QCamera::LoadedStatus; + emit opened(); + } else { + m_status = QCamera::UnavailableStatus; + } + + emit statusChanged(m_status); + + return m_camera != 0; +} + +void QAndroidCameraSession::close() +{ + if (!m_camera) + return; + + stopPreview(); + + m_status = QCamera::UnloadingStatus; + emit statusChanged(m_status); + + m_readyForCapture = false; + m_currentImageCaptureId = -1; + m_currentImageCaptureFileName.clear(); + m_imageSettingsDirty = true; + + m_camera->release(); + delete m_camera; + m_camera = 0; + + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); +} + +void QAndroidCameraSession::setVideoPreview(QAndroidVideoOutput *videoOutput) +{ + if (m_videoOutput) + m_videoOutput->stop(); + + m_videoOutput = videoOutput; +} + +void QAndroidCameraSession::adjustViewfinderSize(const QSize &captureSize, bool restartPreview) +{ + if (!m_camera) + return; + + QSize viewfinderResolution = m_camera->previewSize(); + const qreal aspectRatio = qreal(captureSize.width()) / qreal(captureSize.height()); + if (qFuzzyCompare(aspectRatio, qreal(viewfinderResolution.width()) / qreal(viewfinderResolution.height()))) + return; + + QList<QSize> previewSizes = m_camera->getSupportedPreviewSizes(); + for (int i = previewSizes.count() - 1; i >= 0; --i) { + const QSize &size = previewSizes.at(i); + // search for viewfinder resolution with the same aspect ratio + if (qFuzzyCompare(aspectRatio, (static_cast<qreal>(size.width())/static_cast<qreal>(size.height())))) { + viewfinderResolution = size; + break; + } + } + + if (m_camera->previewSize() != viewfinderResolution) { + if (m_videoOutput) + m_videoOutput->setVideoSize(viewfinderResolution); + + // if preview is started, we have to stop it first before changing its size + if (m_previewStarted && restartPreview) + m_camera->stopPreview(); + + m_camera->setPreviewSize(viewfinderResolution); + + // restart preview + if (m_previewStarted && restartPreview) + m_camera->startPreview(); + } +} + +void QAndroidCameraSession::startPreview() +{ + if (!m_camera || m_previewStarted) + return; + + m_status = QCamera::StartingStatus; + emit statusChanged(m_status); + + applyImageSettings(); + adjustViewfinderSize(m_imageSettings.resolution()); + + if (m_videoOutput) { + if (m_videoOutput->isTextureReady()) + m_camera->setPreviewTexture(m_videoOutput->surfaceTexture()); + else + m_videoOutput->setTextureReadyCallback(textureReadyCallback, this); + } + + JMultimediaUtils::enableOrientationListener(true); + + m_camera->startPreview(); + m_previewStarted = true; + + m_status = QCamera::ActiveStatus; + emit statusChanged(m_status); + + setReadyForCapture(true); +} + +void QAndroidCameraSession::stopPreview() +{ + if (!m_camera || !m_previewStarted) + return; + + m_status = QCamera::StoppingStatus; + emit statusChanged(m_status); + + JMultimediaUtils::enableOrientationListener(false); + + m_camera->stopPreview(); + if (m_videoOutput) + m_videoOutput->stop(); + m_previewStarted = false; + + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + + setReadyForCapture(false); +} + +void QAndroidCameraSession::setImageSettings(const QImageEncoderSettings &settings) +{ + if (m_imageSettings == settings) + return; + + m_imageSettings = settings; + if (m_imageSettings.codec().isEmpty()) + m_imageSettings.setCodec(QLatin1String("jpeg")); + + m_imageSettingsDirty = true; + + applyImageSettings(); + + if (m_readyForCapture && m_captureMode.testFlag(QCamera::CaptureStillImage)) + adjustViewfinderSize(m_imageSettings.resolution()); +} + +int QAndroidCameraSession::currentCameraRotation() const +{ + if (!m_camera) + return 0; + + // subtract natural camera orientation and physical device orientation + int rotation = 0; + int deviceOrientation = (JMultimediaUtils::getDeviceOrientation() + 45) / 90 * 90; + if (m_camera->getFacing() == JCamera::CameraFacingFront) + rotation = (m_nativeOrientation - deviceOrientation + 360) % 360; + else // back-facing camera + rotation = (m_nativeOrientation + deviceOrientation) % 360; + + return rotation; +} + +void QAndroidCameraSession::applyImageSettings() +{ + if (!m_camera || !m_imageSettingsDirty) + return; + + const QSize requestedResolution = m_imageSettings.resolution(); + const QList<QSize> supportedResolutions = m_camera->getSupportedPictureSizes(); + + if (!requestedResolution.isValid()) { + // if no resolution is set, use the highest supported one + m_imageSettings.setResolution(supportedResolutions.last()); + } 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_imageSettings.setResolution(supportedResolutions.at(closestIndex)); + } + + int jpegQuality = 100; + switch (m_imageSettings.quality()) { + case QMultimedia::VeryLowQuality: + jpegQuality = 20; + break; + case QMultimedia::LowQuality: + jpegQuality = 40; + break; + case QMultimedia::NormalQuality: + jpegQuality = 60; + break; + case QMultimedia::HighQuality: + jpegQuality = 80; + break; + case QMultimedia::VeryHighQuality: + jpegQuality = 100; + break; + } + + m_camera->setPictureSize(m_imageSettings.resolution()); + m_camera->setJpegQuality(jpegQuality); + + m_imageSettingsDirty = false; +} + +bool QAndroidCameraSession::isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const +{ + return destination & (QCameraImageCapture::CaptureToFile | QCameraImageCapture::CaptureToBuffer); +} + +QCameraImageCapture::CaptureDestinations QAndroidCameraSession::captureDestination() const +{ + return m_captureDestination; +} + +void QAndroidCameraSession::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + if (m_captureDestination != destination) { + m_captureDestination = destination; + emit captureDestinationChanged(m_captureDestination); + } +} + +bool QAndroidCameraSession::isReadyForCapture() const +{ + return m_status == QCamera::ActiveStatus && m_readyForCapture; +} + +void QAndroidCameraSession::setReadyForCapture(bool ready) +{ + if (m_readyForCapture == ready) + return; + + m_readyForCapture = ready; + emit readyForCaptureChanged(ready); +} + +QCameraImageCapture::DriveMode QAndroidCameraSession::driveMode() const +{ + return m_captureImageDriveMode; +} + +void QAndroidCameraSession::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + m_captureImageDriveMode = mode; +} + +int QAndroidCameraSession::capture(const QString &fileName) +{ + ++m_lastImageCaptureId; + + if (!isReadyForCapture()) { + emit imageCaptureError(m_lastImageCaptureId, QCameraImageCapture::NotReadyError, + tr("Camera not ready")); + return m_lastImageCaptureId; + } + + if (m_captureImageDriveMode == QCameraImageCapture::SingleImageCapture) { + setReadyForCapture(false); + + m_currentImageCaptureId = m_lastImageCaptureId; + m_currentImageCaptureFileName = fileName; + + applyImageSettings(); + adjustViewfinderSize(m_imageSettings.resolution()); + + // adjust picture rotation depending on the device orientation + m_camera->setRotation(currentCameraRotation()); + + m_camera->takePicture(); + } else { + emit imageCaptureError(m_lastImageCaptureId, QCameraImageCapture::NotSupportedFeatureError, + tr("Drive mode not supported")); + } + + return m_lastImageCaptureId; +} + +void QAndroidCameraSession::cancelCapture() +{ + if (m_readyForCapture) + return; + + m_captureCanceled = true; +} + +void QAndroidCameraSession::onCameraPictureExposed() +{ + if (m_captureCanceled) + return; + + emit imageExposed(m_currentImageCaptureId); +} + +void QAndroidCameraSession::onCameraPictureCaptured(const QByteArray &data) +{ + if (!m_captureCanceled) { + // generate a preview from the viewport + if (m_videoOutput) + emit imageCaptured(m_currentImageCaptureId, m_videoOutput->toImage()); + + // Loading and saving the captured image can be slow, do it in a separate thread + QtConcurrent::run(this, &QAndroidCameraSession::processCapturedImage, + m_currentImageCaptureId, + data, + m_captureDestination, + m_currentImageCaptureFileName); + } + + m_captureCanceled = false; + + // Preview needs to be restarted after taking a picture + m_camera->startPreview(); + + setReadyForCapture(true); +} + +void QAndroidCameraSession::processCapturedImage(int id, + const QByteArray &data, + QCameraImageCapture::CaptureDestinations dest, + const QString &fileName) +{ + + + if (dest & QCameraImageCapture::CaptureToFile) { + const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName, + QAndroidMediaStorageLocation::Camera, + QLatin1String("IMG_"), + QLatin1String("jpg")); + + QFile file(actualFileName); + if (file.open(QFile::WriteOnly)) { + if (file.write(data) == data.size()) { + // if the picture is saved into the standard picture location, register it + // with the Android media scanner so it appears immediately in apps + // such as the gallery. + QString standardLoc = JMultimediaUtils::getDefaultMediaDirectory(JMultimediaUtils::DCIM); + if (actualFileName.startsWith(standardLoc)) + JMultimediaUtils::registerMediaFile(actualFileName); + + emit imageSaved(id, actualFileName); + } else { + emit imageCaptureError(id, QCameraImageCapture::OutOfSpaceError, file.errorString()); + } + } else { + const QString errorMessage = tr("Could not open destination file: %1").arg(actualFileName); + emit imageCaptureError(id, QCameraImageCapture::ResourceError, errorMessage); + } + } + + if (dest & QCameraImageCapture::CaptureToBuffer) { + QImage image; + const bool ok = image.loadFromData(data, "JPG"); + + if (ok) { + QVideoFrame frame(image); + emit imageAvailable(id, frame); + } else { + emit imageCaptureError(id, QCameraImageCapture::FormatError, + tr("Could not load JPEG data from captured image")); + } + } +} + +void QAndroidCameraSession::onSurfaceTextureReady() +{ + if (m_camera && m_videoOutput) + m_camera->setPreviewTexture(m_videoOutput->surfaceTexture()); +} + +void QAndroidCameraSession::onApplicationStateChanged(Qt::ApplicationState state) +{ + switch (state) { + case Qt::ApplicationInactive: + if (m_state != QCamera::UnloadedState) { + m_savedState = m_state; + close(); + m_state = QCamera::UnloadedState; + emit stateChanged(m_state); + } + break; + case Qt::ApplicationActive: + if (m_savedState != -1) { + setState(QCamera::State(m_savedState)); + m_savedState = -1; + } + break; + default: + break; + } +} + +QT_END_NAMESPACE |