1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qffmpegimagecapture_p.h"
5#include <private/qplatformmediaformatinfo_p.h>
6#include <private/qplatformcamera_p.h>
7#include <private/qplatformimagecapture_p.h>
8#include <qvideoframeformat.h>
9#include <private/qmediastoragelocation_p.h>
10#include <qimagewriter.h>
11
12#include <QtCore/QDebug>
13#include <QtCore/QDir>
14#include <qstandardpaths.h>
15
16#include <qloggingcategory.h>
17
18QT_BEGIN_NAMESPACE
19
20// Probably, might be increased. To be investigated and tested on Android implementation
21static constexpr int MaxPendingImagesCount = 1;
22
23static Q_LOGGING_CATEGORY(qLcImageCapture, "qt.multimedia.imageCapture")
24
25QFFmpegImageCapture::QFFmpegImageCapture(QImageCapture *parent)
26 : QPlatformImageCapture(parent)
27{
28 qRegisterMetaType<QVideoFrame>();
29}
30
31QFFmpegImageCapture::~QFFmpegImageCapture()
32{
33}
34
35bool QFFmpegImageCapture::isReadyForCapture() const
36{
37 return m_isReadyForCapture;
38}
39
40static const char *extensionForFormat(QImageCapture::FileFormat format)
41{
42 const char *fmt = "jpg";
43 switch (format) {
44 case QImageCapture::UnspecifiedFormat:
45 case QImageCapture::JPEG:
46 fmt = "jpg";
47 break;
48 case QImageCapture::PNG:
49 fmt = "png";
50 break;
51 case QImageCapture::WebP:
52 fmt = "webp";
53 break;
54 case QImageCapture::Tiff:
55 fmt = "tiff";
56 break;
57 }
58 return fmt;
59}
60
61int QFFmpegImageCapture::capture(const QString &fileName)
62{
63 QString path = QMediaStorageLocation::generateFileName(requestedName: fileName, type: QStandardPaths::PicturesLocation, extension: QLatin1String(extensionForFormat(format: m_settings.format())));
64 return doCapture(fileName: path);
65}
66
67int QFFmpegImageCapture::captureToBuffer()
68{
69 return doCapture(fileName: QString());
70}
71
72int QFFmpegImageCapture::doCapture(const QString &fileName)
73{
74 qCDebug(qLcImageCapture) << "do capture";
75 if (!m_session) {
76 //emit error in the next event loop,
77 //so application can associate it with returned request id.
78 QMetaObject::invokeMethod(obj: this, member: "error", c: Qt::QueuedConnection,
79 Q_ARG(int, -1),
80 Q_ARG(int, QImageCapture::ResourceError),
81 Q_ARG(QString, QPlatformImageCapture::msgImageCaptureNotSet()));
82
83 qCDebug(qLcImageCapture) << "error 1";
84 return -1;
85 }
86 if (!m_videoSource) {
87 //emit error in the next event loop,
88 //so application can associate it with returned request id.
89 QMetaObject::invokeMethod(obj: this, member: "error", c: Qt::QueuedConnection,
90 Q_ARG(int, -1),
91 Q_ARG(int, QImageCapture::ResourceError),
92 Q_ARG(QString,tr("No camera available.")));
93
94 qCDebug(qLcImageCapture) << "error 2";
95 return -1;
96 }
97 if (m_pendingImages.size() >= MaxPendingImagesCount) {
98 //emit error in the next event loop,
99 //so application can associate it with returned request id.
100 QMetaObject::invokeMethod(obj: this, member: "error", c: Qt::QueuedConnection,
101 Q_ARG(int, -1),
102 Q_ARG(int, QImageCapture::NotReadyError),
103 Q_ARG(QString, QPlatformImageCapture::msgCameraNotReady()));
104
105 qCDebug(qLcImageCapture) << "error 3";
106 return -1;
107 }
108
109 m_lastId++;
110
111 m_pendingImages.enqueue(t: { .id: m_lastId, .filename: fileName, .metaData: QMediaMetaData{} });
112 updateReadyForCapture();
113
114 return m_lastId;
115}
116
117void QFFmpegImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
118{
119 auto *captureSession = static_cast<QFFmpegMediaCaptureSession *>(session);
120 if (m_session == captureSession)
121 return;
122
123 if (m_session) {
124 m_session->disconnect(receiver: this);
125 m_lastId = 0;
126 m_pendingImages.clear();
127 }
128
129 m_session = captureSession;
130
131 if (m_session)
132 connect(sender: m_session, signal: &QFFmpegMediaCaptureSession::primaryActiveVideoSourceChanged, context: this,
133 slot: &QFFmpegImageCapture::onVideoSourceChanged);
134
135 onVideoSourceChanged();
136}
137
138void QFFmpegImageCapture::updateReadyForCapture()
139{
140 const bool ready = m_session && m_pendingImages.size() < MaxPendingImagesCount && m_videoSource
141 && m_videoSource->isActive();
142
143 qCDebug(qLcImageCapture) << "updateReadyForCapture" << ready;
144
145 if (std::exchange(obj&: m_isReadyForCapture, new_val: ready) != ready)
146 emit readyForCaptureChanged(ready);
147}
148
149void QFFmpegImageCapture::newVideoFrame(const QVideoFrame &frame)
150{
151 if (m_pendingImages.empty())
152 return;
153
154 auto pending = m_pendingImages.dequeue();
155
156 qCDebug(qLcImageCapture) << "Taking image" << pending.id;
157
158 emit imageExposed(requestId: pending.id);
159 // ### Add metadata from the AVFrame
160 emit imageMetadataAvailable(id: pending.id, pending.metaData);
161 emit imageAvailable(requestId: pending.id, buffer: frame);
162 QImage image = frame.toImage();
163 if (m_settings.resolution().isValid() && m_settings.resolution() != image.size())
164 image = image.scaled(s: m_settings.resolution());
165
166 emit imageCaptured(requestId: pending.id, preview: image);
167 if (!pending.filename.isEmpty()) {
168 const char *fmt = nullptr;
169 switch (m_settings.format()) {
170 case QImageCapture::UnspecifiedFormat:
171 case QImageCapture::JPEG:
172 fmt = "jpeg";
173 break;
174 case QImageCapture::PNG:
175 fmt = "png";
176 break;
177 case QImageCapture::WebP:
178 fmt = "webp";
179 break;
180 case QImageCapture::Tiff:
181 fmt = "tiff";
182 break;
183 }
184 int quality = -1;
185 switch (m_settings.quality()) {
186 case QImageCapture::VeryLowQuality:
187 quality = 25;
188 break;
189 case QImageCapture::LowQuality:
190 quality = 50;
191 break;
192 case QImageCapture::NormalQuality:
193 break;
194 case QImageCapture::HighQuality:
195 quality = 75;
196 break;
197 case QImageCapture::VeryHighQuality:
198 quality = 99;
199 break;
200 }
201
202 QImageWriter writer(pending.filename, fmt);
203 writer.setQuality(quality);
204
205 if (writer.write(image)) {
206 emit imageSaved(requestId: pending.id, fileName: pending.filename);
207 } else {
208 QImageCapture::Error err = QImageCapture::ResourceError;
209 if (writer.error() == QImageWriter::UnsupportedFormatError)
210 err = QImageCapture::FormatError;
211 emit error(id: pending.id, error: err, errorString: writer.errorString());
212 }
213 }
214
215 updateReadyForCapture();
216}
217
218void QFFmpegImageCapture::setupVideoSourceConnections()
219{
220 connect(sender: m_videoSource, signal: &QPlatformCamera::newVideoFrame, context: this,
221 slot: &QFFmpegImageCapture::newVideoFrame);
222}
223
224QPlatformVideoSource *QFFmpegImageCapture::videoSource() const
225{
226 return m_videoSource;
227}
228
229void QFFmpegImageCapture::onVideoSourceChanged()
230{
231 if (m_videoSource)
232 m_videoSource->disconnect(receiver: this);
233
234 m_videoSource = m_session ? m_session->primaryActiveVideoSource() : nullptr;
235
236 // TODO: optimize, setup the connection only when the capture is ready
237 if (m_videoSource)
238 setupVideoSourceConnections();
239
240 updateReadyForCapture();
241}
242
243QImageEncoderSettings QFFmpegImageCapture::imageSettings() const
244{
245 return m_settings;
246}
247
248void QFFmpegImageCapture::setImageSettings(const QImageEncoderSettings &settings)
249{
250 auto s = settings;
251 const auto supportedFormats = QPlatformMediaIntegration::instance()->formatInfo()->imageFormats;
252 if (supportedFormats.isEmpty()) {
253 emit error(id: -1, error: QImageCapture::FormatError, errorString: "No image formats supported, can't capture.");
254 return;
255 }
256 if (s.format() == QImageCapture::UnspecifiedFormat) {
257 auto f = QImageCapture::JPEG;
258 if (!supportedFormats.contains(t: f))
259 f = supportedFormats.first();
260 s.setFormat(f);
261 } else if (!supportedFormats.contains(t: settings.format())) {
262 emit error(id: -1, error: QImageCapture::FormatError, errorString: "Image format not supported.");
263 return;
264 }
265
266 m_settings = settings;
267}
268
269QT_END_NAMESPACE
270
271#include "moc_qffmpegimagecapture_p.cpp"
272

source code of qtmultimedia/src/plugins/multimedia/ffmpeg/qffmpegimagecapture.cpp