1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Research In Motion
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 #include "bbcamerasession.h"
40 
41 #include "bbcameraorientationhandler.h"
42 #include "bbcameraviewfindersettingscontrol.h"
43 #include "windowgrabber.h"
44 
45 #include <QAbstractVideoSurface>
46 #include <QBuffer>
47 #include <QDebug>
48 #include <QImage>
49 #include <QUrl>
50 #include <QVideoSurfaceFormat>
51 #include <qmath.h>
52 
53 #include <algorithm>
54 
55 QT_BEGIN_NAMESPACE
56 
errorToString(camera_error_t error)57 static QString errorToString(camera_error_t error)
58 {
59     switch (error) {
60     case CAMERA_EOK:
61         return QLatin1String("No error");
62     case CAMERA_EAGAIN:
63         return QLatin1String("Camera unavailable");
64     case CAMERA_EINVAL:
65         return QLatin1String("Invalid argument");
66     case CAMERA_ENODEV:
67         return QLatin1String("Camera not found");
68     case CAMERA_EMFILE:
69         return QLatin1String("File table overflow");
70     case CAMERA_EBADF:
71         return QLatin1String("Invalid handle passed");
72     case CAMERA_EACCESS:
73         return QLatin1String("No permission");
74     case CAMERA_EBADR:
75         return QLatin1String("Invalid file descriptor");
76     case CAMERA_ENOENT:
77         return QLatin1String("File or directory does not exists");
78     case CAMERA_ENOMEM:
79         return QLatin1String("Memory allocation failed");
80     case CAMERA_EOPNOTSUPP:
81         return QLatin1String("Operation not supported");
82     case CAMERA_ETIMEDOUT:
83         return QLatin1String("Communication timeout");
84     case CAMERA_EALREADY:
85         return QLatin1String("Operation already in progress");
86     case CAMERA_EUNINIT:
87         return QLatin1String("Camera library not initialized");
88     case CAMERA_EREGFAULT:
89         return QLatin1String("Callback registration failed");
90     case CAMERA_EMICINUSE:
91         return QLatin1String("Microphone in use already");
92     case CAMERA_ENODATA:
93         return QLatin1String("Data does not exist");
94     case CAMERA_EBUSY:
95         return QLatin1String("Camera busy");
96     case CAMERA_EDESKTOPCAMERAINUSE:
97         return QLatin1String("Desktop camera in use already");
98     case CAMERA_ENOSPC:
99         return QLatin1String("Disk is full");
100     case CAMERA_EPOWERDOWN:
101         return QLatin1String("Camera in power down state");
102     case CAMERA_3ALOCKED:
103         return QLatin1String("3A have been locked");
104 //  case CAMERA_EVIEWFINDERFROZEN: // not yet available in 10.2 NDK
105 //      return QLatin1String("Freeze flag set");
106     default:
107         return QLatin1String("Unknown error");
108     }
109 }
110 
operator <<(QDebug debug,camera_error_t error)111 QDebug operator<<(QDebug debug, camera_error_t error)
112 {
113     debug.nospace() << errorToString(error);
114     return debug.space();
115 }
116 
BbCameraSession(QObject * parent)117 BbCameraSession::BbCameraSession(QObject *parent)
118     : QObject(parent)
119     , m_nativeCameraOrientation(0)
120     , m_orientationHandler(new BbCameraOrientationHandler(this))
121     , m_status(QCamera::UnloadedStatus)
122     , m_state(QCamera::UnloadedState)
123     , m_captureMode(QCamera::CaptureStillImage)
124     , m_device("bb:RearCamera")
125     , m_previewIsVideo(true)
126     , m_surface(0)
127     , m_captureImageDriveMode(QCameraImageCapture::SingleImageCapture)
128     , m_lastImageCaptureId(0)
129     , m_captureDestination(QCameraImageCapture::CaptureToFile)
130     , m_videoState(QMediaRecorder::StoppedState)
131     , m_videoStatus(QMediaRecorder::LoadedStatus)
132     , m_handle(CAMERA_HANDLE_INVALID)
133     , m_windowGrabber(new WindowGrabber(this))
134 {
135     connect(this, SIGNAL(statusChanged(QCamera::Status)), SLOT(updateReadyForCapture()));
136     connect(this, SIGNAL(captureModeChanged(QCamera::CaptureModes)), SLOT(updateReadyForCapture()));
137     connect(m_orientationHandler, SIGNAL(orientationChanged(int)), SLOT(deviceOrientationChanged(int)));
138 
139     connect(m_windowGrabber, SIGNAL(frameGrabbed(QImage, int)), SLOT(viewfinderFrameGrabbed(QImage)));
140 }
141 
~BbCameraSession()142 BbCameraSession::~BbCameraSession()
143 {
144     stopViewFinder();
145     closeCamera();
146 }
147 
handle() const148 camera_handle_t BbCameraSession::handle() const
149 {
150     return m_handle;
151 }
152 
state() const153 QCamera::State BbCameraSession::state() const
154 {
155     return m_state;
156 }
157 
setState(QCamera::State state)158 void BbCameraSession::setState(QCamera::State state)
159 {
160     if (m_state == state)
161         return;
162 
163     const QCamera::State previousState = m_state;
164 
165     if (previousState == QCamera::UnloadedState) {
166         if (state == QCamera::LoadedState) {
167             if (openCamera()) {
168                 m_state = state;
169             }
170         } else if (state == QCamera::ActiveState) {
171             if (openCamera()) {
172                 QMetaObject::invokeMethod(this, "applyConfiguration", Qt::QueuedConnection);
173                 m_state = state;
174             }
175         }
176     } else if (previousState == QCamera::LoadedState) {
177         if (state == QCamera::UnloadedState) {
178             closeCamera();
179             m_state = state;
180         } else if (state == QCamera::ActiveState) {
181             QMetaObject::invokeMethod(this, "applyConfiguration", Qt::QueuedConnection);
182             m_state = state;
183         }
184     } else if (previousState == QCamera::ActiveState) {
185         if (state == QCamera::LoadedState) {
186             stopViewFinder();
187             m_state = state;
188         } else if (state == QCamera::UnloadedState) {
189             stopViewFinder();
190             closeCamera();
191             m_state = state;
192         }
193     }
194 
195     if (m_state != previousState)
196         emit stateChanged(m_state);
197 }
198 
status() const199 QCamera::Status BbCameraSession::status() const
200 {
201     return m_status;
202 }
203 
captureMode() const204 QCamera::CaptureModes BbCameraSession::captureMode() const
205 {
206     return m_captureMode;
207 }
208 
setCaptureMode(QCamera::CaptureModes captureMode)209 void BbCameraSession::setCaptureMode(QCamera::CaptureModes captureMode)
210 {
211     if (m_captureMode == captureMode)
212         return;
213 
214     m_captureMode = captureMode;
215     emit captureModeChanged(m_captureMode);
216 }
217 
isCaptureModeSupported(QCamera::CaptureModes mode) const218 bool BbCameraSession::isCaptureModeSupported(QCamera::CaptureModes mode) const
219 {
220     if (m_handle == CAMERA_HANDLE_INVALID) {
221         // the camera has not been loaded yet via QCamera::load(), so
222         // we open it temporarily to peek for the supported capture modes
223 
224         camera_unit_t unit = CAMERA_UNIT_REAR;
225         if (m_device == cameraIdentifierFront())
226             unit = CAMERA_UNIT_FRONT;
227         else if (m_device == cameraIdentifierRear())
228             unit = CAMERA_UNIT_REAR;
229         else if (m_device == cameraIdentifierDesktop())
230             unit = CAMERA_UNIT_DESKTOP;
231 
232         camera_handle_t handle;
233         const camera_error_t result = camera_open(unit, CAMERA_MODE_RW, &handle);
234         if (result != CAMERA_EOK)
235             return true;
236 
237         const bool supported = isCaptureModeSupported(handle, mode);
238 
239         camera_close(handle);
240 
241         return supported;
242     } else {
243         return isCaptureModeSupported(m_handle, mode);
244     }
245 }
246 
cameraIdentifierFront()247 QByteArray BbCameraSession::cameraIdentifierFront()
248 {
249     return "bb:FrontCamera";
250 }
251 
cameraIdentifierRear()252 QByteArray BbCameraSession::cameraIdentifierRear()
253 {
254     return "bb:RearCamera";
255 }
256 
cameraIdentifierDesktop()257 QByteArray BbCameraSession::cameraIdentifierDesktop()
258 {
259     return "bb:DesktopCamera";
260 }
261 
setDevice(const QByteArray & device)262 void BbCameraSession::setDevice(const QByteArray &device)
263 {
264     m_device = device;
265 }
266 
device() const267 QByteArray BbCameraSession::device() const
268 {
269     return m_device;
270 }
271 
surface() const272 QAbstractVideoSurface* BbCameraSession::surface() const
273 {
274     return m_surface;
275 }
276 
setSurface(QAbstractVideoSurface * surface)277 void BbCameraSession::setSurface(QAbstractVideoSurface *surface)
278 {
279     QMutexLocker locker(&m_surfaceMutex);
280 
281     if (m_surface == surface)
282         return;
283 
284     m_surface = surface;
285 }
286 
isReadyForCapture() const287 bool BbCameraSession::isReadyForCapture() const
288 {
289     if (m_captureMode & QCamera::CaptureStillImage)
290         return (m_status == QCamera::ActiveStatus);
291 
292     if (m_captureMode & QCamera::CaptureVideo)
293         return (m_status == QCamera::ActiveStatus);
294 
295     return false;
296 }
297 
driveMode() const298 QCameraImageCapture::DriveMode BbCameraSession::driveMode() const
299 {
300     return m_captureImageDriveMode;
301 }
302 
setDriveMode(QCameraImageCapture::DriveMode mode)303 void BbCameraSession::setDriveMode(QCameraImageCapture::DriveMode mode)
304 {
305     m_captureImageDriveMode = mode;
306 }
307 
308 /**
309  * A helper structure that keeps context data for image capture callbacks.
310  */
311 struct ImageCaptureData
312 {
313     int requestId;
314     QString fileName;
315     BbCameraSession *session;
316 };
317 
imageCaptureShutterCallback(camera_handle_t handle,void * context)318 static void imageCaptureShutterCallback(camera_handle_t handle, void *context)
319 {
320     Q_UNUSED(handle)
321 
322     const ImageCaptureData *data = static_cast<ImageCaptureData*>(context);
323 
324     // We are inside a worker thread here, so emit imageExposed inside the main thread
325     QMetaObject::invokeMethod(data->session, "imageExposed", Qt::QueuedConnection,
326                               Q_ARG(int, data->requestId));
327 }
328 
imageCaptureImageCallback(camera_handle_t handle,camera_buffer_t * buffer,void * context)329 static void imageCaptureImageCallback(camera_handle_t handle, camera_buffer_t *buffer, void *context)
330 {
331     Q_UNUSED(handle)
332 
333     QScopedPointer<ImageCaptureData> data(static_cast<ImageCaptureData*>(context));
334 
335     if (buffer->frametype != CAMERA_FRAMETYPE_JPEG) {
336         // We are inside a worker thread here, so emit error signal inside the main thread
337         QMetaObject::invokeMethod(data->session, "imageCaptureError", Qt::QueuedConnection,
338                                   Q_ARG(int, data->requestId),
339                                   Q_ARG(QCameraImageCapture::Error, QCameraImageCapture::FormatError),
340                                   Q_ARG(QString, BbCameraSession::tr("Camera provides image in unsupported format")));
341         return;
342     }
343 
344     const QByteArray rawData((const char*)buffer->framebuf, buffer->framedesc.jpeg.bufsize);
345 
346     QImage image;
347     const bool ok = image.loadFromData(rawData, "JPG");
348     if (!ok) {
349         const QString errorMessage = BbCameraSession::tr("Could not load JPEG data from frame");
350         // We are inside a worker thread here, so emit error signal inside the main thread
351         QMetaObject::invokeMethod(data->session, "imageCaptureError", Qt::QueuedConnection,
352                                   Q_ARG(int, data->requestId),
353                                   Q_ARG(QCameraImageCapture::Error, QCameraImageCapture::FormatError),
354                                   Q_ARG(QString, errorMessage));
355         return;
356     }
357 
358 
359     // We are inside a worker thread here, so invoke imageCaptured inside the main thread
360     QMetaObject::invokeMethod(data->session, "imageCaptured", Qt::QueuedConnection,
361                               Q_ARG(int, data->requestId),
362                               Q_ARG(QImage, image),
363                               Q_ARG(QString, data->fileName));
364 }
365 
capture(const QString & fileName)366 int BbCameraSession::capture(const QString &fileName)
367 {
368     m_lastImageCaptureId++;
369 
370     if (!isReadyForCapture()) {
371         emit imageCaptureError(m_lastImageCaptureId, QCameraImageCapture::NotReadyError, tr("Camera not ready"));
372         return m_lastImageCaptureId;
373     }
374 
375     if (m_captureImageDriveMode == QCameraImageCapture::SingleImageCapture) {
376         // prepare context object for callback
377         ImageCaptureData *context = new ImageCaptureData;
378         context->requestId = m_lastImageCaptureId;
379         context->fileName = fileName;
380         context->session = this;
381 
382         const camera_error_t result = camera_take_photo(m_handle,
383                                                         imageCaptureShutterCallback,
384                                                         0,
385                                                         0,
386                                                         imageCaptureImageCallback,
387                                                         context, false);
388 
389         if (result != CAMERA_EOK)
390             qWarning() << "Unable to take photo:" << result;
391     } else {
392         // TODO: implement burst mode when available in Qt API
393     }
394 
395     return m_lastImageCaptureId;
396 }
397 
cancelCapture()398 void BbCameraSession::cancelCapture()
399 {
400     // BB10 API doesn't provide functionality for that
401 }
402 
isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const403 bool BbCameraSession::isCaptureDestinationSupported(QCameraImageCapture::CaptureDestinations destination) const
404 {
405     // capture to buffer, file and both are supported.
406     return destination & (QCameraImageCapture::CaptureToFile | QCameraImageCapture::CaptureToBuffer);
407 }
408 
captureDestination() const409 QCameraImageCapture::CaptureDestinations BbCameraSession::captureDestination() const
410 {
411     return m_captureDestination;
412 }
413 
setCaptureDestination(QCameraImageCapture::CaptureDestinations destination)414 void BbCameraSession::setCaptureDestination(QCameraImageCapture::CaptureDestinations destination)
415 {
416     if (m_captureDestination != destination) {
417         m_captureDestination = destination;
418         emit captureDestinationChanged(m_captureDestination);
419     }
420 }
421 
supportedResolutions(const QImageEncoderSettings &,bool * continuous) const422 QList<QSize> BbCameraSession::supportedResolutions(const QImageEncoderSettings&, bool *continuous) const
423 {
424     if (continuous)
425         *continuous = false;
426 
427     if (m_status == QCamera::UnloadedStatus)
428         return QList<QSize>();
429 
430     if (m_captureMode & QCamera::CaptureStillImage) {
431         return supportedResolutions(QCamera::CaptureStillImage);
432     } else if (m_captureMode & QCamera::CaptureVideo) {
433         return supportedResolutions(QCamera::CaptureVideo);
434     }
435 
436     return QList<QSize>();
437 }
438 
imageSettings() const439 QImageEncoderSettings BbCameraSession::imageSettings() const
440 {
441     return m_imageEncoderSettings;
442 }
443 
setImageSettings(const QImageEncoderSettings & settings)444 void BbCameraSession::setImageSettings(const QImageEncoderSettings &settings)
445 {
446     m_imageEncoderSettings = settings;
447     if (m_imageEncoderSettings.codec().isEmpty())
448         m_imageEncoderSettings.setCodec(QLatin1String("jpeg"));
449 }
450 
outputLocation() const451 QUrl BbCameraSession::outputLocation() const
452 {
453     return QUrl::fromLocalFile(m_videoOutputLocation);
454 }
455 
setOutputLocation(const QUrl & location)456 bool BbCameraSession::setOutputLocation(const QUrl &location)
457 {
458     m_videoOutputLocation = location.toLocalFile();
459 
460     return true;
461 }
462 
videoState() const463 QMediaRecorder::State BbCameraSession::videoState() const
464 {
465     return m_videoState;
466 }
467 
setVideoState(QMediaRecorder::State state)468 void BbCameraSession::setVideoState(QMediaRecorder::State state)
469 {
470     if (m_videoState == state)
471         return;
472 
473     const QMediaRecorder::State previousState = m_videoState;
474 
475     if (previousState == QMediaRecorder::StoppedState) {
476         if (state == QMediaRecorder::RecordingState) {
477             if (startVideoRecording()) {
478                 m_videoState = state;
479             }
480         } else if (state == QMediaRecorder::PausedState) {
481             // do nothing
482         }
483     } else if (previousState == QMediaRecorder::RecordingState) {
484         if (state == QMediaRecorder::StoppedState) {
485             stopVideoRecording();
486             m_videoState = state;
487         } else if (state == QMediaRecorder::PausedState) {
488             //TODO: (pause) not supported by BB10 API yet
489         }
490     } else if (previousState == QMediaRecorder::PausedState) {
491         if (state == QMediaRecorder::StoppedState) {
492             stopVideoRecording();
493             m_videoState = state;
494         } else if (state == QMediaRecorder::RecordingState) {
495             //TODO: (resume) not supported by BB10 API yet
496         }
497     }
498 
499     emit videoStateChanged(m_videoState);
500 }
501 
videoStatus() const502 QMediaRecorder::Status BbCameraSession::videoStatus() const
503 {
504     return m_videoStatus;
505 }
506 
duration() const507 qint64 BbCameraSession::duration() const
508 {
509     return (m_videoRecordingDuration.isValid() ? m_videoRecordingDuration.elapsed() : 0);
510 }
511 
applyVideoSettings()512 void BbCameraSession::applyVideoSettings()
513 {
514     if (m_handle == CAMERA_HANDLE_INVALID)
515         return;
516 
517     // apply viewfinder configuration
518     const QList<QSize> videoOutputResolutions = supportedResolutions(QCamera::CaptureVideo);
519 
520     if (!m_videoEncoderSettings.resolution().isValid() || !videoOutputResolutions.contains(m_videoEncoderSettings.resolution()))
521         m_videoEncoderSettings.setResolution(videoOutputResolutions.first());
522 
523     QSize viewfinderResolution;
524 
525     if (m_previewIsVideo) {
526         // The viewfinder is responsible for encoding the video frames, so the resolutions must match.
527         viewfinderResolution = m_videoEncoderSettings.resolution();
528     } else {
529         // The frames are encoded separately from the viewfinder, so only the aspect ratio must match.
530         const QSize videoResolution = m_videoEncoderSettings.resolution();
531         const qreal aspectRatio = static_cast<qreal>(videoResolution.width())/static_cast<qreal>(videoResolution.height());
532 
533         QList<QSize> sizes = supportedViewfinderResolutions(QCamera::CaptureVideo);
534         std::reverse(sizes.begin(), sizes.end()); // use smallest possible resolution
535         for (const QSize &size : qAsConst(sizes)) {
536             // search for viewfinder resolution with the same aspect ratio
537             if (qFuzzyCompare(aspectRatio, (static_cast<qreal>(size.width())/static_cast<qreal>(size.height())))) {
538                 viewfinderResolution = size;
539                 break;
540             }
541         }
542     }
543 
544     Q_ASSERT(viewfinderResolution.isValid());
545 
546     const QByteArray windowId = QString().sprintf("qcamera_vf_%p", this).toLatin1();
547     m_windowGrabber->setWindowId(windowId);
548 
549     const QByteArray windowGroupId = m_windowGrabber->windowGroupId();
550 
551     const int rotationAngle = (360 - m_nativeCameraOrientation);
552 
553     camera_error_t result = CAMERA_EOK;
554     result = camera_set_videovf_property(m_handle,
555                                          CAMERA_IMGPROP_WIN_GROUPID, windowGroupId.data(),
556                                          CAMERA_IMGPROP_WIN_ID, windowId.data(),
557                                          CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(),
558                                          CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(),
559                                          CAMERA_IMGPROP_ROTATION, rotationAngle);
560 
561     if (result != CAMERA_EOK) {
562         qWarning() << "Unable to apply video viewfinder settings:" << result;
563         return;
564     }
565 
566     const QSize resolution = m_videoEncoderSettings.resolution();
567 
568     QString videoCodec = m_videoEncoderSettings.codec();
569     if (videoCodec.isEmpty())
570         videoCodec = QLatin1String("h264");
571 
572     camera_videocodec_t cameraVideoCodec = CAMERA_VIDEOCODEC_H264;
573     if (videoCodec == QLatin1String("none"))
574         cameraVideoCodec = CAMERA_VIDEOCODEC_NONE;
575     else if (videoCodec == QLatin1String("avc1"))
576         cameraVideoCodec = CAMERA_VIDEOCODEC_AVC1;
577     else if (videoCodec == QLatin1String("h264"))
578         cameraVideoCodec = CAMERA_VIDEOCODEC_H264;
579 
580     qreal frameRate = m_videoEncoderSettings.frameRate();
581     if (frameRate == 0) {
582         const QList<qreal> frameRates = supportedFrameRates(QVideoEncoderSettings(), 0);
583         if (!frameRates.isEmpty())
584             frameRate = frameRates.last();
585     }
586 
587     QString audioCodec = m_audioEncoderSettings.codec();
588     if (audioCodec.isEmpty())
589         audioCodec = QLatin1String("aac");
590 
591     camera_audiocodec_t cameraAudioCodec = CAMERA_AUDIOCODEC_AAC;
592     if (audioCodec == QLatin1String("none"))
593         cameraAudioCodec = CAMERA_AUDIOCODEC_NONE;
594     else if (audioCodec == QLatin1String("aac"))
595         cameraAudioCodec = CAMERA_AUDIOCODEC_AAC;
596     else if (audioCodec == QLatin1String("raw"))
597         cameraAudioCodec = CAMERA_AUDIOCODEC_RAW;
598 
599     result = camera_set_video_property(m_handle,
600                                        CAMERA_IMGPROP_WIDTH, resolution.width(),
601                                        CAMERA_IMGPROP_HEIGHT, resolution.height(),
602                                        CAMERA_IMGPROP_ROTATION, rotationAngle,
603                                        CAMERA_IMGPROP_VIDEOCODEC, cameraVideoCodec,
604                                        CAMERA_IMGPROP_AUDIOCODEC, cameraAudioCodec);
605 
606     if (result != CAMERA_EOK) {
607         qWarning() << "Unable to apply video settings:" << result;
608         emit videoError(QMediaRecorder::ResourceError, tr("Unable to apply video settings"));
609     }
610 }
611 
supportedResolutions(const QVideoEncoderSettings & settings,bool * continuous) const612 QList<QSize> BbCameraSession::supportedResolutions(const QVideoEncoderSettings &settings, bool *continuous) const
613 {
614     Q_UNUSED(settings);
615 
616     if (continuous)
617         *continuous = false;
618 
619     return supportedResolutions(QCamera::CaptureVideo);
620 }
621 
supportedFrameRates(const QVideoEncoderSettings & settings,bool * continuous) const622 QList<qreal> BbCameraSession::supportedFrameRates(const QVideoEncoderSettings &settings, bool *continuous) const
623 {
624     Q_UNUSED(settings);
625 
626     if (m_handle == CAMERA_HANDLE_INVALID)
627         return QList<qreal>();
628 
629     int supported = 0;
630     double rates[20];
631     bool maxmin = false;
632 
633     /**
634      * Since in current version of the BB10 platform the video viewfinder encodes the video frames, we use
635      * the values as returned by camera_get_video_vf_framerates().
636      */
637     const camera_error_t result = camera_get_video_vf_framerates(m_handle, 20, &supported, rates, &maxmin);
638     if (result != CAMERA_EOK) {
639         qWarning() << "Unable to retrieve supported viewfinder framerates:" << result;
640         return QList<qreal>();
641     }
642 
643     QList<qreal> frameRates;
644     for (int i = 0; i < supported; ++i)
645         frameRates << rates[i];
646 
647     if (continuous)
648         *continuous = maxmin;
649 
650     return frameRates;
651 }
652 
videoSettings() const653 QVideoEncoderSettings BbCameraSession::videoSettings() const
654 {
655     return m_videoEncoderSettings;
656 }
657 
setVideoSettings(const QVideoEncoderSettings & settings)658 void BbCameraSession::setVideoSettings(const QVideoEncoderSettings &settings)
659 {
660     m_videoEncoderSettings = settings;
661 }
662 
audioSettings() const663 QAudioEncoderSettings BbCameraSession::audioSettings() const
664 {
665     return m_audioEncoderSettings;
666 }
667 
setAudioSettings(const QAudioEncoderSettings & settings)668 void BbCameraSession::setAudioSettings(const QAudioEncoderSettings &settings)
669 {
670     m_audioEncoderSettings = settings;
671 }
672 
updateReadyForCapture()673 void BbCameraSession::updateReadyForCapture()
674 {
675     emit readyForCaptureChanged(isReadyForCapture());
676 }
677 
imageCaptured(int requestId,const QImage & rawImage,const QString & fileName)678 void BbCameraSession::imageCaptured(int requestId, const QImage &rawImage, const QString &fileName)
679 {
680     QTransform transform;
681 
682     // subtract out the native rotation
683     transform.rotate(m_nativeCameraOrientation);
684 
685     // subtract out the current device orientation
686     if (m_device == cameraIdentifierRear())
687         transform.rotate(360 - m_orientationHandler->orientation());
688     else
689         transform.rotate(m_orientationHandler->orientation());
690 
691     const QImage image = rawImage.transformed(transform);
692 
693     // Generate snap preview as downscaled image
694     {
695         QSize previewSize = image.size();
696         int downScaleSteps = 0;
697         while (previewSize.width() > 800 && downScaleSteps < 8) {
698             previewSize.rwidth() /= 2;
699             previewSize.rheight() /= 2;
700             downScaleSteps++;
701         }
702 
703         const QImage snapPreview = image.scaled(previewSize);
704 
705         emit imageCaptured(requestId, snapPreview);
706     }
707 
708     if (m_captureDestination & QCameraImageCapture::CaptureToBuffer) {
709         QVideoFrame frame(image);
710 
711         emit imageAvailable(requestId, frame);
712     }
713 
714     if (m_captureDestination & QCameraImageCapture::CaptureToFile) {
715         const QString actualFileName = m_mediaStorageLocation.generateFileName(fileName,
716                                                                                QCamera::CaptureStillImage,
717                                                                                QLatin1String("IMG_"),
718                                                                                QLatin1String("jpg"));
719 
720         QFile file(actualFileName);
721         if (file.open(QFile::WriteOnly)) {
722             if (image.save(&file, "JPG")) {
723                 emit imageSaved(requestId, actualFileName);
724             } else {
725                 emit imageCaptureError(requestId, QCameraImageCapture::OutOfSpaceError, file.errorString());
726             }
727         } else {
728             const QString errorMessage = tr("Could not open destination file:\n%1").arg(actualFileName);
729             emit imageCaptureError(requestId, QCameraImageCapture::ResourceError, errorMessage);
730         }
731     }
732 }
733 
handleVideoRecordingPaused()734 void BbCameraSession::handleVideoRecordingPaused()
735 {
736     //TODO: implement once BB10 API supports pausing a video
737 }
738 
handleVideoRecordingResumed()739 void BbCameraSession::handleVideoRecordingResumed()
740 {
741     if (m_videoStatus == QMediaRecorder::StartingStatus) {
742         m_videoStatus = QMediaRecorder::RecordingStatus;
743         emit videoStatusChanged(m_videoStatus);
744 
745         m_videoRecordingDuration.restart();
746     }
747 }
748 
deviceOrientationChanged(int angle)749 void BbCameraSession::deviceOrientationChanged(int angle)
750 {
751     if (m_handle != CAMERA_HANDLE_INVALID)
752         camera_set_device_orientation(m_handle, angle);
753 }
754 
handleCameraPowerUp()755 void BbCameraSession::handleCameraPowerUp()
756 {
757     stopViewFinder();
758     startViewFinder();
759 }
760 
viewfinderFrameGrabbed(const QImage & image)761 void BbCameraSession::viewfinderFrameGrabbed(const QImage &image)
762 {
763     QTransform transform;
764 
765     // subtract out the native rotation
766     transform.rotate(m_nativeCameraOrientation);
767 
768     // subtract out the current device orientation
769     if (m_device == cameraIdentifierRear())
770         transform.rotate(360 - m_orientationHandler->viewfinderOrientation());
771     else
772         transform.rotate(m_orientationHandler->viewfinderOrientation());
773 
774     QImage frame = image.copy().transformed(transform);
775 
776     QMutexLocker locker(&m_surfaceMutex);
777     if (m_surface) {
778         if (frame.size() != m_surface->surfaceFormat().frameSize()) {
779             m_surface->stop();
780             m_surface->start(QVideoSurfaceFormat(frame.size(), QVideoFrame::Format_ARGB32));
781         }
782 
783         QVideoFrame videoFrame(frame);
784 
785         m_surface->present(videoFrame);
786     }
787 }
788 
openCamera()789 bool BbCameraSession::openCamera()
790 {
791     if (m_handle != CAMERA_HANDLE_INVALID) // camera is already open
792         return true;
793 
794     m_status = QCamera::LoadingStatus;
795     emit statusChanged(m_status);
796 
797     camera_unit_t unit = CAMERA_UNIT_REAR;
798     if (m_device == cameraIdentifierFront())
799         unit = CAMERA_UNIT_FRONT;
800     else if (m_device == cameraIdentifierRear())
801         unit = CAMERA_UNIT_REAR;
802     else if (m_device == cameraIdentifierDesktop())
803         unit = CAMERA_UNIT_DESKTOP;
804 
805     camera_error_t result = camera_open(unit, CAMERA_MODE_RW, &m_handle);
806     if (result != CAMERA_EOK) {
807         m_handle = CAMERA_HANDLE_INVALID;
808         m_status = QCamera::UnloadedStatus;
809         emit statusChanged(m_status);
810 
811         qWarning() << "Unable to open camera:" << result;
812         emit error(QCamera::CameraError, tr("Unable to open camera"));
813         return false;
814     }
815 
816     result = camera_get_native_orientation(m_handle, &m_nativeCameraOrientation);
817     if (result != CAMERA_EOK) {
818         qWarning() << "Unable to retrieve native camera orientation:" << result;
819         emit error(QCamera::CameraError, tr("Unable to retrieve native camera orientation"));
820         return false;
821     }
822 
823     m_previewIsVideo = camera_has_feature(m_handle, CAMERA_FEATURE_PREVIEWISVIDEO);
824 
825     m_status = QCamera::LoadedStatus;
826     emit statusChanged(m_status);
827 
828     emit cameraOpened();
829 
830     return true;
831 }
832 
closeCamera()833 void BbCameraSession::closeCamera()
834 {
835     if (m_handle == CAMERA_HANDLE_INVALID) // camera is closed already
836         return;
837 
838     m_status = QCamera::UnloadingStatus;
839     emit statusChanged(m_status);
840 
841     const camera_error_t result = camera_close(m_handle);
842     if (result != CAMERA_EOK) {
843         m_status = QCamera::LoadedStatus;
844         emit statusChanged(m_status);
845 
846         qWarning() << "Unable to close camera:" << result;
847         emit error(QCamera::CameraError, tr("Unable to close camera"));
848         return;
849     }
850 
851     m_handle = CAMERA_HANDLE_INVALID;
852 
853     m_status = QCamera::UnloadedStatus;
854     emit statusChanged(m_status);
855 }
856 
viewFinderStatusCallback(camera_handle_t handle,camera_devstatus_t status,uint16_t value,void * context)857 static void viewFinderStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context)
858 {
859     Q_UNUSED(handle)
860 
861     if (status == CAMERA_STATUS_FOCUS_CHANGE) {
862         BbCameraSession *session = static_cast<BbCameraSession*>(context);
863         QMetaObject::invokeMethod(session, "focusStatusChanged", Qt::QueuedConnection, Q_ARG(int, value));
864         return;
865     } else if (status == CAMERA_STATUS_POWERUP) {
866         BbCameraSession *session = static_cast<BbCameraSession*>(context);
867         QMetaObject::invokeMethod(session, "handleCameraPowerUp", Qt::QueuedConnection);
868     }
869 }
870 
startViewFinder()871 bool BbCameraSession::startViewFinder()
872 {
873     m_status = QCamera::StartingStatus;
874     emit statusChanged(m_status);
875 
876     QSize viewfinderResolution;
877     camera_error_t result = CAMERA_EOK;
878     if (m_captureMode & QCamera::CaptureStillImage) {
879         result = camera_start_photo_viewfinder(m_handle, 0, viewFinderStatusCallback, this);
880         viewfinderResolution = currentViewfinderResolution(QCamera::CaptureStillImage);
881     } else if (m_captureMode & QCamera::CaptureVideo) {
882         result = camera_start_video_viewfinder(m_handle, 0, viewFinderStatusCallback, this);
883         viewfinderResolution = currentViewfinderResolution(QCamera::CaptureVideo);
884     }
885 
886     if (result != CAMERA_EOK) {
887         qWarning() << "Unable to start viewfinder:" << result;
888         return false;
889     }
890 
891     const int angle = m_orientationHandler->viewfinderOrientation();
892 
893     const QSize rotatedSize = ((angle == 0 || angle == 180) ? viewfinderResolution
894                                                             : viewfinderResolution.transposed());
895 
896     m_surfaceMutex.lock();
897     if (m_surface) {
898         const bool ok = m_surface->start(QVideoSurfaceFormat(rotatedSize, QVideoFrame::Format_ARGB32));
899         if (!ok)
900             qWarning() << "Unable to start camera viewfinder surface";
901     }
902     m_surfaceMutex.unlock();
903 
904     m_status = QCamera::ActiveStatus;
905     emit statusChanged(m_status);
906 
907     return true;
908 }
909 
stopViewFinder()910 void BbCameraSession::stopViewFinder()
911 {
912     m_windowGrabber->stop();
913 
914     m_status = QCamera::StoppingStatus;
915     emit statusChanged(m_status);
916 
917     m_surfaceMutex.lock();
918     if (m_surface) {
919         m_surface->stop();
920     }
921     m_surfaceMutex.unlock();
922 
923     camera_error_t result = CAMERA_EOK;
924     if (m_captureMode & QCamera::CaptureStillImage)
925         result = camera_stop_photo_viewfinder(m_handle);
926     else if (m_captureMode & QCamera::CaptureVideo)
927         result = camera_stop_video_viewfinder(m_handle);
928 
929     if (result != CAMERA_EOK) {
930         qWarning() << "Unable to stop viewfinder:" << result;
931         return;
932     }
933 
934     m_status = QCamera::LoadedStatus;
935     emit statusChanged(m_status);
936 }
937 
applyConfiguration()938 void BbCameraSession::applyConfiguration()
939 {
940     if (m_captureMode & QCamera::CaptureStillImage) {
941         const QList<QSize> photoOutputResolutions = supportedResolutions(QCamera::CaptureStillImage);
942 
943         if (!m_imageEncoderSettings.resolution().isValid() || !photoOutputResolutions.contains(m_imageEncoderSettings.resolution()))
944             m_imageEncoderSettings.setResolution(photoOutputResolutions.first());
945 
946         const QSize photoResolution = m_imageEncoderSettings.resolution();
947         const qreal aspectRatio = static_cast<qreal>(photoResolution.width())/static_cast<qreal>(photoResolution.height());
948 
949         // apply viewfinder configuration
950         QSize viewfinderResolution;
951         QList<QSize> sizes = supportedViewfinderResolutions(QCamera::CaptureStillImage);
952         std::reverse(sizes.begin(), sizes.end()); // use smallest possible resolution
953         for (const QSize &size : qAsConst(sizes)) {
954             // search for viewfinder resolution with the same aspect ratio
955             if (qFuzzyCompare(aspectRatio, (static_cast<qreal>(size.width())/static_cast<qreal>(size.height())))) {
956                 viewfinderResolution = size;
957                 break;
958             }
959         }
960 
961         Q_ASSERT(viewfinderResolution.isValid());
962 
963         const QByteArray windowId = QString().sprintf("qcamera_vf_%p", this).toLatin1();
964         m_windowGrabber->setWindowId(windowId);
965 
966         const QByteArray windowGroupId = m_windowGrabber->windowGroupId();
967 
968         camera_error_t result = camera_set_photovf_property(m_handle,
969                                                             CAMERA_IMGPROP_WIN_GROUPID, windowGroupId.data(),
970                                                             CAMERA_IMGPROP_WIN_ID, windowId.data(),
971                                                             CAMERA_IMGPROP_WIDTH, viewfinderResolution.width(),
972                                                             CAMERA_IMGPROP_HEIGHT, viewfinderResolution.height(),
973                                                             CAMERA_IMGPROP_FORMAT, CAMERA_FRAMETYPE_NV12,
974                                                             CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation);
975 
976         if (result != CAMERA_EOK) {
977             qWarning() << "Unable to apply photo viewfinder settings:" << result;
978             return;
979         }
980 
981 
982         int jpegQuality = 100;
983         switch (m_imageEncoderSettings.quality()) {
984         case QMultimedia::VeryLowQuality:
985             jpegQuality = 20;
986             break;
987         case QMultimedia::LowQuality:
988             jpegQuality = 40;
989             break;
990         case QMultimedia::NormalQuality:
991             jpegQuality = 60;
992             break;
993         case QMultimedia::HighQuality:
994             jpegQuality = 80;
995             break;
996         case QMultimedia::VeryHighQuality:
997             jpegQuality = 100;
998             break;
999         }
1000 
1001         // apply photo configuration
1002         result = camera_set_photo_property(m_handle,
1003                                            CAMERA_IMGPROP_WIDTH, photoResolution.width(),
1004                                            CAMERA_IMGPROP_HEIGHT, photoResolution.height(),
1005                                            CAMERA_IMGPROP_JPEGQFACTOR, jpegQuality,
1006                                            CAMERA_IMGPROP_ROTATION, 360 - m_nativeCameraOrientation);
1007 
1008         if (result != CAMERA_EOK) {
1009             qWarning() << "Unable to apply photo settings:" << result;
1010             return;
1011         }
1012 
1013     } else if (m_captureMode & QCamera::CaptureVideo) {
1014         applyVideoSettings();
1015     }
1016 
1017     startViewFinder();
1018 }
1019 
videoRecordingStatusCallback(camera_handle_t handle,camera_devstatus_t status,uint16_t value,void * context)1020 static void videoRecordingStatusCallback(camera_handle_t handle, camera_devstatus_t status, uint16_t value, void *context)
1021 {
1022     Q_UNUSED(handle)
1023     Q_UNUSED(value)
1024 
1025     if (status == CAMERA_STATUS_VIDEO_PAUSE) {
1026         BbCameraSession *session = static_cast<BbCameraSession*>(context);
1027         QMetaObject::invokeMethod(session, "handleVideoRecordingPaused", Qt::QueuedConnection);
1028     } else if (status == CAMERA_STATUS_VIDEO_RESUME) {
1029         BbCameraSession *session = static_cast<BbCameraSession*>(context);
1030         QMetaObject::invokeMethod(session, "handleVideoRecordingResumed", Qt::QueuedConnection);
1031     }
1032 }
1033 
startVideoRecording()1034 bool BbCameraSession::startVideoRecording()
1035 {
1036     m_videoRecordingDuration.invalidate();
1037 
1038     m_videoStatus = QMediaRecorder::StartingStatus;
1039     emit videoStatusChanged(m_videoStatus);
1040 
1041     if (m_videoOutputLocation.isEmpty())
1042         m_videoOutputLocation = m_mediaStorageLocation.generateFileName(QLatin1String("VID_"), m_mediaStorageLocation.defaultDir(QCamera::CaptureVideo), QLatin1String("mp4"));
1043 
1044     emit actualLocationChanged(m_videoOutputLocation);
1045 
1046     const camera_error_t result = camera_start_video(m_handle, QFile::encodeName(m_videoOutputLocation), 0, videoRecordingStatusCallback, this);
1047     if (result != CAMERA_EOK) {
1048         m_videoStatus = QMediaRecorder::LoadedStatus;
1049         emit videoStatusChanged(m_videoStatus);
1050 
1051         emit videoError(QMediaRecorder::ResourceError, tr("Unable to start video recording"));
1052         return false;
1053     }
1054 
1055     return true;
1056 }
1057 
stopVideoRecording()1058 void BbCameraSession::stopVideoRecording()
1059 {
1060     m_videoStatus = QMediaRecorder::FinalizingStatus;
1061     emit videoStatusChanged(m_videoStatus);
1062 
1063     const camera_error_t result = camera_stop_video(m_handle);
1064     if (result != CAMERA_EOK) {
1065         emit videoError(QMediaRecorder::ResourceError, tr("Unable to stop video recording"));
1066     }
1067 
1068     m_videoStatus = QMediaRecorder::LoadedStatus;
1069     emit videoStatusChanged(m_videoStatus);
1070 
1071     m_videoRecordingDuration.invalidate();
1072 }
1073 
isCaptureModeSupported(camera_handle_t handle,QCamera::CaptureModes mode) const1074 bool BbCameraSession::isCaptureModeSupported(camera_handle_t handle, QCamera::CaptureModes mode) const
1075 {
1076     if (mode & QCamera::CaptureStillImage)
1077         return camera_has_feature(handle, CAMERA_FEATURE_PHOTO);
1078 
1079     if (mode & QCamera::CaptureVideo)
1080         return camera_has_feature(handle, CAMERA_FEATURE_VIDEO);
1081 
1082     return false;
1083 }
1084 
supportedResolutions(QCamera::CaptureMode mode) const1085 QList<QSize> BbCameraSession::supportedResolutions(QCamera::CaptureMode mode) const
1086 {
1087     Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID);
1088 
1089     QList<QSize> list;
1090 
1091     camera_error_t result = CAMERA_EOK;
1092     camera_res_t resolutions[20];
1093     unsigned int supported = 0;
1094 
1095     if (mode == QCamera::CaptureStillImage)
1096         result = camera_get_photo_output_resolutions(m_handle, CAMERA_FRAMETYPE_JPEG, 20, &supported, resolutions);
1097     else if (mode == QCamera::CaptureVideo)
1098         result = camera_get_video_output_resolutions(m_handle, 20, &supported, resolutions);
1099 
1100     if (result != CAMERA_EOK)
1101         return list;
1102 
1103     for (unsigned int i = 0; i < supported; ++i)
1104         list << QSize(resolutions[i].width, resolutions[i].height);
1105 
1106     return list;
1107 }
1108 
supportedViewfinderResolutions(QCamera::CaptureMode mode) const1109 QList<QSize> BbCameraSession::supportedViewfinderResolutions(QCamera::CaptureMode mode) const
1110 {
1111     Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID);
1112 
1113     QList<QSize> list;
1114 
1115     camera_error_t result = CAMERA_EOK;
1116     camera_res_t resolutions[20];
1117     unsigned int supported = 0;
1118 
1119     if (mode == QCamera::CaptureStillImage)
1120         result = camera_get_photo_vf_resolutions(m_handle, 20, &supported, resolutions);
1121     else if (mode == QCamera::CaptureVideo)
1122         result = camera_get_video_vf_resolutions(m_handle, 20, &supported, resolutions);
1123 
1124     if (result != CAMERA_EOK)
1125         return list;
1126 
1127     for (unsigned int i = 0; i < supported; ++i)
1128         list << QSize(resolutions[i].width, resolutions[i].height);
1129 
1130     return list;
1131 }
1132 
currentViewfinderResolution(QCamera::CaptureMode mode) const1133 QSize BbCameraSession::currentViewfinderResolution(QCamera::CaptureMode mode) const
1134 {
1135     Q_ASSERT(m_handle != CAMERA_HANDLE_INVALID);
1136 
1137     camera_error_t result = CAMERA_EOK;
1138     int width = 0;
1139     int height = 0;
1140 
1141     if (mode == QCamera::CaptureStillImage)
1142         result = camera_get_photovf_property(m_handle, CAMERA_IMGPROP_WIDTH, &width,
1143                                                        CAMERA_IMGPROP_HEIGHT, &height);
1144     else if (mode == QCamera::CaptureVideo)
1145         result = camera_get_videovf_property(m_handle, CAMERA_IMGPROP_WIDTH, &width,
1146                                                        CAMERA_IMGPROP_HEIGHT, &height);
1147 
1148     if (result != CAMERA_EOK)
1149         return QSize();
1150 
1151     return QSize(width, height);
1152 }
1153 
1154 QT_END_NAMESPACE
1155