Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qffmpegimagecapture.cpp
Go to the documentation of this file.
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
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
19
20using namespace Qt::StringLiterals;
21
22// Probably, might be increased. To be investigated and tested on Android implementation
23static constexpr int MaxPendingImagesCount = 1;
24
25Q_STATIC_LOGGING_CATEGORY(qLcImageCapture, "qt.multimedia.imageCapture")
26
27QFFmpegImageCapture::QFFmpegImageCapture(QImageCapture *parent)
28 : QPlatformImageCapture(parent)
29{
30 qRegisterMetaType<QVideoFrame>();
31}
32
34
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(fileName, QStandardPaths::PicturesLocation, QLatin1String(extensionForFormat(m_settings.format())));
64 return doCapture(path);
65}
66
68{
69 return doCapture(QString());
70}
71
72int QFFmpegImageCapture::doCapture(const QString &fileName)
73{
74 using namespace Qt::Literals::StringLiterals;
75
76 if (!m_session) {
77 //emit error in the next event loop,
78 //so application can associate it with returned request id.
79 QMetaObject::invokeMethod(
80 this,
81 [this]() {
82 Q_EMIT error(
83 -1,
84 QImageCapture::ResourceError,
85 QPlatformImageCapture::msgImageCaptureNotSet());
86 });
87 return -1;
88 }
89
90 if (!m_videoSource) {
91 //emit error in the next event loop,
92 //so application can associate it with returned request id.
93 QMetaObject::invokeMethod(
94 this,
95 [this]() {
96 Q_EMIT error(
97 -1,
98 QImageCapture::ResourceError,
99 u"No camera available."_s);
100 });
101 return -1;
102 }
103
104 if (m_pendingImages.size() >= MaxPendingImagesCount) {
105 //emit error in the next event loop,
106 //so application can associate it with returned request id.
107 QMetaObject::invokeMethod(
108 this,
109 [this]() {
110 Q_EMIT error(
111 -1,
112 QImageCapture::NotReadyError,
113 QPlatformImageCapture::msgCameraNotReady());
114 });
115 return -1;
116 }
117
118 m_lastId++;
119
120 m_pendingImages.enqueue({ m_lastId, fileName, QMediaMetaData{} });
122
123 return m_lastId;
124}
125
126void QFFmpegImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
127{
128 auto *captureSession = static_cast<QFFmpegMediaCaptureSession *>(session);
129 if (m_session == captureSession)
130 return;
131
132 if (m_session) {
133 m_session->disconnect(this);
134 m_lastId = 0;
135 m_pendingImages.clear();
136 }
137
138 m_session = captureSession;
139
140 if (m_session) {
141 connect(m_session, &QFFmpegMediaCaptureSession::primaryActiveVideoSourceChanged, this,
143 connect(m_session, &QFFmpegMediaCaptureSession::cameraChanged, this,
145 }
146
148
149 if (m_session)
151}
152
153void QFFmpegImageCapture::cancelPendingImage(QImageCapture::Error error, const QString &errorMsg)
154{
155 if (m_pendingImages.empty()) {
156 qCDebug(qLcImageCapture) <<
157 "QImageCapture backend received an event to cancel a pending capture, "
158 "but no captures are pending. Most likely an internal Qt bug.";
159 return;
160 }
161
162 PendingImage cancelledImage = m_pendingImages.dequeue();
163
164 Q_EMIT QPlatformImageCapture::error(cancelledImage.id, error, errorMsg);
165
166 updateReadyForCapture();
167}
168
170{
171 const bool ready = m_session && m_pendingImages.size() < MaxPendingImagesCount && m_videoSource
172 && m_videoSource->isActive();
173
174 qCDebug(qLcImageCapture) << "updateReadyForCapture" << ready;
175
176 if (std::exchange(m_isReadyForCapture, ready) != ready)
177 Q_EMIT readyForCaptureChanged(ready);
178}
179
180void QFFmpegImageCapture::newVideoFrame(const QVideoFrame &frame)
181{
182 if (m_pendingImages.empty())
183 return;
184
185 auto pending = m_pendingImages.dequeue();
186
187 qCDebug(qLcImageCapture) << "Taking image" << pending.id;
188
189 Q_EMIT imageExposed(pending.id);
190 // ### Add metadata from the AVFrame
191 Q_EMIT imageMetadataAvailable(pending.id, pending.metaData);
192 Q_EMIT imageAvailable(pending.id, frame);
193 QImage image = frame.toImage();
194 // Assume image is rotated if its orientation differs from the requested resolution
195 const QSize resolution = m_settings.resolution();
196 const bool isRotated = image.width() != image.height()
197 && resolution.width() != resolution.height()
198 && (image.width() > image.height()) != (resolution.width() > resolution.height());
199 const QSize targetSize = isRotated ? resolution.transposed() : resolution;
200 if (targetSize.isValid() && image.size() != targetSize) {
201 qCWarning(qLcImageCapture)
202 << "Image needed to be scaled to match requested resolution (" << image.size()
203 << " -> " << targetSize
204 << ", consider using a resolution supported natively by the camera";
205 image = image.scaled(targetSize);
206 }
207
208 Q_EMIT imageCaptured(pending.id, image);
209 if (!pending.filename.isEmpty()) {
210 const char *fmt = nullptr;
211 switch (m_settings.format()) {
212 case QImageCapture::UnspecifiedFormat:
213 case QImageCapture::JPEG:
214 fmt = "jpeg";
215 break;
216 case QImageCapture::PNG:
217 fmt = "png";
218 break;
219 case QImageCapture::WebP:
220 fmt = "webp";
221 break;
222 case QImageCapture::Tiff:
223 fmt = "tiff";
224 break;
225 }
226 int quality = -1;
227 switch (m_settings.quality()) {
228 case QImageCapture::VeryLowQuality:
229 quality = 25;
230 break;
231 case QImageCapture::LowQuality:
232 quality = 50;
233 break;
234 case QImageCapture::NormalQuality:
235 break;
236 case QImageCapture::HighQuality:
237 quality = 75;
238 break;
239 case QImageCapture::VeryHighQuality:
240 quality = 99;
241 break;
242 }
243
244 QImageWriter writer(pending.filename, fmt);
245 writer.setQuality(quality);
246
247 if (writer.write(image)) {
248 Q_EMIT imageSaved(pending.id, pending.filename);
249 } else {
250 QImageCapture::Error err = QImageCapture::ResourceError;
251 if (writer.error() == QImageWriter::UnsupportedFormatError)
252 err = QImageCapture::FormatError;
253 Q_EMIT error(pending.id, err, writer.errorString());
254 }
255 }
256
258}
259
261{
262 connect(m_videoSource, &QPlatformCamera::newVideoFrame, this,
263 &QFFmpegImageCapture::newVideoFrame);
264}
265
267{
268 return m_videoSource;
269}
270
272{
273 if (m_videoSource)
274 m_videoSource->disconnect(this);
275
276 m_videoSource = m_session ? m_session->primaryActiveVideoSource() : nullptr;
277
278 // TODO: optimize, setup the connection only when the capture is ready
279 if (m_videoSource) {
282 }
283
285}
286
288{
289 return m_settings;
290}
291
292void QFFmpegImageCapture::setImageSettings(const QImageEncoderSettings &settings)
293{
294 auto s = settings;
295 const auto supportedFormats = QPlatformMediaIntegration::instance()->formatInfo()->imageFormats;
296 if (supportedFormats.isEmpty()) {
297 Q_EMIT error(-1, QImageCapture::FormatError, u"No image formats supported, can't capture."_s);
298 return;
299 }
300 if (s.format() == QImageCapture::UnspecifiedFormat) {
301 auto f = QImageCapture::JPEG;
302 if (!supportedFormats.contains(f))
303 f = supportedFormats.first();
304 s.setFormat(f);
305 } else if (!supportedFormats.contains(settings.format())) {
306 Q_EMIT error(-1, QImageCapture::FormatError, u"Image format not supported."_s);
307 return;
308 }
309
310 m_settings = settings;
312}
313
314QT_END_NAMESPACE
315
316#include "moc_qffmpegimagecapture_p.cpp"
void setImageSettings(const QImageEncoderSettings &settings) override
virtual int doCapture(const QString &fileName)
QPlatformVideoSource * videoSource() const
QImageEncoderSettings imageSettings() const override
virtual void applySettingsToVideoSource()
void setCaptureSession(QPlatformMediaCaptureSession *session)
int capture(const QString &fileName) override
~QFFmpegImageCapture() override
virtual void setupVideoSourceConnections()
bool isReadyForCapture() const override
Combined button and popup list for selecting options.
static constexpr int MaxPendingImagesCount
static const char * extensionForFormat(QImageCapture::FileFormat format)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_STATIC_LOGGING_CATEGORY(name,...)