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/qvideoframeformat.h>
7#include <QtMultimedia/private/qmediastoragelocation_p.h>
8#include <QtMultimedia/private/qplatformcamera_p.h>
9#include <QtMultimedia/private/qplatformimagecapture_p.h>
10#include <QtMultimedia/private/qvideoframe_p.h>
11#include <QtCore/qdebug.h>
12#include <QtCore/qdir.h>
13#include <QtCore/qstandardpaths.h>
14#include <QtCore/qcoreapplication.h>
15#include <QtCore/qloggingcategory.h>
16
19#include <common/qgstutils_p.h>
20
21#include <utility>
22
24
25namespace {
26Q_LOGGING_CATEGORY(qLcImageCaptureGst, "qt.multimedia.imageCapture")
27
29{
32 QThreadPool *m_instance{};
33 bool m_appUnderDestruction = false;
34
35 QThreadPool *get(const QMutexLocker<QMutex> &)
36 {
37 if (m_instance)
38 return m_instance;
39 if (m_appUnderDestruction || !qApp)
40 return nullptr;
41
42 using namespace std::chrono;
43
44 m_instance = new QThreadPool(qApp);
45 m_instance->setMaxThreadCount(1); // 1 thread;
46 static constexpr auto expiryTimeout = minutes(5);
47 m_instance->setExpiryTimeout(round<milliseconds>(expiryTimeout).count());
48
50 // we need to make sure that thread-local QRhi is destroyed before the application to
51 // prevent QTBUG-124189
52 QMutexLocker guard(&m_poolMutex);
53 delete m_instance;
54 m_instance = {};
55 m_appUnderDestruction = true;
56 });
57
59 m_appUnderDestruction = false;
60 });
61 return m_instance;
62 }
63
64 template <typename Functor>
65 QFuture<void> run(Functor &&f)
66 {
67 QMutexLocker guard(&m_poolMutex);
68 QThreadPool *pool = get(guard);
69 if (!pool)
70 return QFuture<void>{};
71
72 return QtConcurrent::run(pool, std::forward<Functor>(f));
73 }
74};
75
77
78}; // namespace
79
80QMaybe<QPlatformImageCapture *> QGstreamerImageCapture::create(QImageCapture *parent)
81{
83 "queue", "capsfilter", "videoconvert", "jpegenc", "jifmux", "fakesink");
84 if (error)
85 return *error;
86
88}
89
90QGstreamerImageCapture::QGstreamerImageCapture(QImageCapture *parent)
91 : QPlatformImageCapture(parent),
92 QGstreamerBufferProbe(ProbeBuffers),
93 bin{
94 QGstBin::create("imageCaptureBin"),
95 },
96 queue{
97 QGstElement::createFromFactory("queue", "imageCaptureQueue"),
98 },
99 filter{
100 QGstElement::createFromFactory("capsfilter", "filter"),
101 },
102 videoConvert{
103 QGstElement::createFromFactory("videoconvert", "imageCaptureConvert"),
104 },
105 encoder{
106 QGstElement::createFromFactory("jpegenc", "jpegEncoder"),
107 },
108 muxer{
109 QGstElement::createFromFactory("jifmux", "jpegMuxer"),
110 },
111 sink{
112 QGstElement::createFromFactory("fakesink", "imageCaptureSink"),
113 }
114{
115 // configures the queue to be fast, lightweight and non blocking
116 queue.set("leaky", 2 /*downstream*/);
117 queue.set("silent", true);
118 queue.set("max-size-buffers", uint(1));
119 queue.set("max-size-bytes", uint(0));
120 queue.set("max-size-time", quint64(0));
121
122 // imageCaptureSink do not wait for a preroll buffer when going READY -> PAUSED
123 // as no buffer will arrive until capture() is called
124 sink.set("async", false);
125
126 bin.add(queue, filter, videoConvert, encoder, muxer, sink);
127 qLinkGstElements(queue, filter, videoConvert, encoder, muxer, sink);
128 bin.addGhostPad(queue, "sink");
129
130 addProbeToPad(queue.staticPad("src").pad(), false);
131
132 sink.set("signal-handoffs", true);
133 m_handoffConnection = sink.connect("handoff", G_CALLBACK(&saveImageFilter), this);
134}
135
137{
138 bin.setStateSync(GST_STATE_NULL);
139
140 // wait for pending futures
141 auto pendingFutures = [&] {
142 QMutexLocker guard(&m_mutex);
143 return std::move(m_pendingFutures);
144 }();
145
146 for (QFuture<void> &pendingImage : pendingFutures)
147 pendingImage.waitForFinished();
148}
149
151{
152 QMutexLocker guard(&m_mutex);
153 return m_session && !passImage && cameraActive;
154}
155
163
165{
166 return doCapture(QString());
167}
168
169int QGstreamerImageCapture::doCapture(const QString &fileName)
170{
171 qCDebug(qLcImageCaptureGst) << "do capture";
172
173 {
174 QMutexLocker guard(&m_mutex);
175 if (!m_session) {
176 invokeDeferred([this] {
179 });
180
181 qCDebug(qLcImageCaptureGst) << "error 1";
182 return -1;
183 }
184 if (!m_session->camera()) {
185 invokeDeferred([this] {
186 emit error(-1, QImageCapture::ResourceError, tr("No camera available."));
187 });
188
189 qCDebug(qLcImageCaptureGst) << "error 2";
190 return -1;
191 }
192 if (passImage) {
193 invokeDeferred([this] {
196 });
197
198 qCDebug(qLcImageCaptureGst) << "error 3";
199 return -1;
200 }
201 m_lastId++;
202
203 pendingImages.enqueue({ m_lastId, fileName, QMediaMetaData{} });
204 // let one image pass the pipeline
205 passImage = true;
206 }
207
209 return m_lastId;
210}
211
212void QGstreamerImageCapture::setResolution(const QSize &resolution)
213{
214 QGstCaps padCaps = bin.staticPad("sink").currentCaps();
215 if (padCaps.isNull()) {
216 qDebug() << "Camera not ready";
217 return;
218 }
219 QGstCaps caps = padCaps.copy();
220 if (caps.isNull())
221 return;
222
223 gst_caps_set_simple(caps.caps(), "width", G_TYPE_INT, resolution.width(), "height", G_TYPE_INT,
224 resolution.height(), nullptr);
225 filter.set("caps", caps);
226}
227
228// HACK: gcc-10 and earlier reject [=,this] when building with c++17
229#if __cplusplus >= 202002L
230# define EQ_THIS_CAPTURE =, this
231#else
232# define EQ_THIS_CAPTURE =
233#endif
234
236{
237 QMutexLocker guard(&m_mutex);
238
239 if (!passImage)
240 return false;
241 qCDebug(qLcImageCaptureGst) << "probe buffer";
242
243 QGstBufferHandle bufferHandle{
244 buffer,
246 };
247
248 passImage = false;
249
250 bool ready = isReadyForCapture();
251 invokeDeferred([this, ready] {
253 });
254
255 QGstCaps caps = bin.staticPad("sink").currentCaps();
256 auto memoryFormat = caps.memoryFormat();
257
258 GstVideoInfo previewInfo;
260 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
261 if (optionalFormatAndVideoInfo)
262 std::tie(fmt, previewInfo) = std::move(*optionalFormatAndVideoInfo);
263
264 int futureId = futureIDAllocator += 1;
265
266 // ensure QVideoFrame::toImage is executed on a worker thread that is joined before the
267 // qApplication is destroyed
268 QFuture<void> future = s_threadPoolSingleton.run([EQ_THIS_CAPTURE]() mutable {
269 QMutexLocker guard(&m_mutex);
270 auto scopeExit = qScopeGuard([&] {
271 m_pendingFutures.remove(futureId);
272 });
273
274 if (!m_session) {
275 qDebug() << "QGstreamerImageCapture::probeBuffer: no session";
276 return;
277 }
278
279 auto *sink = m_session->gstreamerVideoSink();
280 auto gstBuffer = std::make_unique<QGstVideoBuffer>(std::move(bufferHandle), previewInfo,
281 sink, fmt, memoryFormat);
282
283 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(gstBuffer), fmt);
284 QImage img = frame.toImage();
285 if (img.isNull()) {
286 qDebug() << "received a null image";
287 return;
288 }
289
290 QMediaMetaData imageMetaData = metaData();
291 imageMetaData.insert(QMediaMetaData::Resolution, frame.size());
292 pendingImages.head().metaData = std::move(imageMetaData);
293 PendingImage pendingImage = pendingImages.head();
294
295 invokeDeferred([this, pendingImage = std::move(pendingImage), frame = std::move(frame),
296 img = std::move(img)]() mutable {
297 emit imageExposed(pendingImage.id);
298 qCDebug(qLcImageCaptureGst) << "Image available!";
299 emit imageAvailable(pendingImage.id, frame);
300 emit imageCaptured(pendingImage.id, img);
301 emit imageMetadataAvailable(pendingImage.id, pendingImage.metaData);
302 });
303 });
304
305 if (!future.isValid()) // during qApplication shutdown the threadpool becomes unusable
306 return true;
307
308 m_pendingFutures.insert(futureId, future);
309
310 return true;
311}
312
313#undef EQ_THIS_CAPTURE
314
316{
317 QMutexLocker guard(&m_mutex);
318 QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session);
319 if (m_session == captureSession)
320 return;
321
322 bool readyForCapture = isReadyForCapture();
323 if (m_session) {
324 disconnect(m_session, nullptr, this, nullptr);
325 m_lastId = 0;
326 pendingImages.clear();
327 passImage = false;
328 cameraActive = false;
329 }
330
331 m_session = captureSession;
332 if (!m_session) {
333 if (readyForCapture)
335 return;
336 }
337
341}
342
344{
345 {
346 QMutexLocker guard(&m_mutex);
348 }
349
350 // ensure taginject injects this metaData
352}
353
355{
356 qCDebug(qLcImageCaptureGst) << "cameraActiveChanged" << cameraActive << active;
357 if (cameraActive == active)
358 return;
359 cameraActive = active;
360 qCDebug(qLcImageCaptureGst) << "isReady" << isReadyForCapture();
362}
363
365{
366 QMutexLocker guard(&m_mutex);
367 if (m_session->camera()) {
368 cameraActiveChanged(m_session->camera()->isActive());
369 connect(m_session->camera(), &QPlatformCamera::activeChanged, this,
371 } else {
372 cameraActiveChanged(false);
373 }
374}
375
376gboolean QGstreamerImageCapture::saveImageFilter(GstElement *, GstBuffer *buffer, GstPad *,
377 QGstreamerImageCapture *capture)
378{
379 capture->saveBufferToImage(buffer);
380 return true;
381}
382
383void QGstreamerImageCapture::saveBufferToImage(GstBuffer *buffer)
384{
385 QMutexLocker guard(&m_mutex);
386 passImage = false;
387
388 if (pendingImages.isEmpty())
389 return;
390
391 PendingImage imageData = pendingImages.dequeue();
392 if (imageData.filename.isEmpty())
393 return;
394
395 int id = futureIDAllocator++;
396 QGstBufferHandle bufferHandle{
397 buffer,
399 };
400
401 QFuture<void> saveImageFuture = QtConcurrent::run([this, imageData, bufferHandle,
402 id]() mutable {
403 auto cleanup = qScopeGuard([&] {
404 QMutexLocker guard(&m_mutex);
405 m_pendingFutures.remove(id);
406 });
407
408 qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename;
409
410 QFile f(imageData.filename);
411 if (!f.open(QFile::WriteOnly)) {
412 qCDebug(qLcImageCaptureGst) << " could not open image file for writing";
413 return;
414 }
415
416 GstMapInfo info;
417 GstBuffer *buffer = bufferHandle.get();
418 if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
419 f.write(reinterpret_cast<const char *>(info.data), info.size);
420 gst_buffer_unmap(buffer, &info);
421 }
422 f.close();
423
424 QMetaObject::invokeMethod(this, [this, imageData = std::move(imageData)]() mutable {
425 emit imageSaved(imageData.id, imageData.filename);
426 });
427 });
428
429 m_pendingFutures.insert(id, saveImageFuture);
430}
431
433{
434 return m_settings;
435}
436
438{
439 if (m_settings != settings) {
440 QSize resolution = settings.resolution();
441 if (m_settings.resolution() != resolution && !resolution.isEmpty())
442 setResolution(resolution);
443
444 m_settings = settings;
445 }
446}
447
449
450#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
void aboutToQuit(QPrivateSignal)
This signal is emitted when the application is about to quit the main event loop, e....
\inmodule QtCore
Definition qfile.h:93
bool isValid() const
Definition qfuture.h:125
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void add)(const Ts &...ts)
Definition qgst_p.h:707
void addGhostPad(const QGstElement &child, const char *name)
Definition qgst.cpp:1242
GstCaps * caps() const
Definition qgst.cpp:542
QGstCaps copy() const
Definition qgst.cpp:512
bool setStateSync(GstState state, std::chrono::nanoseconds timeout=std::chrono::seconds(1))
Definition qgst.cpp:1002
QGstPad staticPad(const char *name) const
Definition qgst.cpp:953
void set(const char *property, const char *str)
Definition qgst.cpp:554
GstPad * pad() const
Definition qgst.cpp:824
QGstCaps currentCaps() const
Definition qgst.cpp:762
void addProbeToPad(GstPad *pad, bool downstream=true)
int capture(const QString &fileName) override
bool isReadyForCapture() const override
bool probeBuffer(GstBuffer *buffer) override
QImageEncoderSettings imageSettings() const override
static QMaybe< QPlatformImageCapture * > create(QImageCapture *parent)
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:402
void clear()
Definition qlist.h:435
iterator insert(const Key &key, const T &value)
Definition qmap.h:689
size_type remove(const Key &key)
Definition qmap.h:301
\inmodule QtMultimedia
\inmodule QtCore
Definition qmutex.h:313
\inmodule QtCore
Definition qmutex.h:281
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
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 destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
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
\inmodule QtCore
Definition qthreadpool.h:22
The QVideoFrameFormat class specifies the stream format of a video presentation surface.
static QVideoFrame createFrame(std::unique_ptr< Buffer > buffer, QVideoFrameFormat format)
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.
ThreadPoolSingleton s_threadPoolSingleton
QTCONCURRENT_RUN_NODISCARD auto run(QThreadPool *pool, Function &&f, Args &&...args)
#define qApp
DBusConnection const char DBusError * error
static QDBusError::ErrorType get(const char *name)
std::enable_if_t<(std::is_base_of_v< QGstElement, Ts > &&...), void qLinkGstElements)(const Ts &...ts)
Definition qgst_p.h:660
std::optional< QString > qGstErrorMessageIfElementsNotAvailable(const Arg &arg, Args... args)
Definition qgst_p.h:833
struct _GstElement GstElement
#define EQ_THIS_CAPTURE
static void applyMetaDataToTagSetter(const QMediaMetaData &metadata, GstTagSetter *element)
#define qDebug
[1]
Definition qlogging.h:165
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
const GLfloat * m
GLenum GLenum GLsizei count
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...
QThreadPool * get(const QMutexLocker< QMutex > &)