Qt 6.x
The Qt SDK
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#include <private/qplatformcamera_p.h>
6#include <private/qplatformimagecapture_p.h>
7#include <qgstvideobuffer_p.h>
8#include <qgstutils_p.h>
10#include <qvideoframeformat.h>
11#include <private/qmediastoragelocation_p.h>
12
13#include <QtCore/QDebug>
14#include <QtCore/QDir>
15#include <utility>
16#include <qstandardpaths.h>
17
18#include <qloggingcategory.h>
19
21
22static Q_LOGGING_CATEGORY(qLcImageCaptureGst, "qt.multimedia.imageCapture")
23
25{
26 QGstElement videoconvert("videoconvert", "imageCaptureConvert");
27 if (!videoconvert)
28 return errorMessageCannotFindElement("videoconvert");
29
30 QGstElement jpegenc("jpegenc", "jpegEncoder");
31 if (!jpegenc)
32 return errorMessageCannotFindElement("jpegenc");
33
34 QGstElement jifmux("jifmux", "jpegMuxer");
35 if (!jifmux)
36 return errorMessageCannotFindElement("jifmux");
37
38 return new QGstreamerImageCapture(videoconvert, jpegenc, jifmux, parent);
39}
40
41QGstreamerImageCapture::QGstreamerImageCapture(QGstElement videoconvert, QGstElement jpegenc,
44 QGstreamerBufferProbe(ProbeBuffers),
45 videoConvert(std::move(videoconvert)),
46 encoder(std::move(jpegenc)),
47 muxer(std::move(jifmux))
48{
49 bin = QGstBin("imageCaptureBin");
50
51 queue = QGstElement("queue", "imageCaptureQueue");
52 // configures the queue to be fast, lightweight and non blocking
53 queue.set("leaky", 2 /*downstream*/);
54 queue.set("silent", true);
55 queue.set("max-size-buffers", uint(1));
56 queue.set("max-size-bytes", uint(0));
57 queue.set("max-size-time", quint64(0));
58
59 sink = QGstElement("fakesink","imageCaptureSink");
60 filter = QGstElement("capsfilter", "filter");
61 // imageCaptureSink do not wait for a preroll buffer when going READY -> PAUSED
62 // as no buffer will arrive until capture() is called
63 sink.set("async", false);
64
65 bin.add(queue, filter, videoConvert, encoder, muxer, sink);
66 queue.link(filter, videoConvert, encoder, muxer, sink);
67 bin.addGhostPad(queue, "sink");
68
69 addProbeToPad(queue.staticPad("src").pad(), false);
70
71 sink.set("signal-handoffs", true);
72 g_signal_connect(sink.object(), "handoff", G_CALLBACK(&QGstreamerImageCapture::saveImageFilter), this);
73}
74
76{
77 bin.setStateSync(GST_STATE_NULL);
78}
79
81{
82 return m_session && !passImage && cameraActive;
83}
84
86{
88 return doCapture(path);
89}
90
92{
93 return doCapture(QString());
94}
95
96int QGstreamerImageCapture::doCapture(const QString &fileName)
97{
98 qCDebug(qLcImageCaptureGst) << "do capture";
99 if (!m_session) {
100 //emit error in the next event loop,
101 //so application can associate it with returned request id.
103 Q_ARG(int, -1),
106
107 qCDebug(qLcImageCaptureGst) << "error 1";
108 return -1;
109 }
110 if (!m_session->camera()) {
111 //emit error in the next event loop,
112 //so application can associate it with returned request id.
114 Q_ARG(int, -1),
116 Q_ARG(QString,tr("No camera available.")));
117
118 qCDebug(qLcImageCaptureGst) << "error 2";
119 return -1;
120 }
121 if (passImage) {
122 //emit error in the next event loop,
123 //so application can associate it with returned request id.
125 Q_ARG(int, -1),
128
129 qCDebug(qLcImageCaptureGst) << "error 3";
130 return -1;
131 }
132 m_lastId++;
133
134 pendingImages.enqueue({m_lastId, fileName, QMediaMetaData{}});
135 // let one image pass the pipeline
136 passImage = true;
137
139 return m_lastId;
140}
141
142void QGstreamerImageCapture::setResolution(const QSize &resolution)
143{
144 auto padCaps = QGstCaps(gst_pad_get_current_caps(bin.staticPad("sink").pad()), QGstCaps::HasRef);
145 if (padCaps.isNull()) {
146 qDebug() << "Camera not ready";
147 return;
148 }
149 auto caps = QGstCaps(gst_caps_copy(padCaps.get()), QGstCaps::HasRef);
150 if (caps.isNull()) {
151 return;
152 }
153 gst_caps_set_simple(caps.get(),
154 "width", G_TYPE_INT, resolution.width(),
155 "height", G_TYPE_INT, resolution.height(),
156 nullptr);
157 filter.set("caps", caps);
158}
159
161{
162 if (!passImage)
163 return false;
164 qCDebug(qLcImageCaptureGst) << "probe buffer";
165
166 passImage = false;
167
169
170 auto caps = QGstCaps(gst_pad_get_current_caps(bin.staticPad("sink").pad()), QGstCaps::HasRef);
171 GstVideoInfo previewInfo;
172 gst_video_info_from_caps(&previewInfo, caps.get());
173
174 auto memoryFormat = caps.memoryFormat();
175 auto fmt = caps.formatForCaps(&previewInfo);
176 auto *sink = m_session->gstreamerVideoSink();
177 auto *gstBuffer = new QGstVideoBuffer(buffer, previewInfo, sink, fmt, memoryFormat);
178 QVideoFrame frame(gstBuffer, fmt);
179 QImage img = frame.toImage();
180 if (img.isNull()) {
181 qDebug() << "received a null image";
182 return true;
183 }
184
185 auto &imageData = pendingImages.head();
186
188
189 qCDebug(qLcImageCaptureGst) << "Image available!";
191
193
197 imageData.metaData = metaData;
198
199 // ensure taginject injects this metaData
200 const auto &md = static_cast<const QGstreamerMetaData &>(metaData);
201 md.setMetaData(muxer.element());
202
204
205 return true;
206}
207
209{
210 QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session);
211 if (m_session == captureSession)
212 return;
213
214 bool readyForCapture = isReadyForCapture();
215 if (m_session) {
216 disconnect(m_session, nullptr, this, nullptr);
217 m_lastId = 0;
218 pendingImages.clear();
219 passImage = false;
220 cameraActive = false;
221 }
222
223 m_session = captureSession;
224 if (!m_session) {
225 if (readyForCapture)
227 return;
228 }
229
232}
233
235{
236 qCDebug(qLcImageCaptureGst) << "cameraActiveChanged" << cameraActive << active;
237 if (cameraActive == active)
238 return;
239 cameraActive = active;
240 qCDebug(qLcImageCaptureGst) << "isReady" << isReadyForCapture();
242}
243
245{
246 if (m_session->camera()) {
247 cameraActiveChanged(m_session->camera()->isActive());
249 } else {
250 cameraActiveChanged(false);
251 }
252}
253
254gboolean QGstreamerImageCapture::saveImageFilter(GstElement *element,
255 GstBuffer *buffer,
256 GstPad *pad,
257 void *appdata)
258{
259 Q_UNUSED(element);
260 Q_UNUSED(pad);
261 QGstreamerImageCapture *capture = static_cast<QGstreamerImageCapture *>(appdata);
262
263 capture->passImage = false;
264
265 if (capture->pendingImages.isEmpty()) {
266 return true;
267 }
268
269 auto imageData = capture->pendingImages.dequeue();
270 if (imageData.filename.isEmpty()) {
271 return true;
272 }
273
274 qCDebug(qLcImageCaptureGst) << "saving image as" << imageData.filename;
275
276 QFile f(imageData.filename);
277 if (f.open(QFile::WriteOnly)) {
278 GstMapInfo info;
279 if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
280 f.write(reinterpret_cast<const char *>(info.data), info.size);
281 gst_buffer_unmap(buffer, &info);
282 }
283 f.close();
284
286 savedSignal.invoke(capture,
288 Q_ARG(int, imageData.id),
289 Q_ARG(QString, imageData.filename));
290 } else {
291 qCDebug(qLcImageCaptureGst) << " could not open image file for writing";
292 }
293
294 return TRUE;
295}
296
298{
299 return m_settings;
300}
301
303{
304 if (m_settings != settings) {
305 QSize resolution = settings.resolution();
306 if (m_settings.resolution() != resolution && !resolution.isEmpty()) {
307 setResolution(resolution);
308 }
309 m_settings = settings;
310 }
311}
312
314
315#include "moc_qgstreamerimagecapture_p.cpp"
bool isEmpty() const noexcept
Returns true if the byte array has size 0; otherwise returns false.
Definition qbytearray.h:106
static QDateTime currentDateTime()
This is an overloaded member function, provided for convenience. It differs from the above function o...
qint64 size() const
Returns the file size in bytes.
\inmodule QtCore
Definition qfile.h:93
@ HasRef
Definition qgst_p.h:163
GstElement * element() const
Definition qgst_p.h:526
bool setStateSync(GstState state)
Definition qgst_p.h:463
QGstPad staticPad(const char *name) const
Definition qgst_p.h:443
GstPad * pad() const
Definition qgst_p.h:344
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 setCaptureSession(QPlatformMediaCaptureSession *session)
QGstreamerVideoSink * gstreamerVideoSink() const
QPlatformCamera * camera() override
void setMetaData(GstBin *bin) const
\inmodule QtMultimedia
\inmodule QtGui
Definition qimage.h:37
void clear()
Definition qlist.h:417
\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 qmetaobject.h:18
static QMetaMethod fromSignal(PointerToMemberFunction signal)
bool invoke(QObject *object, Qt::ConnectionType connectionType, QGenericReturnArgument returnValue, 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()) const
\obsolete [6.5] Please use the variadic overload of this function
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
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)
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
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
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:123
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
The QVideoFrame class represents a frame of video data.
Definition qvideoframe.h:26
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.
QTextStream & bin(QTextStream &stream)
Calls QTextStream::setIntegerBase(2) on stream and returns stream.
@ QueuedConnection
QString errorMessageCannotFindElement(std::string_view element)
Definition qgst_p.h:590
#define qDebug
[1]
Definition qlogging.h:160
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
#define Q_ARG(Type, data)
Definition qobjectdefs.h:62
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
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define tr(X)
#define emit
#define Q_UNUSED(x)
unsigned long long quint64
Definition qtypes.h:56
unsigned int uint
Definition qtypes.h:29
QVideoFrameFormat::PixelFormat fmt
QFileInfo info(fileName)
[8]
QSettings settings("MySoft", "Star Runner")
[0]
myObject disconnect()
[26]
QQueue< int > queue
[0]
QByteArray imageData
[15]
QFrame frame
[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...
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent