diff options
Diffstat (limited to 'src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp')
-rw-r--r-- | src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp new file mode 100644 index 000000000..f0f2f16e9 --- /dev/null +++ b/src/multimedia/platform/gstreamer/mediacapture/qgstreamerimagecapture.cpp @@ -0,0 +1,380 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgstreamerimagecapture_p.h" +#include "qplatformcamera_p.h" +#include <private/qgstvideobuffer_p.h> +#include <private/qgstutils_p.h> +#include <private/qgstreamermetadata_p.h> +#include <qvideoframeformat.h> + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <qstandardpaths.h> + +#include <qloggingcategory.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcImageCapture, "qt.multimedia.imageCapture") + +QGstreamerImageCapture::QGstreamerImageCapture(QImageCapture *parent) + : QPlatformImageCapture(parent), + QGstreamerBufferProbe(ProbeBuffers) +{ + bin = QGstBin("imageCaptureBin"); + + queue = QGstElement("queue", "imageCaptureQueue"); + // configures the queue to be fast, lightweight and non blocking + queue.set("leaky", 2 /*downstream*/); + queue.set("silent", true); + queue.set("max-size-buffers", 1); + queue.set("max-size-bytes", 0); + queue.set("max-size-time", 0); + + videoConvert = QGstElement("videoconvert", "imageCaptureConvert"); + encoder = QGstElement("jpegenc", "jpegEncoder"); + muxer = QGstElement("jifmux", "jpegMuxer"); + sink = QGstElement("fakesink","imageCaptureSink"); + bin.add(queue, videoConvert, encoder, muxer, sink); + queue.link(videoConvert, encoder, muxer, sink); + bin.addGhostPad(queue, "sink"); + bin.lockState(true); + + addProbeToPad(queue.staticPad("src").pad(), false); + + sink.set("signal-handoffs", true); + g_signal_connect(sink.object(), "handoff", G_CALLBACK(&QGstreamerImageCapture::saveImageFilter), this); +} + +QGstreamerImageCapture::~QGstreamerImageCapture() +{ + if (m_session) + m_session->releaseVideoPad(videoSrcPad); +} + +bool QGstreamerImageCapture::isReadyForCapture() const +{ + return m_session && !passImage && cameraActive; +} + + +static QDir defaultDir() +{ + QStringList dirCandidates; + + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + dirCandidates << QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + dirCandidates << QDir::homePath(); + dirCandidates << QDir::currentPath(); + dirCandidates << QDir::tempPath(); + + for (const QString &path : qAsConst(dirCandidates)) { + QDir dir(path); + if (dir.exists() && QFileInfo(path).isWritable()) + return dir; + } + + return QDir(); +} + +QString generateFileName(const QDir &dir, const QString &ext) +{ + int lastClip = 0; + const auto list = dir.entryList(QStringList() << QString::fromLatin1("img_*.%1").arg(ext)); + for (const QString &fileName : list) { + int imgNumber = QStringView{fileName}.mid(5, fileName.size()-6-ext.length()).toInt(); + lastClip = qMax(lastClip, imgNumber); + } + + QString name = QString::fromLatin1("img_%1.%2") + .arg(lastClip+1, + 4, //fieldWidth + 10, + QLatin1Char('0')) + .arg(ext); + + return dir.absoluteFilePath(name); +} + + +int QGstreamerImageCapture::capture(const QString &fileName) +{ + QString path = fileName; + if (path.isEmpty()) + path = generateFileName(defaultDir(), QLatin1String("jpg")); + + return doCapture(path); +} + +int QGstreamerImageCapture::captureToBuffer() +{ + return doCapture(QString()); +} + +int QGstreamerImageCapture::doCapture(const QString &fileName) +{ + qCDebug(qLcImageCapture) << "do capture"; + if (!m_session) { + //emit error in the next event loop, + //so application can associate it with returned request id. + QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection, + Q_ARG(int, -1), + Q_ARG(int, QImageCapture::ResourceError), + Q_ARG(QString,tr("Image capture not set to a session."))); + + qCDebug(qLcImageCapture) << "error 1"; + return -1; + } + if (!m_session->camera()) { + //emit error in the next event loop, + //so application can associate it with returned request id. + QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection, + Q_ARG(int, -1), + Q_ARG(int, QImageCapture::ResourceError), + Q_ARG(QString,tr("No camera available."))); + + qCDebug(qLcImageCapture) << "error 2"; + return -1; + } + if (passImage) { + //emit error in the next event loop, + //so application can associate it with returned request id. + QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection, + Q_ARG(int, -1), + Q_ARG(int, QImageCapture::NotReadyError), + Q_ARG(QString,tr("Camera is not ready."))); + + qCDebug(qLcImageCapture) << "error 3"; + return -1; + } + m_lastId++; + + pendingImages.enqueue({m_lastId, fileName, QMediaMetaData{}}); + // let one image pass the pipeline + passImage = true; + + link(); + + gstPipeline.dumpGraph("captureImage"); + + emit readyForCaptureChanged(false); + return m_lastId; +} + +bool QGstreamerImageCapture::probeBuffer(GstBuffer *buffer) +{ + if (!passImage) + return false; + qCDebug(qLcImageCapture) << "probe buffer"; + + passImage = false; + + emit readyForCaptureChanged(isReadyForCapture()); + + GstCaps *caps = gst_pad_get_current_caps(bin.staticPad("sink").pad()); + GstVideoInfo previewInfo; + gst_video_info_from_caps(&previewInfo, caps); + + auto *gstBuffer = new QGstVideoBuffer(buffer, previewInfo); + auto fmt = QGstUtils::formatForCaps(caps, &previewInfo); + QVideoFrame frame(gstBuffer, fmt); + QImage img = frame.toImage(); + if (img.isNull()) { + qDebug() << "received a null image"; + return true; + } + + auto &imageData = pendingImages.head(); + + emit imageExposed(imageData.id); + + qCDebug(qLcImageCapture) << "Image available!"; + emit imageAvailable(imageData.id, frame); + + emit imageCaptured(imageData.id, img); + + QMediaMetaData metaData = this->metaData(); + metaData.insert(QMediaMetaData::Date, QDateTime::currentDateTime()); + metaData.insert(QMediaMetaData::Resolution, frame.size()); + imageData.metaData = metaData; + + // ensure taginject injects this metaData + const auto &md = static_cast<const QGstreamerMetaData &>(metaData); + md.setMetaData(muxer.element()); + + emit imageMetadataAvailable(imageData.id, metaData); + + return true; +} + +void QGstreamerImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session) +{ + QGstreamerMediaCapture *captureSession = static_cast<QGstreamerMediaCapture *>(session); + if (m_session == captureSession) + return; + + if (m_session) { + disconnect(m_session, nullptr, this, nullptr); + m_lastId = 0; + pendingImages.clear(); + passImage = false; + cameraActive = false; + bin.setStateSync(GST_STATE_NULL); + gstPipeline.remove(bin); + gstPipeline = {}; + } + + m_session = captureSession; + if (!m_session) + return; + + gstPipeline = captureSession->pipeline(); + gstPipeline.add(bin); + bin.setStateSync(GST_STATE_READY); + connect(m_session, &QPlatformMediaCaptureSession::cameraChanged, this, &QGstreamerImageCapture::onCameraChanged); + onCameraChanged(); +} + +void QGstreamerImageCapture::cameraActiveChanged(bool active) +{ + qCDebug(qLcImageCapture) << "cameraActiveChanged" << cameraActive << active; + if (cameraActive == active) + return; + cameraActive = active; + qCDebug(qLcImageCapture) << "isReady" << isReadyForCapture(); + emit readyForCaptureChanged(isReadyForCapture()); +} + +void QGstreamerImageCapture::onCameraChanged() +{ + if (m_session->camera()) { + cameraActiveChanged(m_session->camera()->isActive()); + connect(m_session->camera(), &QPlatformCamera::activeChanged, this, &QGstreamerImageCapture::cameraActiveChanged); + } + +} + +gboolean QGstreamerImageCapture::saveImageFilter(GstElement *element, + GstBuffer *buffer, + GstPad *pad, + void *appdata) +{ + Q_UNUSED(element); + Q_UNUSED(pad); + QGstreamerImageCapture *capture = static_cast<QGstreamerImageCapture *>(appdata); + + if (capture->pendingImages.isEmpty()) { + capture->unlink(); + return true; + } + + auto imageData = capture->pendingImages.dequeue(); + if (imageData.filename.isEmpty()) { + capture->unlink(); + return true; + } + + qCDebug(qLcImageCapture) << "saving image as" << imageData.filename; + + QFile f(imageData.filename); + if (f.open(QFile::WriteOnly)) { + GstMapInfo info; + if (gst_buffer_map(buffer, &info, GST_MAP_READ)) { + f.write(reinterpret_cast<const char *>(info.data), info.size); + gst_buffer_unmap(buffer, &info); + } + f.close(); + + static QMetaMethod savedSignal = QMetaMethod::fromSignal(&QGstreamerImageCapture::imageSaved); + savedSignal.invoke(capture, + Qt::QueuedConnection, + Q_ARG(int, imageData.id), + Q_ARG(QString, imageData.filename)); + } + + capture->unlink(); + + return TRUE; +} + +void QGstreamerImageCapture::unlink() +{ + return; + if (passImage) + return; + if (gstPipeline.isNull()) + return; + gstPipeline.setStateSync(GST_STATE_PAUSED); + videoSrcPad.unlinkPeer(); + m_session->releaseVideoPad(videoSrcPad); + videoSrcPad = {}; + bin.setStateSync(GST_STATE_READY); + bin.lockState(true); + gstPipeline.setStateSync(GST_STATE_PLAYING); +} + +void QGstreamerImageCapture::link() +{ + if (!(m_session && m_session->camera())) + return; + if (!bin.staticPad("sink").peer().isNull() || gstPipeline.isNull()) + return; + gstPipeline.setStateSync(GST_STATE_PAUSED); + videoSrcPad = m_session->getVideoPad(); + videoSrcPad.link(bin.staticPad("sink")); + bin.lockState(false); + bin.setState(GST_STATE_PAUSED); + gstPipeline.setStateSync(GST_STATE_PLAYING); +} + +QImageEncoderSettings QGstreamerImageCapture::imageSettings() const +{ + return m_settings; +} + +void QGstreamerImageCapture::setImageSettings(const QImageEncoderSettings &settings) +{ + if (m_settings != settings) { + m_settings = settings; + // ### + } +} + +QT_END_NAMESPACE |