1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
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** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "camera.h"
52#include "ui_camera.h"
53#include "videosettings.h"
54#include "imagesettings.h"
55
56#include <QMediaService>
57#include <QMediaRecorder>
58#include <QCameraViewfinder>
59#include <QCameraInfo>
60#include <QMediaMetaData>
61
62#include <QMessageBox>
63#include <QPalette>
64
65#include <QtWidgets>
66
67Q_DECLARE_METATYPE(QCameraInfo)
68
69Camera::Camera() : ui(new Ui::Camera)
70{
71 ui->setupUi(this);
72
73 //Camera devices:
74
75 QActionGroup *videoDevicesGroup = new QActionGroup(this);
76 videoDevicesGroup->setExclusive(true);
77 const QList<QCameraInfo> availableCameras = QCameraInfo::availableCameras();
78 for (const QCameraInfo &cameraInfo : availableCameras) {
79 QAction *videoDeviceAction = new QAction(cameraInfo.description(), videoDevicesGroup);
80 videoDeviceAction->setCheckable(true);
81 videoDeviceAction->setData(QVariant::fromValue(cameraInfo));
82 if (cameraInfo == QCameraInfo::defaultCamera())
83 videoDeviceAction->setChecked(true);
84
85 ui->menuDevices->addAction(videoDeviceAction);
86 }
87
88 connect(videoDevicesGroup, &QActionGroup::triggered, this, &Camera::updateCameraDevice);
89 connect(ui->captureWidget, &QTabWidget::currentChanged, this, &Camera::updateCaptureMode);
90
91 setCamera(QCameraInfo::defaultCamera());
92}
93
94void Camera::setCamera(const QCameraInfo &cameraInfo)
95{
96 m_camera.reset(new QCamera(cameraInfo));
97
98 connect(m_camera.data(), &QCamera::stateChanged, this, &Camera::updateCameraState);
99 connect(m_camera.data(), QOverload<QCamera::Error>::of(&QCamera::error), this, &Camera::displayCameraError);
100
101 m_mediaRecorder.reset(new QMediaRecorder(m_camera.data()));
102 connect(m_mediaRecorder.data(), &QMediaRecorder::stateChanged, this, &Camera::updateRecorderState);
103
104 m_imageCapture.reset(new QCameraImageCapture(m_camera.data()));
105
106 connect(m_mediaRecorder.data(), &QMediaRecorder::durationChanged, this, &Camera::updateRecordTime);
107 connect(m_mediaRecorder.data(), QOverload<QMediaRecorder::Error>::of(&QMediaRecorder::error),
108 this, &Camera::displayRecorderError);
109
110 m_mediaRecorder->setMetaData(QMediaMetaData::Title, QVariant(QLatin1String("Test Title")));
111
112 connect(ui->exposureCompensation, &QAbstractSlider::valueChanged, this, &Camera::setExposureCompensation);
113
114 m_camera->setViewfinder(ui->viewfinder);
115
116 updateCameraState(m_camera->state());
117 updateLockStatus(m_camera->lockStatus(), QCamera::UserRequest);
118 updateRecorderState(m_mediaRecorder->state());
119
120 connect(m_imageCapture.data(), &QCameraImageCapture::readyForCaptureChanged, this, &Camera::readyForCapture);
121 connect(m_imageCapture.data(), &QCameraImageCapture::imageCaptured, this, &Camera::processCapturedImage);
122 connect(m_imageCapture.data(), &QCameraImageCapture::imageSaved, this, &Camera::imageSaved);
123 connect(m_imageCapture.data(), QOverload<int, QCameraImageCapture::Error, const QString &>::of(&QCameraImageCapture::error),
124 this, &Camera::displayCaptureError);
125
126 connect(m_camera.data(), QOverload<QCamera::LockStatus, QCamera::LockChangeReason>::of(&QCamera::lockStatusChanged),
127 this, &Camera::updateLockStatus);
128
129 ui->captureWidget->setTabEnabled(0, (m_camera->isCaptureModeSupported(QCamera::CaptureStillImage)));
130 ui->captureWidget->setTabEnabled(1, (m_camera->isCaptureModeSupported(QCamera::CaptureVideo)));
131
132 updateCaptureMode();
133 m_camera->start();
134}
135
136void Camera::keyPressEvent(QKeyEvent * event)
137{
138 if (event->isAutoRepeat())
139 return;
140
141 switch (event->key()) {
142 case Qt::Key_CameraFocus:
143 displayViewfinder();
144 m_camera->searchAndLock();
145 event->accept();
146 break;
147 case Qt::Key_Camera:
148 if (m_camera->captureMode() == QCamera::CaptureStillImage) {
149 takeImage();
150 } else {
151 if (m_mediaRecorder->state() == QMediaRecorder::RecordingState)
152 stop();
153 else
154 record();
155 }
156 event->accept();
157 break;
158 default:
159 QMainWindow::keyPressEvent(event);
160 }
161}
162
163void Camera::keyReleaseEvent(QKeyEvent *event)
164{
165 if (event->isAutoRepeat())
166 return;
167
168 switch (event->key()) {
169 case Qt::Key_CameraFocus:
170 m_camera->unlock();
171 break;
172 default:
173 QMainWindow::keyReleaseEvent(event);
174 }
175}
176
177void Camera::updateRecordTime()
178{
179 QString str = QString("Recorded %1 sec").arg(m_mediaRecorder->duration()/1000);
180 ui->statusbar->showMessage(str);
181}
182
183void Camera::processCapturedImage(int requestId, const QImage& img)
184{
185 Q_UNUSED(requestId);
186 QImage scaledImage = img.scaled(ui->viewfinder->size(),
187 Qt::KeepAspectRatio,
188 Qt::SmoothTransformation);
189
190 ui->lastImagePreviewLabel->setPixmap(QPixmap::fromImage(scaledImage));
191
192 // Display captured image for 4 seconds.
193 displayCapturedImage();
194 QTimer::singleShot(4000, this, &Camera::displayViewfinder);
195}
196
197void Camera::configureCaptureSettings()
198{
199 switch (m_camera->captureMode()) {
200 case QCamera::CaptureStillImage:
201 configureImageSettings();
202 break;
203 case QCamera::CaptureVideo:
204 configureVideoSettings();
205 break;
206 default:
207 break;
208 }
209}
210
211void Camera::configureVideoSettings()
212{
213 VideoSettings settingsDialog(m_mediaRecorder.data());
214 settingsDialog.setWindowFlags(settingsDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
215
216 settingsDialog.setAudioSettings(m_audioSettings);
217 settingsDialog.setVideoSettings(m_videoSettings);
218 settingsDialog.setFormat(m_videoContainerFormat);
219
220 if (settingsDialog.exec()) {
221 m_audioSettings = settingsDialog.audioSettings();
222 m_videoSettings = settingsDialog.videoSettings();
223 m_videoContainerFormat = settingsDialog.format();
224
225 m_mediaRecorder->setEncodingSettings(
226 m_audioSettings,
227 m_videoSettings,
228 m_videoContainerFormat);
229
230 m_camera->unload();
231 m_camera->start();
232 }
233}
234
235void Camera::configureImageSettings()
236{
237 ImageSettings settingsDialog(m_imageCapture.data());
238 settingsDialog.setWindowFlags(settingsDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint);
239
240 settingsDialog.setImageSettings(m_imageSettings);
241
242 if (settingsDialog.exec()) {
243 m_imageSettings = settingsDialog.imageSettings();
244 m_imageCapture->setEncodingSettings(m_imageSettings);
245 }
246}
247
248void Camera::record()
249{
250 m_mediaRecorder->record();
251 updateRecordTime();
252}
253
254void Camera::pause()
255{
256 m_mediaRecorder->pause();
257}
258
259void Camera::stop()
260{
261 m_mediaRecorder->stop();
262}
263
264void Camera::setMuted(bool muted)
265{
266 m_mediaRecorder->setMuted(muted);
267}
268
269void Camera::toggleLock()
270{
271 switch (m_camera->lockStatus()) {
272 case QCamera::Searching:
273 case QCamera::Locked:
274 m_camera->unlock();
275 break;
276 case QCamera::Unlocked:
277 m_camera->searchAndLock();
278 }
279}
280
281void Camera::updateLockStatus(QCamera::LockStatus status, QCamera::LockChangeReason reason)
282{
283 QColor indicationColor = Qt::black;
284
285 switch (status) {
286 case QCamera::Searching:
287 indicationColor = Qt::yellow;
288 ui->statusbar->showMessage(tr("Focusing..."));
289 ui->lockButton->setText(tr("Focusing..."));
290 break;
291 case QCamera::Locked:
292 indicationColor = Qt::darkGreen;
293 ui->lockButton->setText(tr("Unlock"));
294 ui->statusbar->showMessage(tr("Focused"), 2000);
295 break;
296 case QCamera::Unlocked:
297 indicationColor = reason == QCamera::LockFailed ? Qt::red : Qt::black;
298 ui->lockButton->setText(tr("Focus"));
299 if (reason == QCamera::LockFailed)
300 ui->statusbar->showMessage(tr("Focus Failed"), 2000);
301 }
302
303 QPalette palette = ui->lockButton->palette();
304 palette.setColor(QPalette::ButtonText, indicationColor);
305 ui->lockButton->setPalette(palette);
306}
307
308void Camera::takeImage()
309{
310 m_isCapturingImage = true;
311 m_imageCapture->capture();
312}
313
314void Camera::displayCaptureError(int id, const QCameraImageCapture::Error error, const QString &errorString)
315{
316 Q_UNUSED(id);
317 Q_UNUSED(error);
318 QMessageBox::warning(this, tr("Image Capture Error"), errorString);
319 m_isCapturingImage = false;
320}
321
322void Camera::startCamera()
323{
324 m_camera->start();
325}
326
327void Camera::stopCamera()
328{
329 m_camera->stop();
330}
331
332void Camera::updateCaptureMode()
333{
334 int tabIndex = ui->captureWidget->currentIndex();
335 QCamera::CaptureModes captureMode = tabIndex == 0 ? QCamera::CaptureStillImage : QCamera::CaptureVideo;
336
337 if (m_camera->isCaptureModeSupported(captureMode))
338 m_camera->setCaptureMode(captureMode);
339}
340
341void Camera::updateCameraState(QCamera::State state)
342{
343 switch (state) {
344 case QCamera::ActiveState:
345 ui->actionStartCamera->setEnabled(false);
346 ui->actionStopCamera->setEnabled(true);
347 ui->captureWidget->setEnabled(true);
348 ui->actionSettings->setEnabled(true);
349 break;
350 case QCamera::UnloadedState:
351 case QCamera::LoadedState:
352 ui->actionStartCamera->setEnabled(true);
353 ui->actionStopCamera->setEnabled(false);
354 ui->captureWidget->setEnabled(false);
355 ui->actionSettings->setEnabled(false);
356 }
357}
358
359void Camera::updateRecorderState(QMediaRecorder::State state)
360{
361 switch (state) {
362 case QMediaRecorder::StoppedState:
363 ui->recordButton->setEnabled(true);
364 ui->pauseButton->setEnabled(true);
365 ui->stopButton->setEnabled(false);
366 break;
367 case QMediaRecorder::PausedState:
368 ui->recordButton->setEnabled(true);
369 ui->pauseButton->setEnabled(false);
370 ui->stopButton->setEnabled(true);
371 break;
372 case QMediaRecorder::RecordingState:
373 ui->recordButton->setEnabled(false);
374 ui->pauseButton->setEnabled(true);
375 ui->stopButton->setEnabled(true);
376 break;
377 }
378}
379
380void Camera::setExposureCompensation(int index)
381{
382 m_camera->exposure()->setExposureCompensation(index*0.5);
383}
384
385void Camera::displayRecorderError()
386{
387 QMessageBox::warning(this, tr("Capture Error"), m_mediaRecorder->errorString());
388}
389
390void Camera::displayCameraError()
391{
392 QMessageBox::warning(this, tr("Camera Error"), m_camera->errorString());
393}
394
395void Camera::updateCameraDevice(QAction *action)
396{
397 setCamera(qvariant_cast<QCameraInfo>(action->data()));
398}
399
400void Camera::displayViewfinder()
401{
402 ui->stackedWidget->setCurrentIndex(0);
403}
404
405void Camera::displayCapturedImage()
406{
407 ui->stackedWidget->setCurrentIndex(1);
408}
409
410void Camera::readyForCapture(bool ready)
411{
412 ui->takeImageButton->setEnabled(ready);
413}
414
415void Camera::imageSaved(int id, const QString &fileName)
416{
417 Q_UNUSED(id);
418 ui->statusbar->showMessage(tr("Captured \"%1\"").arg(QDir::toNativeSeparators(fileName)));
419
420 m_isCapturingImage = false;
421 if (m_applicationExiting)
422 close();
423}
424
425void Camera::closeEvent(QCloseEvent *event)
426{
427 if (m_isCapturingImage) {
428 setEnabled(false);
429 m_applicationExiting = true;
430 event->ignore();
431 } else {
432 event->accept();
433 }
434}
435