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>
17#include <common/qgstreamermetadata_p.h>
18#include <common/qgstvideobuffer_p.h>
19#include <common/qgstutils_p.h>
27Q_STATIC_LOGGING_CATEGORY(qLcImageCaptureGst,
"qt.multimedia.imageCapture")
40 if (m_appUnderDestruction || !qApp)
43 using namespace std::chrono;
45 m_instance =
new QThreadPool;
46 m_instance->setMaxThreadCount(1);
47 static constexpr auto expiryTimeout = minutes(5);
48 m_instance->setExpiryTimeout(round<milliseconds>(expiryTimeout).count());
50 QObject::connect(qApp, &QCoreApplication::aboutToQuit, &m_context, [&] {
53 QMutexLocker guard(&m_poolMutex);
56 m_appUnderDestruction =
true;
59 QObject::connect(qApp, &QCoreApplication::destroyed, &m_context, [&] {
60 m_appUnderDestruction =
false;
65 template <
typename Functor>
68 QMutexLocker guard(&m_poolMutex);
69 QThreadPool *pool = get(guard);
71 return QFuture<
void>{};
73 return QtConcurrent::run(pool, std::forward<Functor>(f));
83 static const auto error = qGstErrorMessageIfElementsNotAvailable(
84 "queue",
"capsfilter",
"videoconvert",
"jpegenc",
"jifmux",
"fakesink");
92 : QPlatformImageCapture(parent),
93 QGstreamerBufferProbe(ProbeBuffers),
95 QGstBin::create(
"imageCaptureBin"),
98 QGstElement::createFromFactory(
"queue",
"imageCaptureQueue"),
101 QGstElement::createFromFactory(
"capsfilter",
"filter"),
104 QGstElement::createFromFactory(
"videoconvert",
"imageCaptureConvert"),
107 QGstElement::createFromFactory(
"jpegenc",
"jpegEncoder"),
110 QGstElement::createFromFactory(
"jifmux",
"jpegMuxer"),
113 QGstElement::createFromFactory(
"fakesink",
"imageCaptureSink"),
117 queue.set(
"leaky", 2 );
118 queue.set(
"silent",
true);
119 queue.set(
"max-size-buffers",
int(1));
120 queue.set(
"max-size-bytes",
int(0));
121 queue.set(
"max-size-time", uint64_t(0));
123 bin.add(queue, filter, videoConvert, encoder, muxer, sink);
124 qLinkGstElements(queue, filter, videoConvert, encoder, muxer, sink);
125 bin.addGhostPad(queue,
"sink");
127 addProbeToPad(queue.staticPad(
"src").pad(),
false);
129 sink.set(
"signal-handoffs",
true);
130 sink.set(
"async",
false);
131 m_handoffConnection = sink.connect(
"handoff", G_CALLBACK(&saveImageFilter),
this);
136 bin.setStateSync(GST_STATE_NULL);
139 auto pendingFutures = [&] {
140 QMutexLocker guard(&m_mutex);
141 return std::move(m_pendingFutures);
144 for (QFuture<
void> &pendingImage : pendingFutures)
145 pendingImage.waitForFinished();
150 QMutexLocker guard(&m_mutex);
151 return m_session && !passImage && cameraActive;
156 using namespace Qt::Literals;
157 QString path = QMediaStorageLocation::generateFileName(
158 fileName, QStandardPaths::PicturesLocation, u"jpg"_s);
159 return doCapture(path);
164 return doCapture(QString());
169 qCDebug(qLcImageCaptureGst) <<
"do capture";
172 QMutexLocker guard(&m_mutex);
174 invokeDeferred([
this] {
175 emit error(-1, QImageCapture::ResourceError,
176 QPlatformImageCapture::msgImageCaptureNotSet());
179 qCDebug(qLcImageCaptureGst) <<
"error 1";
182 if (!m_session->camera()) {
183 invokeDeferred([
this] {
184 emit error(-1, QImageCapture::ResourceError, tr(
"No camera available."));
187 qCDebug(qLcImageCaptureGst) <<
"error 2";
191 invokeDeferred([
this] {
192 emit error(-1, QImageCapture::NotReadyError,
193 QPlatformImageCapture::msgCameraNotReady());
196 qCDebug(qLcImageCaptureGst) <<
"error 3";
201 pendingImages.enqueue({ m_lastId, fileName, QMediaMetaData{} });
206 emit readyForCaptureChanged(
false);
212 QGstCaps padCaps = bin.staticPad(
"sink").currentCaps();
213 if (padCaps.isNull()) {
214 qDebug() <<
"Camera not ready";
221 gst_caps_set_simple(caps.caps(),
"width", G_TYPE_INT, resolution.width(),
"height", G_TYPE_INT,
222 resolution.height(),
nullptr);
223 filter.set(
"caps", caps);
227#if __cplusplus
>= 202002L
228# define EQ_THIS_CAPTURE =, this
230# define EQ_THIS_CAPTURE =
235 QMutexLocker guard(&m_mutex);
239 qCDebug(qLcImageCaptureGst) <<
"probe buffer";
241 QGstBufferHandle bufferHandle{
243 QGstBufferHandle::NeedsRef,
249 invokeDeferred([
this, ready] {
250 emit readyForCaptureChanged(ready);
253 QGstCaps caps = bin.staticPad(
"sink").currentCaps();
256 GstVideoInfo previewInfo;
257 QVideoFrameFormat fmt;
258 auto optionalFormatAndVideoInfo = caps.formatAndVideoInfo();
259 if (optionalFormatAndVideoInfo)
260 std::tie(fmt, previewInfo) = std::move(*optionalFormatAndVideoInfo);
262 int futureId = futureIDAllocator += 1;
266 QFuture<
void> future = s_threadPoolSingleton.run([
EQ_THIS_CAPTURE]()
mutable {
267 QMutexLocker guard(&m_mutex);
268 auto scopeExit = qScopeGuard([&] {
269 m_pendingFutures.remove(futureId);
273 qDebug() <<
"QGstreamerImageCapture::probeBuffer: no session";
277 auto *sink = m_session->gstreamerVideoSink();
278 auto gstBuffer = std::make_unique<QGstVideoBuffer>(std::move(bufferHandle), previewInfo,
279 sink, fmt, memoryFormat);
281 QVideoFrame frame = QVideoFramePrivate::createFrame(std::move(gstBuffer), fmt);
282 QImage img = frame.toImage();
284 qDebug() <<
"received a null image";
288 QMediaMetaData imageMetaData = metaData();
289 imageMetaData.insert(QMediaMetaData::Resolution, frame.size());
290 pendingImages.head().metaData = std::move(imageMetaData);
291 PendingImage pendingImage = pendingImages.head();
293 invokeDeferred([
this, pendingImage = std::move(pendingImage), frame = std::move(frame),
294 img = std::move(img)]()
mutable {
295 emit imageExposed(pendingImage.id);
296 qCDebug(qLcImageCaptureGst) <<
"Image available!";
297 emit imageAvailable(pendingImage.id, frame);
298 emit imageCaptured(pendingImage.id, img);
299 emit imageMetadataAvailable(pendingImage.id, pendingImage.metaData);
303 if (!future.isValid())
306 m_pendingFutures.insert(futureId, future);
311#undef EQ_THIS_CAPTURE
315 QMutexLocker guard(&m_mutex);
316 QGstreamerMediaCaptureSession *captureSession =
static_cast<QGstreamerMediaCaptureSession *>(session);
317 if (m_session == captureSession)
322 disconnect(m_session,
nullptr,
this,
nullptr);
324 pendingImages.clear();
326 cameraActive =
false;
329 m_session = captureSession;
332 emit readyForCaptureChanged(
false);
336 connect(m_session, &QPlatformMediaCaptureSession::cameraChanged,
this,
337 &QGstreamerImageCapture::onCameraChanged);
344 QMutexLocker guard(&m_mutex);
345 QPlatformImageCapture::setMetaData(m);
349 applyMetaDataToTagSetter(m, muxer);
354 qCDebug(qLcImageCaptureGst) <<
"cameraActiveChanged" << cameraActive << active;
355 if (cameraActive == active)
357 cameraActive = active;
358 qCDebug(qLcImageCaptureGst) <<
"isReady" << isReadyForCapture();
359 emit readyForCaptureChanged(isReadyForCapture());
364 QMutexLocker guard(&m_mutex);
365 if (m_session->camera()) {
366 cameraActiveChanged(m_session->camera()->isActive());
367 connect(m_session->camera(), &QPlatformCamera::activeChanged,
this,
368 &QGstreamerImageCapture::cameraActiveChanged);
370 cameraActiveChanged(
false);
377 capture->saveBufferToImage(buffer);
383 QMutexLocker guard(&m_mutex);
386 if (pendingImages.isEmpty())
389 PendingImage imageData = pendingImages.dequeue();
390 if (imageData.filename.isEmpty())
393 int id = futureIDAllocator++;
394 QGstBufferHandle bufferHandle{
396 QGstBufferHandle::NeedsRef,
399 QFuture<
void> saveImageFuture = QtConcurrent::run([
this, imageData, bufferHandle,
401 auto cleanup = qScopeGuard([&] {
402 QMutexLocker guard(&m_mutex);
403 m_pendingFutures.remove(id);
406 qCDebug(qLcImageCaptureGst) <<
"saving image as" << imageData.filename;
408 QFile f(imageData.filename);
409 if (!f.open(QFile::WriteOnly)) {
410 qCDebug(qLcImageCaptureGst) <<
" could not open image file for writing";
415 GstBuffer *buffer = bufferHandle.get();
416 if (gst_buffer_map(buffer, &info, GST_MAP_READ)) {
417 f.write(
reinterpret_cast<
const char *>(info.data), info.size);
418 gst_buffer_unmap(buffer, &info);
422 QMetaObject::invokeMethod(
this, [
this, imageData = std::move(imageData)]()
mutable {
423 emit imageSaved(imageData.id, imageData.filename);
427 m_pendingFutures.insert(id, saveImageFuture);
437 if (m_settings != settings) {
438 QSize resolution = settings.resolution();
439 if (m_settings.resolution() != resolution && !resolution.isEmpty())
440 setResolution(resolution);
442 m_settings = settings;
448#include "moc_qgstreamerimagecapture_p.cpp"
MemoryFormat memoryFormat() const
int capture(const QString &fileName) override
bool isReadyForCapture() const override
virtual ~QGstreamerImageCapture()
bool probeBuffer(GstBuffer *buffer) override
QImageEncoderSettings imageSettings() const override
int captureToBuffer() override
void setImageSettings(const QImageEncoderSettings &settings) override
void setMetaData(const QMediaMetaData &m) override
void setCaptureSession(QPlatformMediaCaptureSession *session)
ThreadPoolSingleton s_threadPoolSingleton
QThreadPool * get(const QMutexLocker< QMutex > &)
QFuture< void > run(Functor &&f)
bool m_appUnderDestruction