Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qgstreamerimagecapture.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
6#include <QtMultimedia/private/qplatformcamera_p.h>
7#include <QtMultimedia/private/qplatformimagecapture_p.h>
8#include <QtMultimedia/qvideoframeformat.h>
9#include <QtMultimedia/private/qmediastoragelocation_p.h>
10#include <QtCore/qdebug.h>
11#include <QtCore/qdir.h>
12#include <QtCore/qstandardpaths.h>
13#include <QtCore/qloggingcategory.h>
14
17#include <common/qgstutils_p.h>
18
19#include <utility>
20
22
23static Q_LOGGING_CATEGORY(qLcImageCaptureGst, "qt.multimedia.imageCapture")
24
26{
27 QGstElement videoconvert =
28 QGstElement::createFromFactory("videoconvert", "imageCaptureConvert");
29 if (!videoconvert)
30 return errorMessageCannotFindElement("videoconvert");
31
32 QGstElement jpegenc = QGstElement::createFromFactory("jpegenc", "jpegEncoder");
33 if (!jpegenc)
34 return errorMessageCannotFindElement("jpegenc");
35
36 QGstElement jifmux = QGstElement::createFromFactory("jifmux", "jpegMuxer");
37 if (!jifmux)
38 return errorMessageCannotFindElement("jifmux");
39
40 return new QGstreamerImageCapture(videoconvert, jpegenc, jifmux, parent);
41}
42
43QGstreamerImageCapture::QGstreamerImageCapture(QGstElement videoconvert, QGstElement jpegenc,
44 QGstElement jifmux, QImageCapture *parent)
45 : QPlatformImageCapture(parent),
46 QGstreamerBufferProbe(ProbeBuffers),
47 videoConvert(std::move(videoconvert)),
48 encoder(std::move(jpegenc)),
49 muxer(std::move(jifmux))
50{
51 bin = QGstBin::create("imageCaptureBin");
52
53 queue = QGstElement::createFromFactory("queue", "imageCaptureQueue");
54 // configures the queue to be fast, lightweight and non blocking
55 queue.set("leaky", 2 /*downstream*/);
56 queue.set("silent", true);
57 queue.set("max-size-buffers", uint(1));
58 queue.set("max-size-bytes", uint(0));
59 queue.set("max-size-time", quint64(0));
60
61 sink = QGstElement::createFromFactory("fakesink", "imageCaptureSink");
62 filter = QGstElement::createFromFactory("capsfilter", "filter");
63 // imageCaptureSink do not wait for a preroll buffer when going READY -> PAUSED
64 // as no buffer will arrive until capture() is called
65 sink.set("async", false);
66
67 bin.add(queue, filter, videoConvert, encoder, muxer, sink);
68 qLinkGstElements(queue, filter, videoConvert, encoder, muxer, sink);
69 bin.addGhostPad(queue, "sink");
70
71 addProbeToPad(queue.staticPad("src").pad(), false);
72
73 sink.set("signal-handoffs", true);
74 m_handoffConnection = sink.connect("handoff", G_CALLBACK(&saveImageFilter), this);
75}
76
78{
79 bin.setStateSync(GST_STATE_NULL);
80
81 // wait for pending futures
82 auto pendingFutures = [&] {
83 QMutexLocker guard(&m_mutex);
84 return std::move(m_pendingFutures);
85 }();
86
87 for (QFuture<void> &pendingImage : pendingFutures)
88 pendingImage.waitForFinished();
89}
90
92{
93 QMutexLocker guard(&m_mutex);
94 return m_session && !passImage && cameraActive;
95}
96
104
106{
107 return doCapture(QString());
108}
109
110int QGstreamerImageCapture::doCapture(const QString &fileName)
111{
112 qCDebug(qLcImageCaptureGst) << "do capture";
113
114 {
115 QMutexLocker guard(&m_mutex);
116 if (!m_session) {
117 invokeDeferred([this] {
120 });
121
122 qCDebug(qLcImageCaptureGst) << "error 1";
123 return -1;
124 }
125 if (!m_session->camera()) {
126 invokeDeferred([this] {
127 emit error(-1, QImageCapture::ResourceError, tr("No camera available."));
128 });
129
130 qCDebug(qLcImageCaptureGst) << "error 2";
131 return -1;
132 }
133 if (passImage) {
134 invokeDeferred([this] {
137 });
138
139 qCDebug(qLcImageCaptureGst) << "error 3";
140 return -1;
141 }
142 m_lastId++;
143
144 pendingImages.enqueue({ m_lastId, fileName, QMediaMetaData{} });
145 // let one image pass the pipeline
146 passImage = true;
147 }
148
150 return m_lastId;
151}
152
153void QGstreamerImageCapture::setResolution(const QSize &resolution)
154{
155 QGstCaps padCaps = bin.staticPad("sink").currentCaps();
156 if (padCaps.isNull()) {
157 qDebug() << "Camera not ready";
158 return;
159 }
160 QGstCaps caps = padCaps.copy();
161 if (caps.isNull())
162 return;
163
164 gst_caps_set_simple(caps.caps(), "width", G_TYPE_INT, resolution.width(), "height", G_TYPE_INT,
165 resolution.height(), nullptr);
166 filter.set("caps", caps);
167}
168
169// HACK: gcc-10 and earlier reject [=,this] when building with c++17
170#if __cplusplus >= 202002L
171# define EQ_THIS_CAPTURE =, this
172#else
173# define EQ_THIS_CAPTURE =
174#endif
175
177{
178 QMutexLocker guard(&m_mutex);
179
180 if (!passImage)
181 return false;
182 qCDebug(qLcImageCaptureGst) << "probe buffer";
183
184 QGstBufferHandle bufferHandle{
185 buffer,
187 };
188
189 passImage = false;
190
191 bool ready = isReadyForCapture();
192 invokeDeferred([this, ready] {
194 });
195
196 QGstCaps caps = bin.staticPad("sink").currentCaps();
197 auto memoryFormat = caps.memoryFormat();
198
199 GstVideoInfo previewInfo;
201 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
202 if (optionalFormatAndVideoInfo)
203 std::tie(fmt, previewInfo) = std::move(*optionalFormatAndVideoInfo);
204
205 int futureId = futureIDAllocator += 1;
206
207 // ensure QVideoFrame::toImage is executed on a worker thread that is joined before the
208 // qApplication is destroyed
209 QFuture<void> future = QtConcurrent::run([EQ_THIS_CAPTURE]() mutable {
210 QMutexLocker guard(&m_mutex);
211 auto scopeExit = qScopeGuard([&] {
212 m_pendingFutures.remove(futureId);
213 });
214
215 if (!m_session) {
216 qDebug() << "QGstreamerImageCapture::probeBuffer: no session";
217 return;
218 }
219
220 auto *sink = m_session->gstreamerVideoSink();
221 auto *gstBuffer = new QGstVideoBuffer{
222 std::move(bufferHandle), previewInfo, sink, fmt, memoryFormat,
223 };
224 QVideoFrame frame(gstBuffer, fmt);
225
226 QImage img = frame.toImage();
227 if (img.isNull()) {
228 qDebug() << "received a null image";
229 return;
230 }
231
232 QMediaMetaData imageMetaData = metaData();
233 imageMetaData.insert(QMediaMetaData::Resolution, frame.size());
234 pendingImages.head().metaData = std::move(imageMetaData);
235 PendingImage pendingImage = pendingImages.head();
236
237 invokeDeferred([this, pendingImage = std::move(pendingImage), frame = std::move(frame),
238 img = std::move(img)]() mutable {
239 emit imageExposed(pendingImage.id);
240 qCDebug(qLcImageCaptureGst) << "Image available!";
241 emit imageAvailable(pendingImage.id, frame);
242 emit imageCaptured(pendingImage.id, img);
243 emit imageMetadataAvailable(pendingImage.id, pendingImage.metaData);
244 });
245 });
246
247 m_pendingFutures.insert(futureId, future);
248
249 return true;
250}
251
252#undef EQ_THIS_CAPTURE
253
255{
256 QMutexLocker guard(&m_mutex);
257 QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session);
258 if (m_session == captureSession)
259 return;
260
261 bool readyForCapture = isReadyForCapture();
262 if (m_session) {
263 disconnect(m_session, nullptr, this, nullptr);
264 m_lastId = 0;
265 pendingImages.clear();
266 passImage = false;
267 cameraActive = false;
268 }
269
270 m_session = captureSession;
271 if (!m_session) {
272 if (readyForCapture)
274 return;
275 }
276
280}
281
283{
284 {
285 QMutexLocker guard(&m_mutex);
287 }
288
289 // ensure taginject injects this metaData
291}
292
294{
295 qCDebug(qLcImageCaptureGst) << "cameraActiveChanged" << cameraActive << active;
296 if (cameraActive == active)
297 return;
298 cameraActive = active;
299 qCDebug(qLcImageCaptureGst) << "isReady" << isReadyForCapture();
301}
302
304{
305 QMutexLocker guard(&m_mutex);
306 if (m_session->camera()) {
307 cameraActiveChanged(m_session->camera()->isActive());
308 connect(m_session->camera(), &QPlatformCamera::activeChanged, this,
310 } else {
311 cameraActiveChanged(false);
312 }
313}
314
315gboolean QGstreamerImageCapture::saveImageFilter(GstElement *, GstBuffer *buffer, GstPad *,
316 QGstreamerImageCapture *capture)
317{
318 capture->saveBufferToImage(buffer);
319 return true;
320}
321
322void QGstreamerImageCapture::saveBufferToImage(GstBuffer *buffer)
323{
324 QMutexLocker guard(&m_mutex);
325 passImage = false;
326
327 if (pendingImages.isEmpty())
328 return;
329
330 PendingImage imageData = pendingImages.dequeue();
331 if (imageData.filename.isEmpty())
332 return;
333
334 int id = futureIDAllocator++;
335 QGstBufferHandle bufferHandle{
336 buffer,
338 };
339
340 QFuture<void> saveImageFuture = QtConcurrent::run([this, imageData, bufferHandle,
341 id]() mutable {
342 auto cleanup = qScopeGuard([&] {
343 QMutexLocker guard(&m_mutex);
344 m_pendingFutures.remove(id);
345 });
346
347 qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename;
348
349 QFile f(imageData.filename);
350 if (!f.open(QFile::WriteOnly)) {
351 qCDebug(qLcImageCaptureGst) << " could not open image file for writing";
352 return;
353 }
354
355 GstMapInfo info;
356 GstBuffer *buffer = bufferHandle.get();
357 if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
358 f.write(reinterpret_cast<const char *>(info.data), info.size);
359 gst_buffer_unmap(buffer, &info);
360 }
361 f.close();
362
363 QMetaObject::invokeMethod(this, [this, imageData = std::move(imageData)]() mutable {
364 imageSaved(imageData.id, imageData.filename);
365 });
366 });
367
368 m_pendingFutures.insert(id, saveImageFuture);
369}
370
372{
373 return m_settings;
374}
375
377{
378 if (m_settings != settings) {
379 QSize resolution = settings.resolution();
380 if (m_settings.resolution() != resolution && !resolution.isEmpty())
381 setResolution(resolution);
382
383 m_settings = settings;
384 }
385}
386
388
389#include "moc_qgstreamerimagecapture_p.cpp"
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:107
\inmodule QtCore
Definition qfile.h:93
static QGstBin create(const char *name)
Definition qgst.cpp:1060
GstCaps * caps() const
Definition qgst.cpp:526
QGstCaps copy() const
Definition qgst.cpp:498
bool setStateSync(GstState state, std::chrono::nanoseconds timeout=std::chrono::seconds(1))
Definition qgst.cpp:947
QGstPad staticPad(const char *name) const
Definition qgst.cpp:898
static QGstElement createFromFactory(const char *factory, const char *name=nullptr)
Definition qgst.cpp:835
QGstCaps currentCaps() const
Definition qgst.cpp:742
int capture(const QString &fileName) override
bool isReadyForCapture() const override
bool probeBuffer(GstBuffer *buffer) override
QImageEncoderSettings imageSettings() const override
void setImageSettings(const QImageEncoderSettings &settings) override
void setMetaData(const QMediaMetaData &m) override
void setCaptureSession(QPlatformMediaCaptureSession *session)
QGstreamerVideoSink * gstreamerVideoSink() const
QPlatformCamera * camera() override
\inmodule QtMultimedia
\inmodule QtGui
Definition qimage.h:37
bool isEmpty() const noexcept
Definition qlist.h:401
void clear()
Definition qlist.h:434
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
size_type remove(const Key &key)
Definition qmap.h:300
\inmodule QtMultimedia
\inmodule QtCore
Definition qmutex.h:313
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
void imageCaptured(int requestId, const QImage &preview)
QMediaMetaData metaData() const
void imageSaved(int requestId, const QString &fileName)
void imageAvailable(int requestId, const QVideoFrame &buffer)
static QString msgImageCaptureNotSet()
void imageMetadataAvailable(int id, const QMediaMetaData &)
void imageExposed(int requestId)
virtual void setMetaData(const QMediaMetaData &m)
void readyForCaptureChanged(bool ready)
virtual bool isActive() const =0
void activeChanged(bool)
void enqueue(const T &t)
Adds value t to the tail of the queue.
Definition qqueue.h:18
T & head()
Returns a reference to the queue's head item.
Definition qqueue.h:20
T dequeue()
Removes the head item in the queue and returns it.
Definition qqueue.h:19
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:133
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:130
constexpr bool isEmpty() const noexcept
Returns true if either of the width and height is less than or equal to 0; otherwise returns false.
Definition qsize.h:124
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:27
QSize size
the size of the widget excluding any window frame
Definition qwidget.h:113
Q_MULTIMEDIA_EXPORT QString generateFileName(const QString &requestedName, QStandardPaths::StandardLocation type, const QString &extension)
Combined button and popup list for selecting options.
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
QTextStream & bin(QTextStream &stream)
Calls QTextStream::setIntegerBase(2) on stream and returns stream.
DBusConnection const char DBusError * error
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void qLinkGstElements)(const Ts &...ts)
Definition qgst_p.h:644
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:817
#define EQ_THIS_CAPTURE
static void applyMetaDataToTagSetter(const QMediaMetaData &metadata, GstTagSetter *element)
#define qDebug
[1]
Definition qlogging.h:164
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
const GLfloat * m
GLfloat GLfloat f
GLenum GLuint buffer
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
GLint void * img
Definition qopenglext.h:233
GLsizei const GLchar *const * path
GLsizei GLenum GLboolean sink
QScopeGuard< typename std::decay< F >::type > qScopeGuard(F &&f)
[qScopeGuard]
Definition qscopeguard.h:60
#define tr(X)
#define emit
unsigned long long quint64
Definition qtypes.h:61
unsigned int uint
Definition qtypes.h:34
QVideoFrameFormat::PixelFormat fmt
QFuture< void > future
[5]
QSettings settings("MySoft", "Star Runner")
[0]
myObject disconnect()
[26]
QQueue< int > queue
[0]
QByteArray imageData
[15]
QFrame frame
[0]
QHostInfo info
[0]
view create()
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...