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