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 // emit error in the next event loop,
115 // so application can associate it with returned request id.
116 auto invokeDeferred = [&](auto &&fn) {
117 QMetaObject::invokeMethod(this, std::forward<decltype(fn)>(fn), Qt::QueuedConnection);
118 };
119
120 QMutexLocker guard(&m_mutex);
121 if (!m_session) {
122 invokeDeferred([this] {
125 });
126
127 qCDebug(qLcImageCaptureGst) << "error 1";
128 return -1;
129 }
130 if (!m_session->camera()) {
131 invokeDeferred([this] {
132 emit error(-1, QImageCapture::ResourceError, tr("No camera available."));
133 });
134
135 qCDebug(qLcImageCaptureGst) << "error 2";
136 return -1;
137 }
138 if (passImage) {
139 invokeDeferred([this] {
142 });
143
144 qCDebug(qLcImageCaptureGst) << "error 3";
145 return -1;
146 }
147 m_lastId++;
148
149 pendingImages.enqueue({m_lastId, fileName, QMediaMetaData{}});
150 // let one image pass the pipeline
151 passImage = true;
152
154 return m_lastId;
155}
156
157void QGstreamerImageCapture::setResolution(const QSize &resolution)
158{
159 QGstCaps padCaps = bin.staticPad("sink").currentCaps();
160 if (padCaps.isNull()) {
161 qDebug() << "Camera not ready";
162 return;
163 }
164 QGstCaps caps = padCaps.copy();
165 if (caps.isNull())
166 return;
167
168 gst_caps_set_simple(caps.caps(), "width", G_TYPE_INT, resolution.width(), "height", G_TYPE_INT,
169 resolution.height(), nullptr);
170 filter.set("caps", caps);
171}
172
174{
175 QMutexLocker guard(&m_mutex);
176
177 if (!passImage)
178 return false;
179 qCDebug(qLcImageCaptureGst) << "probe buffer";
180
181 QGstBufferHandle bufferHandle{
182 buffer,
184 };
185
186 passImage = false;
187
189
190 QGstCaps caps = bin.staticPad("sink").currentCaps();
191 auto memoryFormat = caps.memoryFormat();
192
193 GstVideoInfo previewInfo;
195 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
196 if (optionalFormatAndVideoInfo)
197 std::tie(fmt, previewInfo) = std::move(*optionalFormatAndVideoInfo);
198
199 auto *sink = m_session->gstreamerVideoSink();
200 auto *gstBuffer = new QGstVideoBuffer{
201 std::move(bufferHandle), previewInfo, sink, fmt, memoryFormat,
202 };
203 QVideoFrame frame(gstBuffer, fmt);
204 QImage img = frame.toImage();
205 if (img.isNull()) {
206 qDebug() << "received a null image";
207 return true;
208 }
209
210 auto &imageData = pendingImages.head();
211
213
214 qCDebug(qLcImageCaptureGst) << "Image available!";
216
218
221 imageData.metaData = metaData;
222
224
225 return true;
226}
227
229{
230 QMutexLocker guard(&m_mutex);
231 QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session);
232 if (m_session == captureSession)
233 return;
234
235 bool readyForCapture = isReadyForCapture();
236 if (m_session) {
237 disconnect(m_session, nullptr, this, nullptr);
238 m_lastId = 0;
239 pendingImages.clear();
240 passImage = false;
241 cameraActive = false;
242 }
243
244 m_session = captureSession;
245 if (!m_session) {
246 if (readyForCapture)
248 return;
249 }
250
254}
255
257{
258 {
259 QMutexLocker guard(&m_mutex);
261 }
262
263 // ensure taginject injects this metaData
265}
266
268{
269 qCDebug(qLcImageCaptureGst) << "cameraActiveChanged" << cameraActive << active;
270 if (cameraActive == active)
271 return;
272 cameraActive = active;
273 qCDebug(qLcImageCaptureGst) << "isReady" << isReadyForCapture();
275}
276
278{
279 QMutexLocker guard(&m_mutex);
280 if (m_session->camera()) {
281 cameraActiveChanged(m_session->camera()->isActive());
282 connect(m_session->camera(), &QPlatformCamera::activeChanged, this,
284 } else {
285 cameraActiveChanged(false);
286 }
287}
288
289gboolean QGstreamerImageCapture::saveImageFilter(GstElement *, GstBuffer *buffer, GstPad *,
290 QGstreamerImageCapture *capture)
291{
292 capture->saveBufferToImage(buffer);
293 return true;
294}
295
296void QGstreamerImageCapture::saveBufferToImage(GstBuffer *buffer)
297{
298 QMutexLocker guard(&m_mutex);
299 passImage = false;
300
301 if (pendingImages.isEmpty())
302 return;
303
304 PendingImage imageData = pendingImages.dequeue();
305 if (imageData.filename.isEmpty())
306 return;
307
308 int id = futureIDAllocator++;
309 QGstBufferHandle bufferHandle{
310 buffer,
312 };
313
314 QFuture<void> saveImageFuture = QtConcurrent::run([this, imageData, bufferHandle,
315 id]() mutable {
316 auto cleanup = qScopeGuard([&] {
317 QMutexLocker guard(&m_mutex);
318 m_pendingFutures.remove(id);
319 });
320
321 qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename;
322
323 QFile f(imageData.filename);
324 if (!f.open(QFile::WriteOnly)) {
325 qCDebug(qLcImageCaptureGst) << " could not open image file for writing";
326 return;
327 }
328
329 GstMapInfo info;
330 GstBuffer *buffer = bufferHandle.get();
331 if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
332 f.write(reinterpret_cast<const char *>(info.data), info.size);
333 gst_buffer_unmap(buffer, &info);
334 }
335 f.close();
336
337 QMetaObject::invokeMethod(this, [this, imageData = std::move(imageData)]() mutable {
338 imageSaved(imageData.id, imageData.filename);
339 });
340 });
341
342 m_pendingFutures.insert(id, saveImageFuture);
343}
344
346{
347 return m_settings;
348}
349
351{
352 if (m_settings != settings) {
353 QSize resolution = settings.resolution();
354 if (m_settings.resolution() != resolution && !resolution.isEmpty())
355 setResolution(resolution);
356
357 m_settings = settings;
358 }
359}
360
362
363#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:1058
MemoryFormat memoryFormat() const
Definition qgst.cpp:506
std::optional< std::pair< QVideoFrameFormat, GstVideoInfo > > formatAndVideoInfo() const
Definition qgst.cpp:329
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
Q_INVOKABLE void insert(Key k, const QVariant &value)
\qmlmethod void QtMultimedia::mediaMetaData::insert(Key k, variant value) Inserts a value into a Key:...
\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.
@ QueuedConnection
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:643
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:804
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
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...