diff options
Diffstat (limited to 'src/plugins/blackberry/camera/bbcamerasession.cpp')
-rw-r--r-- | src/plugins/blackberry/camera/bbcamerasession.cpp | 1373 |
1 files changed, 1373 insertions, 0 deletions
diff --git a/src/plugins/blackberry/camera/bbcamerasession.cpp b/src/plugins/blackberry/camera/bbcamerasession.cpp new file mode 100644 index 000000000..d07e84495 --- /dev/null +++ b/src/plugins/blackberry/camera/bbcamerasession.cpp @@ -0,0 +1,1373 @@ +/**************************************************************************** +** +** 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 <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_ENOSPC: + return QLatin1String("Disk is full"); + 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"); + case CAMERA_EDESKTOPCAMERAINUSE: + return QLatin1String("Desktop camera in use already"); + 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_locksApplyMode(IndependentMode) + , m_focusLockStatus(QCamera::Unlocked) + , m_videoState(QMediaRecorder::StoppedState) + , m_videoStatus(QMediaRecorder::LoadedStatus) + , m_handle(CAMERA_HANDLE_INVALID) +{ + 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))); +} + +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()) { + applyConfiguration(); + if (startViewFinder()) { + m_state = state; + } + } + } + } else if (previousState == QCamera::LoadedState) { + if (state == QCamera::UnloadedState) { + closeCamera(); + m_state = state; + } else if (state == QCamera::ActiveState) { + applyConfiguration(); + if (startViewFinder()) { + 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, QObject::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 = QObject::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")); +} + +QCamera::LockTypes BbCameraSession::supportedLocks() const +{ + if (m_locksApplyMode == FocusOnlyMode) + return QCamera::LockFocus; + else + return (QCamera::LockExposure | QCamera::LockWhiteBalance | QCamera::LockFocus); +} + +QCamera::LockStatus BbCameraSession::lockStatus(QCamera::LockType lock) const +{ + switch (lock) { + case QCamera::LockExposure: + return QCamera::Unlocked; + case QCamera::LockWhiteBalance: + return QCamera::Unlocked; + case QCamera::LockFocus: + return m_focusLockStatus; + default: + return QCamera::Unlocked; + } +} + +void BbCameraSession::searchAndLock(QCamera::LockTypes locks) +{ + m_currentLockTypes |= locks; + + uint32_t lockModes = CAMERA_3A_NONE; + + switch (m_locksApplyMode) { + case IndependentMode: + if (m_currentLockTypes & QCamera::LockExposure) + lockModes |= CAMERA_3A_AUTOEXPOSURE; + if (m_currentLockTypes & QCamera::LockWhiteBalance) + lockModes |= CAMERA_3A_AUTOWHITEBALANCE; + if (m_currentLockTypes & QCamera::LockFocus) + lockModes |= CAMERA_3A_AUTOFOCUS; + break; + case FocusExposureBoundMode: + if ((m_currentLockTypes & QCamera::LockExposure) || (m_currentLockTypes & QCamera::LockFocus)) + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS); + break; + case AllBoundMode: + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS | CAMERA_3A_AUTOWHITEBALANCE); + break; + case FocusOnlyMode: + lockModes = CAMERA_3A_AUTOFOCUS; + break; + } + + const camera_error_t result = camera_set_3a_lock(m_handle, lockModes); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to set lock modes:" << result; + } +} + +void BbCameraSession::unlock(QCamera::LockTypes locks) +{ + m_currentLockTypes &= ~locks; + + uint32_t lockModes = CAMERA_3A_NONE; + + switch (m_locksApplyMode) { + case IndependentMode: + if (m_currentLockTypes & QCamera::LockExposure) + lockModes |= CAMERA_3A_AUTOEXPOSURE; + if (m_currentLockTypes & QCamera::LockWhiteBalance) + lockModes |= CAMERA_3A_AUTOWHITEBALANCE; + if (m_currentLockTypes & QCamera::LockFocus) + lockModes |= CAMERA_3A_AUTOFOCUS; + break; + case FocusExposureBoundMode: + if ((m_currentLockTypes & QCamera::LockExposure) || (m_currentLockTypes & QCamera::LockFocus)) + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS); + break; + case AllBoundMode: + lockModes = (CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOFOCUS | CAMERA_3A_AUTOWHITEBALANCE); + break; + case FocusOnlyMode: + lockModes = CAMERA_3A_AUTOFOCUS; + break; + } + + const camera_error_t result = camera_set_3a_lock(m_handle, lockModes); + + if (result != CAMERA_EOK) + qWarning() << "Unable to set lock modes:" << result; +} + +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()); + + camera_error_t result = CAMERA_EOK; + result = camera_set_videovf_property(m_handle, + CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(), + CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(), + CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation); + + if (result != CAMERA_EOK) { + qWarning() << "Unable to apply video viewfinder settings:" << result; + return; + } + + 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; + + const QSize resolution = m_videoEncoderSettings.resolution(); + + 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_VIDEOCODEC, cameraVideoCodec, + CAMERA_IMGPROP_AUDIOCODEC, cameraAudioCodec); + + 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; +} + +static QImage convertFrameToImage(camera_buffer_t *buffer) +{ + if (buffer->frametype != CAMERA_FRAMETYPE_NV12) + return QImage(); + + const unsigned int width = buffer->framedesc.nv12.width; + const unsigned int height = buffer->framedesc.nv12.height; + + /** + * Copying the data from the buffer into our own data array and working + * on this copy is actually faster than working on the buffer. + * Looks like we hit some cache misses here, since the stride inside the + * NV12 frame is really large (4096) in comparison to the actual image width (768) + */ + const unsigned long long size = width*height + width*height/2; + unsigned char *data = new unsigned char[size]; + + unsigned char *source = buffer->framebuf; + unsigned char *dest = data; + for (uint row = 0; row < height; ++row) { + memcpy(dest, source, width); + source += buffer->framedesc.nv12.stride; + dest += width; + } + + source = buffer->framebuf + buffer->framedesc.nv12.uv_offset; + for (uint row = 0; row < height/2; ++row) { + memcpy(dest, source, width); + source += buffer->framedesc.nv12.uv_stride; + dest += width; + } + + QImage image(width, height, QImage::Format_RGB32); + + unsigned char *dataPtr = data; + unsigned char *uvDataPtr = 0; + unsigned int uv_base_offset = width*height; + int yValue = 0; + int uValue = 0; + int vValue = 0; + int bValue = 0; + int gValue = 0; + int rValue = 0; + unsigned char *rowStart = 0; + + unsigned char *imageDest = 0; + for (unsigned int y = 0; y < height; ++y) { + imageDest = const_cast<unsigned char*>(image.constScanLine(y)); + rowStart = data + (uv_base_offset + (width*qFloor(y/2.0))); + for (unsigned int x = 0; x < width; ++x) { + uvDataPtr = rowStart + (qFloor(x/2)*2); + + yValue = ((*dataPtr++) - 16) * 1.164; + uValue = ((*uvDataPtr++) - 128); + vValue = ((*uvDataPtr) - 128); + + bValue = yValue + 2.018 * uValue; + gValue = yValue - 0.813 * vValue - 0.391 * uValue; + rValue = yValue + 1.596 * vValue; + + *imageDest = qBound(0, bValue, 255); + imageDest++; + *imageDest = qBound(0, gValue, 255); + imageDest++; + *imageDest = qBound(0, rValue, 255); + imageDest++; + *imageDest = 255; + imageDest++; + } + } + + delete [] data; + + return image; +} + +void BbCameraSession::handlePhotoViewFinderData(camera_buffer_t *buffer) +{ + QTransform transform; + + transform.rotate(m_nativeCameraOrientation); + + const QImage frame = convertFrameToImage(buffer).transformed(transform); + + 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_RGB32)); + } + + QVideoFrame videoFrame(frame); + + m_surface->present(videoFrame); + } +} + + +void BbCameraSession::handleVideoViewFinderData(camera_buffer_t *buffer) +{ + QTransform transform; + + transform.rotate(m_nativeCameraOrientation); + + const QImage frame = convertFrameToImage(buffer).transformed(transform); + + 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_RGB32)); + } + + QVideoFrame videoFrame(frame); + + m_surface->present(videoFrame); + } +} + +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::handleFocusStatusChanged(int value) +{ + const camera_focusstate_t focusState = static_cast<camera_focusstate_t>(value); + + switch (focusState) { + case CAMERA_FOCUSSTATE_NONE: + case CAMERA_FOCUSSTATE_WAITING: + m_focusLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Unlocked, QCamera::UserRequest); + break; + case CAMERA_FOCUSSTATE_SEARCHING: + m_focusLockStatus = QCamera::Searching; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Searching, QCamera::UserRequest); + break; + case CAMERA_FOCUSSTATE_FAILED: + m_focusLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Unlocked, QCamera::LockFailed); + break; + case CAMERA_FOCUSSTATE_LOCKED: + m_focusLockStatus = QCamera::Locked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Locked, QCamera::LockAcquired); + break; + case CAMERA_FOCUSSTATE_SCENECHANGE: + m_focusLockStatus = QCamera::Unlocked; + emit lockStatusChanged(QCamera::LockFocus, QCamera::Unlocked, QCamera::LockTemporaryLost); + break; + default: + break; + } +} + +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(); +} + +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); + + 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 photoViewFinderDataCallback(camera_handle_t handle, camera_buffer_t *buffer, void *context) +{ + Q_UNUSED(handle) + + BbCameraSession *session = static_cast<BbCameraSession*>(context); + session->handlePhotoViewFinderData(buffer); +} + +static void videoViewFinderDataCallback(camera_handle_t handle, camera_buffer_t *buffer, void *context) +{ + Q_UNUSED(handle) + + BbCameraSession *session = static_cast<BbCameraSession*>(context); + session->handleVideoViewFinderData(buffer); +} + +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, "handleFocusStatusChanged", Qt::QueuedConnection, Q_ARG(int, value)); + } else if (status == CAMERA_STATUS_POWERUP) { + BbCameraSession *session = static_cast<BbCameraSession*>(context); + QMetaObject::invokeMethod(session, "handleCameraPowerUp", Qt::QueuedConnection); + } +} + +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, photoViewFinderDataCallback, viewFinderStatusCallback, this); + viewfinderResolution = currentViewfinderResolution(QCamera::CaptureStillImage); + } else if (m_captureMode & QCamera::CaptureVideo) { + result = camera_start_video_viewfinder(m_handle, videoViewFinderDataCallback, viewFinderStatusCallback, this); + viewfinderResolution = currentViewfinderResolution(QCamera::CaptureVideo); + } + + if (result != CAMERA_EOK) { + qWarning() << "Unable to start viewfinder:" << result; + return false; + } + + // retrieve information about lock apply modes + { + int supported = 0; + uint32_t modes[20]; + + const camera_error_t result = camera_get_3a_lock_modes(m_handle, 20, &supported, modes); + + if (result == CAMERA_EOK) { + // see API documentation of camera_get_3a_lock_modes for explanation of case discrimination below + if (supported == 4) { + m_locksApplyMode = IndependentMode; + } else if (supported == 3) { + m_locksApplyMode = FocusExposureBoundMode; + } else if (supported == 2) { + if (modes[0] == (CAMERA_3A_AUTOFOCUS | CAMERA_3A_AUTOEXPOSURE | CAMERA_3A_AUTOWHITEBALANCE)) + m_locksApplyMode = AllBoundMode; + else + m_locksApplyMode = FocusOnlyMode; + } + } + } + + 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_RGB32)); + 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_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()); + + camera_error_t result = camera_set_photovf_property(m_handle, + 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(); + } +} + +static void videoRecordingStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context) +{ + Q_UNUSED(handle) + Q_UNUSED(value) + + 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); + } +} + +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 |