diff options
author | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2013-11-20 16:43:44 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2013-11-20 16:43:45 +0100 |
commit | 0c8be637087a3c7aa038cf72f3a3ed6f7aca0510 (patch) | |
tree | 9d55f94a9bcb70b60a5f73439fb1c308cea392ca /src/plugins/qnx/camera/bbcamerasession.cpp | |
parent | f15105a02dc1c6b2b95b8ab0dafd36098840fb0e (diff) | |
parent | 2c7e734c8b4decc89820cc946ff72c89aee5cde5 (diff) | |
download | qtmultimedia-0c8be637087a3c7aa038cf72f3a3ed6f7aca0510.tar.gz |
Merge remote-tracking branch 'origin/stable' into dev
Change-Id: I25197ccc930730be363f8f65624d7fa10c1d33e9
Diffstat (limited to 'src/plugins/qnx/camera/bbcamerasession.cpp')
-rw-r--r-- | src/plugins/qnx/camera/bbcamerasession.cpp | 1164 |
1 files changed, 1164 insertions, 0 deletions
diff --git a/src/plugins/qnx/camera/bbcamerasession.cpp b/src/plugins/qnx/camera/bbcamerasession.cpp new file mode 100644 index 000000000..59db66f4a --- /dev/null +++ b/src/plugins/qnx/camera/bbcamerasession.cpp @@ -0,0 +1,1164 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Research In Motion +** 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 "bbcamerasession.h" + +#include "bbcameraorientationhandler.h" +#include "bbcameraviewfindersettingscontrol.h" +#include "windowgrabber.h" + +#include <QAbstractVideoSurface> +#include <QBuffer> +#include <QDebug> +#include <QImage> +#include <QUrl> +#include <QVideoSurfaceFormat> +#include <qmath.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +static QString errorToString(camera_error_t error) +{ + switch (error) { + case CAMERA_EOK: + return QLatin1String("No error"); + case CAMERA_EAGAIN: + return QLatin1String("Camera unavailable"); + case CAMERA_EINVAL: + return QLatin1String("Inavlid argument"); + case CAMERA_ENODEV: + return QLatin1String("Camera not found"); + case CAMERA_EMFILE: + return QLatin1String("File table overflow"); + case CAMERA_EBADF: + return QLatin1String("Invalid handle passed"); + case CAMERA_EACCESS: + return QLatin1String("No permission"); + case CAMERA_EBADR: + return QLatin1String("Invalid file descriptor"); + case CAMERA_ENOENT: + return QLatin1String("File or directory does not exists"); + case CAMERA_ENOMEM: + return QLatin1String("Memory allocation failed"); + case CAMERA_EOPNOTSUPP: + return QLatin1String("Operation not supported"); + case CAMERA_ETIMEDOUT: + return QLatin1String("Communication timeout"); + case CAMERA_EALREADY: + return QLatin1String("Operation already in progress"); + case CAMERA_EUNINIT: + return QLatin1String("Camera library not initialized"); + case CAMERA_EREGFAULT: + return QLatin1String("Callback registration failed"); + case CAMERA_EMICINUSE: + return QLatin1String("Microphone in use already"); +#ifndef Q_OS_BLACKBERRY_TABLET + case CAMERA_ENODATA: + return QLatin1String("Data does not exist"); + case CAMERA_EBUSY: + return QLatin1String("Camera busy"); + case CAMERA_EDESKTOPCAMERAINUSE: + return QLatin1String("Desktop camera in use already"); + case CAMERA_ENOSPC: + return QLatin1String("Disk is full"); + case CAMERA_EPOWERDOWN: + return QLatin1String("Camera in power down state"); + case CAMERA_3ALOCKED: + return QLatin1String("3A have been locked"); +// case CAMERA_EVIEWFINDERFROZEN: // not yet available in 10.2 NDK +// return QLatin1String("Freeze flag set"); +#endif + default: + return QLatin1String("Unknown error"); + } +} + +QDebug operator<<(QDebug debug, camera_error_t error) +{ + debug.nospace() << errorToString(error); + return debug.space(); +} + +BbCameraSession::BbCameraSession(QObject *parent) + : QObject(parent) + , m_nativeCameraOrientation(0) + , m_orientationHandler(new BbCameraOrientationHandler(this)) + , m_status(QCamera::UnloadedStatus) + , m_state(QCamera::UnloadedState) + , m_captureMode(QCamera::CaptureStillImage) + , m_device("bb:RearCamera") + , m_previewIsVideo(true) + , m_surface(0) + , m_captureImageDriveMode(QCameraImageCapture::SingleImageCapture) + , m_lastImageCaptureId(0) + , m_captureDestination(QCameraImageCapture::CaptureToFile) + , m_videoState(QMediaRecorder::StoppedState) + , m_videoStatus(QMediaRecorder::LoadedStatus) + , m_handle(CAMERA_HANDLE_INVALID) + , m_windowGrabber(new WindowGrabber(this)) +{ + connect(this, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyForCapture())); + connect(this, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateReadyForCapture())); + connect(m_orientationHandler, SIGNAL(orientationChanged(int)), SLOT(deviceOrientationChanged(int))); + + connect(m_windowGrabber, SIGNAL(frameGrabbed(QImage)), SLOT(viewfinderFrameGrabbed(QImage))); +} + +BbCameraSession::~BbCameraSession() +{ + stopViewFinder(); + closeCamera(); +} + +camera_handle_t BbCameraSession::handle() const +{ + return m_handle; +} + +QCamera::State BbCameraSession::state() const +{ + return m_state; +} + +void BbCameraSession::setState(QCamera::State state) +{ + if (m_state == state) + return; + + const QCamera::State previousState = m_state; + + if (previousState == QCamera::UnloadedState) { + if (state == QCamera::LoadedState) { + if (openCamera()) { + m_state = state; + } + } else if (state == QCamera::ActiveState) { + if (openCamera()) { + QMetaObject::invokeMethod(this, "applyConfiguration", Qt::QueuedConnection); + m_state = state; + } + } + } else if (previousState == QCamera::LoadedState) { + if (state == QCamera::UnloadedState) { + closeCamera(); + m_state = state; + } else if (state == QCamera::ActiveState) { + QMetaObject::invokeMethod(this, "applyConfiguration", Qt::QueuedConnection); + m_state = state; + } + } else if (previousState == QCamera::ActiveState) { + if (state == QCamera::LoadedState) { + stopViewFinder(); + m_state = state; + } else if (state == QCamera::UnloadedState) { + stopViewFinder(); + closeCamera(); + m_state = state; + } + } + + if (m_state != previousState) + emit stateChanged(m_state); +} + +QCamera::Status BbCameraSession::status() const +{ + return m_status; +} + +QCamera::CaptureModes BbCameraSession::captureMode() const +{ + return m_captureMode; +} + +void BbCameraSession::setCaptureMode(QCamera::CaptureModes captureMode) +{ + if (m_captureMode == captureMode) + return; + + m_captureMode = captureMode; + emit captureModeChanged(m_captureMode); +} + +bool BbCameraSession::isCaptureModeSupported(QCamera::CaptureModes mode) const +{ + if (m_handle == CAMERA_HANDLE_INVALID) { + // the camera has not been loaded yet via QCamera::load(), so + // we open it temporarily to peek for the supported capture modes + + camera_unit_t unit = CAMERA_UNIT_REAR; + if (m_device == cameraIdentifierFront()) + unit = CAMERA_UNIT_FRONT; + else if (m_device == cameraIdentifierRear()) + unit = CAMERA_UNIT_REAR; + else if (m_device == cameraIdentifierDesktop()) + unit = CAMERA_UNIT_DESKTOP; + + camera_handle_t handle; + const camera_error_t result = camera_open(unit, CAMERA_MODE_RW, &handle); + if (result != CAMERA_EOK) + return true; + + const bool supported = isCaptureModeSupported(handle, mode); + + camera_close(handle); + + return supported; + } else { + return isCaptureModeSupported(m_handle, mode); + } +} + +QByteArray BbCameraSession::cameraIdentifierFront() +{ + return "bb:FrontCamera"; +} + +QByteArray BbCameraSession::cameraIdentifierRear() +{ + return "bb:RearCamera"; +} + +QByteArray BbCameraSession::cameraIdentifierDesktop() +{ + return "bb:DesktopCamera"; +} + +void BbCameraSession::setDevice(const QByteArray &device) +{ + m_device = device; +} + +QByteArray BbCameraSession::device() const +{ + return m_device; +} + +QAbstractVideoSurface* BbCameraSession::surface() const +{ + return m_surface; +} + +void BbCameraSession::setSurface(QAbstractVideoSurface *surface) +{ + QMutexLocker locker(&m_surfaceMutex); + + if (m_surface == surface) + return; + + m_surface = surface; +} + +bool BbCameraSession::isReadyForCapture() const +{ + if (m_captureMode & QCamera::CaptureStillImage) + return (m_status == QCamera::ActiveStatus); + + if (m_captureMode & QCamera::CaptureVideo) + return (m_status == QCamera::ActiveStatus); + + return false; +} + +QCameraImageCapture::DriveMode BbCameraSession::driveMode() const +{ + return m_captureImageDriveMode; +} + +void BbCameraSession::setDriveMode(QCameraImageCapture::DriveMode mode) +{ + m_captureImageDriveMode = mode; +} + +/** + * A helper structure that keeps context data for image capture callbacks. + */ +struct ImageCaptureData +{ + int requestId; + QString fileName; + BbCameraSession *session; +}; + +static void imageCaptureShutterCallback(camera_handle_t handle, void *context) +{ + Q_UNUSED(handle) + + const ImageCaptureData *data = static_cast<ImageCaptureData*>(context); + + // We are inside a worker thread here, so emit imageExposed inside the main thread + QMetaObject::invokeMethod(data->session, "imageExposed", Qt::QueuedConnection, + Q_ARG(int, data->requestId)); +} + +static void imageCaptureImageCallback(camera_handle_t handle, camera_buffer_t *buffer, void *context) +{ + Q_UNUSED(handle) + + QScopedPointer<ImageCaptureData> data(static_cast<ImageCaptureData*>(context)); + + if (buffer->frametype != CAMERA_FRAMETYPE_JPEG) { + // We are inside a worker thread here, so emit error signal inside the main thread + QMetaObject::invokeMethod(data->session, "imageCaptureError", Qt::QueuedConnection, + Q_ARG(int, data->requestId), + Q_ARG(QCameraImageCapture::Error, QCameraImageCapture::FormatError), + Q_ARG(QString, BbCameraSession::tr("Camera provides image in unsupported format"))); + return; + } + + const QByteArray rawData((const char*)buffer->framebuf, buffer->framedesc.jpeg.bufsize); + + QImage image; + const bool ok = image.loadFromData(rawData, "JPG"); + if (!ok) { + const QString errorMessage = BbCameraSession::tr("Could not load JPEG data from frame"); + // We are inside a worker thread here, so emit error signal inside the main thread + QMetaObject::invokeMethod(data->session, "imageCaptureError", Qt::QueuedConnection, + Q_ARG(int, data->requestId), + Q_ARG(QCameraImageCapture::Error, QCameraImageCapture::FormatError), + Q_ARG(QString, errorMessage)); + return; + } + + + // We are inside a worker thread here, so invoke imageCaptured inside the main thread + QMetaObject::invokeMethod(data->session, "imageCaptured", Qt::QueuedConnection, + Q_ARG(int, data->requestId), + Q_ARG(QImage, image), + Q_ARG(QString, data->fileName)); +} + +int BbCameraSession::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) { + // prepare context object for callback + ImageCaptureData *context = new ImageCaptureData; + context->requestId = m_lastImageCaptureId; + context->fileName = fileName; + context->session = this; + + const camera_error_t result = camera_take_photo(m_handle, + imageCaptureShutterCallback, + 0, + 0, + imageCaptureImageCallback, + context, false); + + if (result != CAMERA_EOK) + qWarning() << "Unable to take photo:" << result; + } else { + // TODO: implement burst mode when available in Qt API + } + + return m_lastImageCaptureId; +} + +void BbCameraSession::cancelCapture() +{ + // BB10 API doesn't provide functionality for that +} + +bool BbCameraSession::isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const +{ + // capture to buffer, file and both are supported. + return destination & (QCameraImageCapture::CaptureToFile | QCameraImageCapture::CaptureToBuffer); +} + +QCameraImageCapture::CaptureDestinations BbCameraSession::captureDestination() const +{ + return m_captureDestination; +} + +void BbCameraSession::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination) +{ + if (m_captureDestination != destination) { + m_captureDestination = destination; + emit captureDestinationChanged(m_captureDestination); + } +} + +QList<QSize> BbCameraSession::supportedResolutions(const QImageEncoderSettings&, bool *continuous) const +{ + if (continuous) + *continuous = false; + + if (m_status == QCamera::UnloadedStatus) + return QList<QSize>(); + + if (m_captureMode & QCamera::CaptureStillImage) { + return supportedResolutions(QCamera::CaptureStillImage); + } else if (m_captureMode & QCamera::CaptureVideo) { + return supportedResolutions(QCamera::CaptureVideo); + } + + return QList<QSize>(); +} + +QImageEncoderSettings BbCameraSession::imageSettings() const +{ + return m_imageEncoderSettings; +} + +void BbCameraSession::setImageSettings(const QImageEncoderSettings &settings) +{ + m_imageEncoderSettings = settings; + if (m_imageEncoderSettings.codec().isEmpty()) + m_imageEncoderSettings.setCodec(QLatin1String("jpeg")); +} + +QUrl BbCameraSession::outputLocation() const +{ + return QUrl::fromLocalFile(m_videoOutputLocation); +} + +bool BbCameraSession::setOutputLocation(const QUrl &location) +{ + m_videoOutputLocation = location.toLocalFile(); + + return true; +} + +QMediaRecorder::State BbCameraSession::videoState() const +{ + return m_videoState; +} + +void BbCameraSession::setVideoState(QMediaRecorder::State state) +{ + if (m_videoState == state) + return; + + const QMediaRecorder::State previousState = m_videoState; + + if (previousState == QMediaRecorder::StoppedState) { + if (state == QMediaRecorder::RecordingState) { + if (startVideoRecording()) { + m_videoState = state; + } + } else if (state == QMediaRecorder::PausedState) { + // do nothing + } + } else if (previousState == QMediaRecorder::RecordingState) { + if (state == QMediaRecorder::StoppedState) { + stopVideoRecording(); + m_videoState = state; + } else if (state == QMediaRecorder::PausedState) { + //TODO: (pause) not supported by BB10 API yet + } + } else if (previousState == QMediaRecorder::PausedState) { + if (state == QMediaRecorder::StoppedState) { + stopVideoRecording(); + m_videoState = state; + } else if (state == QMediaRecorder::RecordingState) { + //TODO: (resume) not supported by BB10 API yet + } + } + + emit videoStateChanged(m_videoState); +} + +QMediaRecorder::Status BbCameraSession::videoStatus() const +{ + return m_videoStatus; +} + +qint64 BbCameraSession::duration() const +{ + return (m_videoRecordingDuration.isValid() ? m_videoRecordingDuration.elapsed() : 0); +} + +void BbCameraSession::applyVideoSettings() +{ + if (m_handle == CAMERA_HANDLE_INVALID) + return; + + // apply viewfinder configuration + const QList<QSize> videoOutputResolutions = supportedResolutions(QCamera::CaptureVideo); + + if (!m_videoEncoderSettings.resolution().isValid() || !videoOutputResolutions.contains(m_videoEncoderSettings.resolution())) + m_videoEncoderSettings.setResolution(videoOutputResolutions.first()); + + QSize viewfinderResolution; + + if (m_previewIsVideo) { + // The viewfinder is responsible for encoding the video frames, so the resolutions must match. + viewfinderResolution = m_videoEncoderSettings.resolution(); + } else { + // The frames are encoded separately from the viewfinder, so only the aspect ratio must match. + const QSize videoResolution = m_videoEncoderSettings.resolution(); + const qreal aspectRatio = static_cast<qreal>(videoResolution.width())/static_cast<qreal>(videoResolution.height()); + + QList<QSize> sizes = supportedViewfinderResolutions(QCamera::CaptureVideo); + std::reverse(sizes.begin(), sizes.end()); // use smallest possible resolution + foreach (const QSize &size, sizes) { + // 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; + } + } + } + + Q_ASSERT(viewfinderResolution.isValid()); + + const QByteArray windowId = QString().sprintf("qcamera_vf_%p", this).toLatin1(); + m_windowGrabber->setWindowId(windowId); + + const QByteArray windowGroupId = m_windowGrabber->windowGroupId(); + + const int rotationAngle = (360 - m_nativeCameraOrientation); + + camera_error_t result = CAMERA_EOK; + result = camera_set_videovf_property(m_handle, + CAMERA_IMGPROP_WIN_GROUPID, windowGroupId.data(), + CAMERA_IMGPROP_WIN_ID, windowId.data(), + CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(), + CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(), + CAMERA_IMGPROP_ROTATION, rotationAngle); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply video viewfinder settings:" << result; + return; + } + + const QSize resolution = m_videoEncoderSettings.resolution(); + +#ifndef Q_OS_BLACKBERRY_TABLET + QString videoCodec = m_videoEncoderSettings.codec(); + if (videoCodec.isEmpty()) + videoCodec = QLatin1String("h264"); + + camera_videocodec_t cameraVideoCodec = CAMERA_VIDEOCODEC_H264; + if (videoCodec == QLatin1String("none")) + cameraVideoCodec = CAMERA_VIDEOCODEC_NONE; + else if (videoCodec == QLatin1String("avc1")) + cameraVideoCodec = CAMERA_VIDEOCODEC_AVC1; + else if (videoCodec == QLatin1String("h264")) + cameraVideoCodec = CAMERA_VIDEOCODEC_H264; + + qreal frameRate = m_videoEncoderSettings.frameRate(); + if (frameRate == 0) { + const QList<qreal> frameRates = supportedFrameRates(QVideoEncoderSettings(), 0); + if (!frameRates.isEmpty()) + frameRate = frameRates.last(); + } + + QString audioCodec = m_audioEncoderSettings.codec(); + if (audioCodec.isEmpty()) + audioCodec = QLatin1String("aac"); + + camera_audiocodec_t cameraAudioCodec = CAMERA_AUDIOCODEC_AAC; + if (audioCodec == QLatin1String("none")) + cameraAudioCodec = CAMERA_AUDIOCODEC_NONE; + else if (audioCodec == QLatin1String("aac")) + cameraAudioCodec = CAMERA_AUDIOCODEC_AAC; + else if (audioCodec == QLatin1String("raw")) + cameraAudioCodec = CAMERA_AUDIOCODEC_RAW; + + result = camera_set_video_property(m_handle, + CAMERA_IMGPROP_WIDTH, resolution.width(), + CAMERA_IMGPROP_HEIGHT, resolution.height(), + CAMERA_IMGPROP_ROTATION, rotationAngle, + CAMERA_IMGPROP_VIDEOCODEC, cameraVideoCodec, + CAMERA_IMGPROP_AUDIOCODEC, cameraAudioCodec); +#else + result = camera_set_video_property(m_handle, + CAMERA_IMGPROP_WIDTH, resolution.width(), + CAMERA_IMGPROP_HEIGHT, resolution.height()); +#endif + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply video settings:" << result; + emit videoError(QMediaRecorder::ResourceError, tr("Unable to apply video settings")); + } +} + +QList<QSize> BbCameraSession::supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + + if (continuous) + *continuous = false; + + return supportedResolutions(QCamera::CaptureVideo); +} + +QList<qreal> BbCameraSession::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const +{ + Q_UNUSED(settings); + + if (m_handle == CAMERA_HANDLE_INVALID) + return QList<qreal>(); + + int supported = 0; + double rates[20]; + bool maxmin = false; + + /** + * Since in current version of the BB10 platform the video viewfinder encodes the video frames, we use + * the values as returned by camera_get_video_vf_framerates(). + */ + const camera_error_t result = camera_get_video_vf_framerates(m_handle, 20, &supported, rates, &maxmin); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve supported viewfinder framerates:" << result; + return QList<qreal>(); + } + + QList<qreal> frameRates; + for (int i = 0; i < supported; ++i) + frameRates << rates[i]; + + if (continuous) + *continuous = maxmin; + + return frameRates; +} + +QVideoEncoderSettings BbCameraSession::videoSettings() const +{ + return m_videoEncoderSettings; +} + +void BbCameraSession::setVideoSettings(const QVideoEncoderSettings &settings) +{ + m_videoEncoderSettings = settings; +} + +QAudioEncoderSettings BbCameraSession::audioSettings() const +{ + return m_audioEncoderSettings; +} + +void BbCameraSession::setAudioSettings(const QAudioEncoderSettings &settings) +{ + m_audioEncoderSettings = settings; +} + +void BbCameraSession::updateReadyForCapture() +{ + emit readyForCaptureChanged(isReadyForCapture()); +} + +void BbCameraSession::imageCaptured(int requestId, const QImage &rawImage, const QString &fileName) +{ + QTransform transform; + + // subtract out the native rotation + transform.rotate(m_nativeCameraOrientation); + + // subtract out the current device orientation + if (m_device == cameraIdentifierRear()) + transform.rotate(360 - m_orientationHandler->orientation()); + else + transform.rotate(m_orientationHandler->orientation()); + + const QImage image = rawImage.transformed(transform); + + // Generate snap preview as downscaled image + { + QSize previewSize = image.size(); + int downScaleSteps = 0; + while (previewSize.width() > 800 && downScaleSteps < 8) { + previewSize.rwidth() /= 2; + previewSize.rheight() /= 2; + downScaleSteps++; + } + + const QImage snapPreview = image.scaled(previewSize); + + emit imageCaptured(requestId, snapPreview); + } + + if (m_captureDestination & QCameraImageCapture::CaptureToBuffer) { + QVideoFrame frame(image); + + emit imageAvailable(requestId, frame); + } + + if (m_captureDestination & QCameraImageCapture::CaptureToFile) { + const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName, + QCamera::CaptureStillImage, + QLatin1String("IMG_"), + QLatin1String("jpg")); + + QFile file(actualFileName); + if (file.open(QFile::WriteOnly)) { + if (image.save(&file, "JPG")) { + emit imageSaved(requestId, actualFileName); + } else { + emit imageCaptureError(requestId, QCameraImageCapture::OutOfSpaceError, file.errorString()); + } + } else { + const QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName); + emit imageCaptureError(requestId, QCameraImageCapture::ResourceError, errorMessage); + } + } +} + +void BbCameraSession::handleVideoRecordingPaused() +{ + //TODO: implement once BB10 API supports pausing a video +} + +void BbCameraSession::handleVideoRecordingResumed() +{ + if (m_videoStatus == QMediaRecorder::StartingStatus) { + m_videoStatus = QMediaRecorder::RecordingStatus; + emit videoStatusChanged(m_videoStatus); + + m_videoRecordingDuration.restart(); + } +} + +void BbCameraSession::deviceOrientationChanged(int angle) +{ + if (m_handle != CAMERA_HANDLE_INVALID) + camera_set_device_orientation(m_handle, angle); +} + +void BbCameraSession::handleCameraPowerUp() +{ + stopViewFinder(); + startViewFinder(); +} + +void BbCameraSession::viewfinderFrameGrabbed(const QImage &image) +{ + QTransform transform; + + transform.rotate(m_nativeCameraOrientation); + + QImage frame = image.copy().transformed(transform); + if (m_device == cameraIdentifierFront()) + frame = frame.mirrored(true, false); + + QMutexLocker locker(&m_surfaceMutex); + if (m_surface) { + if (frame.size() != m_surface->surfaceFormat().frameSize()) { + m_surface->stop(); + m_surface->start(QVideoSurfaceFormat(frame.size(), QVideoFrame::Format_ARGB32)); + } + + QVideoFrame videoFrame(frame); + + m_surface->present(videoFrame); + } +} + +bool BbCameraSession::openCamera() +{ + if (m_handle != CAMERA_HANDLE_INVALID) // camera is already open + return true; + + m_status = QCamera::LoadingStatus; + emit statusChanged(m_status); + + camera_unit_t unit = CAMERA_UNIT_REAR; + if (m_device == cameraIdentifierFront()) + unit = CAMERA_UNIT_FRONT; + else if (m_device == cameraIdentifierRear()) + unit = CAMERA_UNIT_REAR; + else if (m_device == cameraIdentifierDesktop()) + unit = CAMERA_UNIT_DESKTOP; + + camera_error_t result = camera_open(unit, CAMERA_MODE_RW, &m_handle); + if (result != CAMERA_EOK) { + m_handle = CAMERA_HANDLE_INVALID; + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); + + qWarning() << "Unable to open camera:" << result; + emit error(QCamera::CameraError, tr("Unable to open camera")); + return false; + } + + result = camera_get_native_orientation(m_handle, &m_nativeCameraOrientation); + if (result != CAMERA_EOK) { + qWarning() << "Unable to retrieve native camera orientation:" << result; + emit error(QCamera::CameraError, tr("Unable to retrieve native camera orientation")); + return false; + } + + m_previewIsVideo = camera_has_feature(m_handle, CAMERA_FEATURE_PREVIEWISVIDEO); + + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + + emit cameraOpened(); + + return true; +} + +void BbCameraSession::closeCamera() +{ + if (m_handle == CAMERA_HANDLE_INVALID) // camera is closed already + return; + + m_status = QCamera::UnloadingStatus; + emit statusChanged(m_status); + + const camera_error_t result = camera_close(m_handle); + if (result != CAMERA_EOK) { + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); + + qWarning() << "Unable to close camera:" << result; + emit error(QCamera::CameraError, tr("Unable to close camera")); + return; + } + + m_handle = CAMERA_HANDLE_INVALID; + + m_status = QCamera::UnloadedStatus; + emit statusChanged(m_status); +} + +static void viewFinderStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context) +{ + Q_UNUSED(handle) + + if (status == CAMERA_STATUS_FOCUS_CHANGE) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "focusStatusChanged", Qt::QueuedConnection, Q_ARG(int, value)); + return; + } +#ifndef Q_OS_BLACKBERRY_TABLET + else if (status == CAMERA_STATUS_POWERUP) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleCameraPowerUp", Qt::QueuedConnection); + } +#endif +} + +bool BbCameraSession::startViewFinder() +{ + m_status = QCamera::StartingStatus; + emit statusChanged(m_status); + + QSize viewfinderResolution; + camera_error_t result = CAMERA_EOK; + if (m_captureMode & QCamera::CaptureStillImage) { + result = camera_start_photo_viewfinder(m_handle, 0, viewFinderStatusCallback, this); + viewfinderResolution = currentViewfinderResolution(QCamera::CaptureStillImage); + } else if (m_captureMode & QCamera::CaptureVideo) { + result = camera_start_video_viewfinder(m_handle, 0, viewFinderStatusCallback, this); + viewfinderResolution = currentViewfinderResolution(QCamera::CaptureVideo); + } + + if (result != CAMERA_EOK) { + qWarning() << "Unable to start viewfinder:" << result; + return false; + } + + const int angle = m_orientationHandler->orientation(); + + const QSize rotatedSize = ((angle == 0 || angle == 180) ? viewfinderResolution + : viewfinderResolution.transposed()); + + m_surfaceMutex.lock(); + if (m_surface) { + const bool ok = m_surface->start(QVideoSurfaceFormat(rotatedSize, QVideoFrame::Format_ARGB32)); + if (!ok) + qWarning() << "Unable to start camera viewfinder surface"; + } + m_surfaceMutex.unlock(); + + m_status = QCamera::ActiveStatus; + emit statusChanged(m_status); + + return true; +} + +void BbCameraSession::stopViewFinder() +{ + m_windowGrabber->stop(); + + m_status = QCamera::StoppingStatus; + emit statusChanged(m_status); + + m_surfaceMutex.lock(); + if (m_surface) { + m_surface->stop(); + } + m_surfaceMutex.unlock(); + + camera_error_t result = CAMERA_EOK; + if (m_captureMode & QCamera::CaptureStillImage) + result = camera_stop_photo_viewfinder(m_handle); + else if (m_captureMode & QCamera::CaptureVideo) + result = camera_stop_video_viewfinder(m_handle); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to stop viewfinder:" << result; + return; + } + + m_status = QCamera::LoadedStatus; + emit statusChanged(m_status); +} + +void BbCameraSession::applyConfiguration() +{ + if (m_captureMode & QCamera::CaptureStillImage) { + const QList<QSize> photoOutputResolutions = supportedResolutions(QCamera::CaptureStillImage); + + if (!m_imageEncoderSettings.resolution().isValid() || !photoOutputResolutions.contains(m_imageEncoderSettings.resolution())) + m_imageEncoderSettings.setResolution(photoOutputResolutions.first()); + + const QSize photoResolution = m_imageEncoderSettings.resolution(); + const qreal aspectRatio = static_cast<qreal>(photoResolution.width())/static_cast<qreal>(photoResolution.height()); + + // apply viewfinder configuration + QSize viewfinderResolution; + QList<QSize> sizes = supportedViewfinderResolutions(QCamera::CaptureStillImage); + std::reverse(sizes.begin(), sizes.end()); // use smallest possible resolution + foreach (const QSize &size, sizes) { + // 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; + } + } + + Q_ASSERT(viewfinderResolution.isValid()); + + const QByteArray windowId = QString().sprintf("qcamera_vf_%p", this).toLatin1(); + m_windowGrabber->setWindowId(windowId); + + const QByteArray windowGroupId = m_windowGrabber->windowGroupId(); + + camera_error_t result = camera_set_photovf_property(m_handle, + CAMERA_IMGPROP_WIN_GROUPID, windowGroupId.data(), + CAMERA_IMGPROP_WIN_ID, windowId.data(), + CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(), + CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(), + CAMERA_IMGPROP_FORMAT, CAMERA_FRAMETYPE_NV12, + CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply photo viewfinder settings:" << result; + return; + } + + + int jpegQuality = 100; + switch (m_imageEncoderSettings.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; + } + + // apply photo configuration + result = camera_set_photo_property(m_handle, + CAMERA_IMGPROP_WIDTH, photoResolution.width(), + CAMERA_IMGPROP_HEIGHT, photoResolution.height(), + CAMERA_IMGPROP_JPEGQFACTOR, jpegQuality, + CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply photo settings:" << result; + return; + } + + } else if (m_captureMode & QCamera::CaptureVideo) { + applyVideoSettings(); + } + + startViewFinder(); +} + +static void videoRecordingStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context) +{ + Q_UNUSED(handle) + Q_UNUSED(value) + +#ifndef Q_OS_BLACKBERRY_TABLET + if (status == CAMERA_STATUS_VIDEO_PAUSE) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleVideoRecordingPaused", Qt::QueuedConnection); + } else if (status == CAMERA_STATUS_VIDEO_RESUME) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleVideoRecordingResumed", Qt::QueuedConnection); + } +#endif +} + +bool BbCameraSession::startVideoRecording() +{ + m_videoRecordingDuration.invalidate(); + + m_videoStatus = QMediaRecorder::StartingStatus; + emit videoStatusChanged(m_videoStatus); + + if (m_videoOutputLocation.isEmpty()) + m_videoOutputLocation = m_mediaStorageLocation.generateFileName(QLatin1String("VID_"), m_mediaStorageLocation.defaultDir(QCamera::CaptureVideo), QLatin1String("mp4")); + + emit actualLocationChanged(m_videoOutputLocation); + + const camera_error_t result = camera_start_video(m_handle, QFile::encodeName(m_videoOutputLocation), 0, videoRecordingStatusCallback, this); + if (result != CAMERA_EOK) { + m_videoStatus = QMediaRecorder::LoadedStatus; + emit videoStatusChanged(m_videoStatus); + + emit videoError(QMediaRecorder::ResourceError, tr("Unable to start video recording")); + return false; + } + + return true; +} + +void BbCameraSession::stopVideoRecording() +{ + m_videoStatus = QMediaRecorder::FinalizingStatus; + emit videoStatusChanged(m_videoStatus); + + const camera_error_t result = camera_stop_video(m_handle); + if (result != CAMERA_EOK) { + emit videoError(QMediaRecorder::ResourceError, tr("Unable to stop video recording")); + } + + m_videoStatus = QMediaRecorder::LoadedStatus; + emit videoStatusChanged(m_videoStatus); + + m_videoRecordingDuration.invalidate(); +} + +bool BbCameraSession::isCaptureModeSupported(camera_handle_t handle, QCamera::CaptureModes mode) const +{ + if (mode & QCamera::CaptureStillImage) + return camera_has_feature(handle, CAMERA_FEATURE_PHOTO); + + if (mode & QCamera::CaptureVideo) + return camera_has_feature(handle, CAMERA_FEATURE_VIDEO); + + return false; +} + +QList<QSize> BbCameraSession::supportedResolutions(QCamera::CaptureMode mode) const +{ + Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID); + + QList<QSize> list; + + camera_error_t result = CAMERA_EOK; + camera_res_t resolutions[20]; + unsigned int supported = 0; + + if (mode == QCamera::CaptureStillImage) + result = camera_get_photo_output_resolutions(m_handle, CAMERA_FRAMETYPE_JPEG, 20, &supported, resolutions); + else if (mode == QCamera::CaptureVideo) + result = camera_get_video_output_resolutions(m_handle, 20, &supported, resolutions); + + if (result != CAMERA_EOK) + return list; + + for (unsigned int i = 0; i < supported; ++i) + list << QSize(resolutions[i].width, resolutions[i].height); + + return list; +} + +QList<QSize> BbCameraSession::supportedViewfinderResolutions(QCamera::CaptureMode mode) const +{ + Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID); + + QList<QSize> list; + + camera_error_t result = CAMERA_EOK; + camera_res_t resolutions[20]; + unsigned int supported = 0; + + if (mode == QCamera::CaptureStillImage) + result = camera_get_photo_vf_resolutions(m_handle, 20, &supported, resolutions); + else if (mode == QCamera::CaptureVideo) + result = camera_get_video_vf_resolutions(m_handle, 20, &supported, resolutions); + + if (result != CAMERA_EOK) + return list; + + for (unsigned int i = 0; i < supported; ++i) + list << QSize(resolutions[i].width, resolutions[i].height); + + return list; +} + +QSize BbCameraSession::currentViewfinderResolution(QCamera::CaptureMode mode) const +{ + Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID); + + camera_error_t result = CAMERA_EOK; + int width = 0; + int height = 0; + + if (mode == QCamera::CaptureStillImage) + result = camera_get_photovf_property(m_handle, CAMERA_IMGPROP_WIDTH, &width, + CAMERA_IMGPROP_HEIGHT, &height); + else if (mode == QCamera::CaptureVideo) + result = camera_get_videovf_property(m_handle, CAMERA_IMGPROP_WIDTH, &width, + CAMERA_IMGPROP_HEIGHT, &height); + + if (result != CAMERA_EOK) + return QSize(); + + return QSize(width, height); +} + +QT_END_NAMESPACE |