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 GstVideoInfo previewInfo;
285 QVideoFrameFormat fmt;
286 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
287 if (optionalFormatAndVideoInfo)
288 std::tie(fmt, previewInfo) =
std::move(*optionalFormatAndVideoInfo);
291 auto gstBuffer = std::make_unique<QGstVideoBuffer>(std::move(buffer), previewInfo, sink,
293 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(gstBuffer), fmt);
295 metadata.insert(QMediaMetaData::Resolution, frame.size());
298 [
this, frame = std::move(frame), taskId, metadata = std::move(metadata)]()
mutable {
299 QImage img = frame.toImage();
301 qDebug() <<
"received a null image";
305 emit imageExposed(taskId);
306 qCDebug(qLcImageCaptureGst) <<
"Image available!";
307 emit imageAvailable(taskId, frame);
308 emit imageCaptured(taskId, img);
309 emit imageMetadataAvailable(taskId, metadata);
312 runInThreadPool([
this, taskId, buffer = std::move(buffer), caps = std::move(caps),
313 metadata = std::move(metadata)]()
mutable {
314 QMutexLocker guard(&m_mutex);
316 qDebug() <<
"QGstreamerImageCapture::probeBuffer: no session";
322 GstVideoInfo previewInfo;
323 QVideoFrameFormat fmt;
324 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
325 if (optionalFormatAndVideoInfo)
326 std::tie(fmt, previewInfo) =
std::move(*optionalFormatAndVideoInfo);
329 auto gstBuffer = std::make_unique<QGstVideoBuffer>(std::move(buffer), previewInfo, sink,
332 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(gstBuffer), fmt);
333 QImage img = frame.toImage();
335 qDebug() <<
"received a null image";
340 imageMetaData.insert(QMediaMetaData::Resolution, frame.size());
342 invokeDeferred([
this, taskId, metadata = std::move(metadata), frame = std::move(frame),
343 img = std::move(img)]()
mutable {
344 emit imageExposed(taskId);
345 qCDebug(qLcImageCaptureGst) <<
"Image available!";
346 emit imageAvailable(taskId, frame);
347 emit imageCaptured(taskId, img);
348 emit imageMetadataAvailable(taskId, metadata);
354void QGstreamerImageCapture::setResolution(
const QSize &resolution)
356 QGstCaps padCaps = bin.staticPad(
"sink").currentCaps();
358 qDebug() <<
"Camera not ready";
365 gst_caps_set_simple(caps.caps(),
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT,
366 resolution.height(),
nullptr);
367 filter.set(
"caps", caps);
370bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer)
372 if (!m_captureNextBuffer.load())
375 QMutexLocker guard(&m_mutex);
376 qCDebug(qLcImageCaptureGst) <<
"probe buffer";
378 QGstBufferHandle bufferHandle{
380 QGstBufferHandle::NeedsRef,
383 m_captureNextBuffer =
false;
385 bool ready = isReadyForCapture();
386 invokeDeferred([
this, ready] {
387 emit readyForCaptureChanged(ready);
391 PendingImage imageData = pendingImages.dequeue();
392 QString saveFileName = imageData.filename;
393 if (!saveFileName.isEmpty())
394 saveBufferToFile(bufferHandle, std::move(saveFileName), imageData.id);
397 QGstCaps caps = bin.staticPad(
"sink").currentCaps();
399 convertBufferToImage(guard, bufferHandle, std::move(caps), std::move(imageMetaData),
405void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
407 QMutexLocker guard(&m_mutex);
408 QGstreamerMediaCaptureSession *captureSession =
static_cast<QGstreamerMediaCaptureSession *>(session);
409 if (m_session == captureSession)
412 bool readyForCapture = isReadyForCapture();
414 disconnect(m_session,
nullptr,
this,
nullptr);
416 pendingImages.clear();
417 m_captureNextBuffer =
false;
418 cameraActive =
false;
421 m_session = captureSession;
424 emit readyForCaptureChanged(
false);
428 connect(m_session, &QPlatformMediaCaptureSession::cameraChanged,
this,
429 &QGstreamerImageCapture::onCameraChanged);
433void QGstreamerImageCapture::setMetaData(
const QMediaMetaData &m)
436 QMutexLocker guard(&m_mutex);
437 QPlatformImageCapture::setMetaData(m);
441 applyMetaDataToTagSetter(m, muxer);
444void QGstreamerImageCapture::cameraActiveChanged(
bool active)
446 qCDebug(qLcImageCaptureGst) <<
"cameraActiveChanged" << cameraActive << active;
447 if (cameraActive == active)
449 cameraActive = active;
450 qCDebug(qLcImageCaptureGst) <<
"isReady" << isReadyForCapture();
451 emit readyForCaptureChanged(isReadyForCapture());
454void QGstreamerImageCapture::onCameraChanged()
456 QMutexLocker guard(&m_mutex);
457 if (m_session->camera()) {
458 cameraActiveChanged(m_session->camera()->isActive());
459 connect(m_session->camera(), &QPlatformCamera::activeChanged,
this,
460 &QGstreamerImageCapture::cameraActiveChanged);
462 cameraActiveChanged(
false);
466QImageEncoderSettings QGstreamerImageCapture::imageSettings()
const
471void QGstreamerImageCapture::setImageSettings(
const QImageEncoderSettings &settings)
473 if (m_settings != settings) {
474 QSize resolution = settings.resolution();
475 if (m_settings.resolution() != resolution && !resolution.isEmpty())
476 setResolution(resolution);
478 m_settings = settings;
484#include "moc_qgstreamerimagecapture_p.cpp"
MemoryFormat memoryFormat() const
ThreadPoolSingleton s_threadPoolSingleton
QThreadPool * get(const QMutexLocker< QMutex > &)
QFuture< void > run(Functor &&f)
bool m_appUnderDestruction