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 <QtGui/qguiapplication.h>
12#include <QtCore/qdebug.h>
13#include <QtCore/qdir.h>
14#include <QtCore/qstandardpaths.h>
15#include <QtCore/qcoreapplication.h>
16#include <QtCore/qloggingcategory.h>
18#include <common/qgstreamermetadata_p.h>
19#include <common/qgstvideobuffer_p.h>
20#include <common/qgstutils_p.h>
28Q_STATIC_LOGGING_CATEGORY(qLcImageCaptureGst,
"qt.multimedia.imageCapture")
41 if (m_appUnderDestruction || !qApp)
44 using namespace std::chrono;
46 m_instance =
new QThreadPool;
47 m_instance->setMaxThreadCount(1);
48 static constexpr auto expiryTimeout = minutes(5);
49 m_instance->setExpiryTimeout(round<milliseconds>(expiryTimeout).count());
51 QObject::connect(qApp, &QCoreApplication::aboutToQuit, &m_context, [&] {
54 QMutexLocker guard(&m_poolMutex);
57 m_appUnderDestruction =
true;
60 QObject::connect(qApp, &QCoreApplication::destroyed, &m_context, [&] {
61 m_appUnderDestruction =
false;
66 template <
typename Functor>
69 QMutexLocker guard(&m_poolMutex);
70 QThreadPool *pool = get(guard);
72 return QFuture<
void>{};
74 return QtConcurrent::run(pool, std::forward<Functor>(f));
82template <
typename Functor>
83void QGstreamerImageCapture::invokeDeferred(Functor &&fn)
85 QMetaObject::invokeMethod(
this, std::forward<
decltype(fn)>(fn), Qt::QueuedConnection);
88template <
typename Functor>
89void QGstreamerImageCapture::runInThreadPool(Functor fn)
91 int futureId = m_futureIDAllocator.fetch_add(1, std::memory_order_relaxed);
93 QFuture<
void> future = QtConcurrent::run([
this, futureId, fn = std::move(fn)]()
mutable {
94 auto cleanup = qScopeGuard([&] {
95 QMutexLocker guard(&m_pendingFuturesMutex);
96 m_pendingFutures.erase(futureId);
101 if (!future.isValid())
104 QMutexLocker guard(&m_pendingFuturesMutex);
105 m_pendingFutures.emplace(futureId, std::move(future));
108q23::expected<QPlatformImageCapture *, QString> QGstreamerImageCapture::create(QImageCapture *parent)
110 static const auto error = qGstErrorMessageIfElementsNotAvailable(
111 "queue",
"capsfilter",
"videoconvert",
"jpegenc",
"jifmux",
"fakesink");
113 return q23::unexpected{ *error };
115 return new QGstreamerImageCapture(parent);
118QGstreamerImageCapture::QGstreamerImageCapture(QImageCapture *parent)
119 : QPlatformImageCapture(parent),
120 QGstreamerBufferProbe(ProbeBuffers),
122 QGstBin::create(
"imageCaptureBin"),
125 QGstElement::createFromFactory(
"queue",
"imageCaptureQueue"),
128 QGstElement::createFromFactory(
"capsfilter",
"filter"),
131 QGstElement::createFromFactory(
"videoconvert",
"imageCaptureConvert"),
134 QGstElement::createFromFactory(
"jpegenc",
"jpegEncoder"),
137 QGstElement::createFromFactory(
"jifmux",
"jpegMuxer"),
140 QGstElement::createFromFactory(
"fakesink",
"imageCaptureSink"),
144 queue.set(
"leaky", 2 );
145 queue.set(
"silent",
true);
146 queue.set(
"max-size-buffers",
int(1));
147 queue.set(
"max-size-bytes",
int(0));
148 queue.set(
"max-size-time", uint64_t(0));
150 bin.add(queue, filter, videoConvert, encoder, muxer, sink);
151 qLinkGstElements(queue, filter, videoConvert, encoder, muxer, sink);
152 bin.addGhostPad(queue,
"sink");
154 addProbeToPad(queue.staticPad(
"src").pad(),
false);
156 sink.set(
"async",
false);
159QGstreamerImageCapture::~QGstreamerImageCapture()
161 bin.setStateSync(GST_STATE_NULL);
164 auto pendingFutures = [&] {
165 QMutexLocker guard(&m_pendingFuturesMutex);
166 return std::move(m_pendingFutures);
169 for (
auto &element : pendingFutures)
170 element.second.waitForFinished();
173bool QGstreamerImageCapture::isReadyForCapture()
const
175 QMutexLocker guard(&m_mutex);
176 return m_session && !m_captureNextBuffer && cameraActive;
179int QGstreamerImageCapture::capture(
const QString &fileName)
181 using namespace Qt::Literals;
182 QString path = QMediaStorageLocation::generateFileName(
183 fileName, QStandardPaths::PicturesLocation, u"jpg"_s);
184 return doCapture(std::move(path));
187int QGstreamerImageCapture::captureToBuffer()
189 return doCapture(QString());
192int QGstreamerImageCapture::doCapture(QString fileName)
194 qCDebug(qLcImageCaptureGst) <<
"do capture";
197 QMutexLocker guard(&m_mutex);
199 invokeDeferred([
this] {
200 emit error(-1, QImageCapture::ResourceError,
201 QPlatformImageCapture::msgImageCaptureNotSet());
204 qCDebug(qLcImageCaptureGst) <<
"error 1";
207 if (!m_session->camera()) {
208 invokeDeferred([
this] {
209 emit error(-1, QImageCapture::ResourceError, tr(
"No camera available."));
212 qCDebug(qLcImageCaptureGst) <<
"error 2";
215 if (m_captureNextBuffer) {
216 invokeDeferred([
this] {
217 emit error(-1, QImageCapture::NotReadyError,
218 QPlatformImageCapture::msgCameraNotReady());
221 qCDebug(qLcImageCaptureGst) <<
"error 3";
226 pendingImages.enqueue({ m_lastId, std::move(fileName) });
228 m_captureNextBuffer =
true;
231 emit readyForCaptureChanged(
false);
235void QGstreamerImageCapture::saveBufferToFile(QGstBufferHandle buffer, QString filename,
int taskId)
237 Q_ASSERT(!filename.isEmpty());
240 [
this, taskId, filename = std::move(filename), buffer = std::move(buffer)]()
mutable {
241 QMutexLocker guard(&m_mutex);
242 qCDebug(qLcImageCaptureGst) <<
"saving image as" << filename;
245 if (!f.open(QFile::WriteOnly)) {
246 qCDebug(qLcImageCaptureGst) <<
" could not open image file for writing";
251 if (gst_buffer_map(buffer.get(), &info, GST_MAP_READ)) {
252 f.write(
reinterpret_cast<
const char *>(info.data), info.size);
253 gst_buffer_unmap(buffer.get(), &info);
257 QMetaObject::invokeMethod(
this, [
this, taskId, filename = std::move(filename)]()
mutable {
258 emit imageSaved(taskId, filename);
262void QGstreamerImageCapture::convertBufferToImage(
const QMutexLocker<QRecursiveMutex> &locker,
263 QGstBufferHandle buffer,
QGstCaps caps,
264 QMediaMetaData metadata,
int taskId)
266 using namespace Qt::Literals;
267 Q_ASSERT(locker.mutex() == &m_mutex);
268 Q_ASSERT(locker.isLocked());
271 constexpr bool isOpenGLPlatform = QT_CONFIG(opengl);
275 static const bool isWaylandQPA = QGuiApplication::platformName() == u"wayland"_s;
277 if (isOpenGLPlatform || isWaylandQPA) {
279 qDebug() <<
"QGstreamerImageCapture::convertBufferToImage: no session";
284 auto optionalVideoInfo = caps.videoInfo();
285 if (optionalVideoInfo)
286 previewInfo = *optionalVideoInfo;
287 QVideoFrame frame = qCreateFrameFromGstBuffer(buffer, previewInfo);
289 metadata.insert(QMediaMetaData::Resolution, frame.size());
292 [
this, frame = std::move(frame), taskId, metadata = std::move(metadata)]()
mutable {
293 QImage img = frame.toImage();
295 qDebug() <<
"received a null image";
299 emit imageExposed(taskId);
300 qCDebug(qLcImageCaptureGst) <<
"Image available!";
301 emit imageAvailable(taskId, frame);
302 emit imageCaptured(taskId, img);
303 emit imageMetadataAvailable(taskId, metadata);
306 runInThreadPool([
this, taskId, buffer = std::move(buffer), caps = std::move(caps),
307 metadata = std::move(metadata)]()
mutable {
308 QMutexLocker guard(&m_mutex);
310 qDebug() <<
"QGstreamerImageCapture::probeBuffer: no session";
315 QVideoFrameFormat fmt;
316 auto optionalVideoInfo = caps.videoInfo();
317 if (optionalVideoInfo) {
318 previewInfo = *optionalVideoInfo;
319 fmt = qVideoFrameFormatFromGstVideoInfo(previewInfo);
322 auto gstBuffer = std::make_unique<QGstVideoBuffer>(std::move(buffer), previewInfo, fmt);
324 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(gstBuffer), fmt);
325 QImage img = frame.toImage();
327 qDebug() <<
"received a null image";
332 imageMetaData.insert(QMediaMetaData::Resolution, frame.size());
334 invokeDeferred([
this, taskId, metadata = std::move(metadata), frame = std::move(frame),
335 img = std::move(img)]()
mutable {
336 emit imageExposed(taskId);
337 qCDebug(qLcImageCaptureGst) <<
"Image available!";
338 emit imageAvailable(taskId, frame);
339 emit imageCaptured(taskId, img);
340 emit imageMetadataAvailable(taskId, metadata);
346void QGstreamerImageCapture::setResolution(
const QSize &resolution)
348 QGstCaps padCaps = bin.staticPad(
"sink").currentCaps();
350 qDebug() <<
"Camera not ready";
357 gst_caps_set_simple(caps.caps(),
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT,
358 resolution.height(),
nullptr);
359 filter.set(
"caps", caps);
362bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer)
364 if (!m_captureNextBuffer.load())
367 QMutexLocker guard(&m_mutex);
368 qCDebug(qLcImageCaptureGst) <<
"probe buffer";
370 QGstBufferHandle bufferHandle{
372 QGstBufferHandle::NeedsRef,
375 m_captureNextBuffer =
false;
377 bool ready = isReadyForCapture();
378 invokeDeferred([
this, ready] {
379 emit readyForCaptureChanged(ready);
383 PendingImage imageData = pendingImages.dequeue();
384 QString saveFileName = imageData.filename;
385 if (!saveFileName.isEmpty())
386 saveBufferToFile(bufferHandle, std::move(saveFileName), imageData.id);
389 QGstCaps caps = bin.staticPad(
"sink").currentCaps();
391 convertBufferToImage(guard, bufferHandle, std::move(caps), std::move(imageMetaData),
397void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
399 QMutexLocker guard(&m_mutex);
400 QGstreamerMediaCaptureSession *captureSession =
static_cast<QGstreamerMediaCaptureSession *>(session);
401 if (m_session == captureSession)
404 bool readyForCapture = isReadyForCapture();
406 disconnect(m_session,
nullptr,
this,
nullptr);
408 pendingImages.clear();
409 m_captureNextBuffer =
false;
410 cameraActive =
false;
413 m_session = captureSession;
416 emit readyForCaptureChanged(
false);
420 connect(m_session, &QPlatformMediaCaptureSession::cameraChanged,
this,
421 &QGstreamerImageCapture::onCameraChanged);
425void QGstreamerImageCapture::setMetaData(
const QMediaMetaData &m)
428 QMutexLocker guard(&m_mutex);
429 QPlatformImageCapture::setMetaData(m);
433 applyMetaDataToTagSetter(m, muxer);
436void QGstreamerImageCapture::cameraActiveChanged(
bool active)
438 qCDebug(qLcImageCaptureGst) <<
"cameraActiveChanged" << cameraActive << active;
439 if (cameraActive == active)
441 cameraActive = active;
442 qCDebug(qLcImageCaptureGst) <<
"isReady" << isReadyForCapture();
443 emit readyForCaptureChanged(isReadyForCapture());
446void QGstreamerImageCapture::onCameraChanged()
448 QMutexLocker guard(&m_mutex);
449 if (m_session->camera()) {
450 cameraActiveChanged(m_session->camera()->isActive());
451 connect(m_session->camera(), &QPlatformCamera::activeChanged,
this,
452 &QGstreamerImageCapture::cameraActiveChanged);
454 cameraActiveChanged(
false);
458QImageEncoderSettings QGstreamerImageCapture::imageSettings()
const
463void QGstreamerImageCapture::setImageSettings(
const QImageEncoderSettings &settings)
465 if (m_settings != settings) {
466 QSize resolution = settings.resolution();
467 if (m_settings.resolution() != resolution && !resolution.isEmpty())
468 setResolution(resolution);
470 m_settings = settings;
476#include "moc_qgstreamerimagecapture_p.cpp"
ThreadPoolSingleton s_threadPoolSingleton
QThreadPool * get(const QMutexLocker< QMutex > &)
QFuture< void > run(Functor &&f)
bool m_appUnderDestruction