Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qffmpegimagecapture_p.h"
++ #include <private/qplatformmediaformatinfo_p.h>
++ #include <private/qplatformcamera_p.h>
++ #include <private/qplatformimagecapture_p.h>
++ #include <qvideoframeformat.h>
++ #include <private/qmediastoragelocation_p.h>
++ #include <qimagewriter.h>
++
++ #include <QtCore/QDebug>
++ #include <QtCore/QDir>
++ #include <qstandardpaths.h>
++
++ #include <qloggingcategory.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ // Probably, might be increased. To be investigated and tested on Android implementation
++ static constexpr int MaxPendingImagesCount = 1;
++
++ Q_STATIC_LOGGING_CATEGORY(qLcImageCapture, "qt.multimedia.imageCapture")
++
++ QFFmpegImageCapture::QFFmpegImageCapture(QImageCapture *parent)
++ : QPlatformImageCapture(parent)
++ {
++ qRegisterMetaType<QVideoFrame>();
++ }
++
++ QFFmpegImageCapture::~QFFmpegImageCapture() = default;
++
++ bool QFFmpegImageCapture::isReadyForCapture() const
++ {
++ return m_isReadyForCapture;
++ }
++
++ static const char *extensionForFormat(QImageCapture::FileFormat format)
++ {
++ const char *fmt = "jpg";
++ switch (format) {
++ case QImageCapture::UnspecifiedFormat:
++ case QImageCapture::JPEG:
++ fmt = "jpg";
++ break;
++ case QImageCapture::PNG:
++ fmt = "png";
++ break;
++ case QImageCapture::WebP:
++ fmt = "webp";
++ break;
++ case QImageCapture::Tiff:
++ fmt = "tiff";
++ break;
++ }
++ return fmt;
++ }
++
++ int QFFmpegImageCapture::capture(const QString &fileName)
++ {
++ QString path = QMediaStorageLocation::generateFileName(fileName, QStandardPaths::PicturesLocation, QLatin1String(extensionForFormat(m_settings.format())));
++ return doCapture(path);
++ }
++
++ int QFFmpegImageCapture::captureToBuffer()
++ {
++ return doCapture(QString());
++ }
++
++ int QFFmpegImageCapture::doCapture(const QString &fileName)
++ {
++ using namespace Qt::Literals::StringLiterals;
++
++ if (!m_session) {
++ //emit error in the next event loop,
++ //so application can associate it with returned request id.
++ QMetaObject::invokeMethod(
++ this,
++ [this]() {
++ Q_EMIT error(
++ -1,
++ QImageCapture::ResourceError,
++ QPlatformImageCapture::msgImageCaptureNotSet());
++ });
++ return -1;
++ }
++
++ if (!m_videoSource) {
++ //emit error in the next event loop,
++ //so application can associate it with returned request id.
++ QMetaObject::invokeMethod(
++ this,
++ [this]() {
++ Q_EMIT error(
++ -1,
++ QImageCapture::ResourceError,
++ u"No camera available."_s);
++ });
++ return -1;
++ }
++
++ if (m_pendingImages.size() >= MaxPendingImagesCount) {
++ //emit error in the next event loop,
++ //so application can associate it with returned request id.
++ QMetaObject::invokeMethod(
++ this,
++ [this]() {
++ Q_EMIT error(
++ -1,
++ QImageCapture::NotReadyError,
++ QPlatformImageCapture::msgCameraNotReady());
++ });
++ return -1;
++ }
++
++ m_lastId++;
++
++ m_pendingImages.enqueue({ m_lastId, fileName, QMediaMetaData{} });
++ updateReadyForCapture();
++
++ return m_lastId;
++ }
++
++ void QFFmpegImageCapture::setCaptureSession(QPlatformMediaCaptureSession *session)
++ {
++ auto *captureSession = static_cast<QFFmpegMediaCaptureSession *>(session);
++ if (m_session == captureSession)
++ return;
++
++ if (m_session) {
++ m_session->disconnect(this);
++ m_lastId = 0;
++ m_pendingImages.clear();
++ }
++
++ m_session = captureSession;
++
++ if (m_session) {
++ connect(m_session, &QFFmpegMediaCaptureSession::primaryActiveVideoSourceChanged, this,
++ &QFFmpegImageCapture::onVideoSourceChanged);
++ connect(m_session, &QFFmpegMediaCaptureSession::cameraChanged, this,
++ &QFFmpegImageCapture::applySettingsToVideoSource);
++ }
++
++ onVideoSourceChanged();
++
++ if (m_session)
++ applySettingsToVideoSource();
++ }
++
++ void QFFmpegImageCapture::cancelPendingImage(QImageCapture::Error error, const QString &errorMsg)
++ {
++ if (m_pendingImages.empty()) {
++ qCDebug(qLcImageCapture) <<
++ "QImageCapture backend received an event to cancel a pending capture, "
++ "but no captures are pending. Most likely an internal Qt bug.";
++ return;
++ }
++
++ PendingImage cancelledImage = m_pendingImages.dequeue();
++
++ Q_EMIT QPlatformImageCapture::error(cancelledImage.id, error, errorMsg);
++
++ updateReadyForCapture();
++ }
++
++ void QFFmpegImageCapture::updateReadyForCapture()
++ {
++ const bool ready = m_session && m_pendingImages.size() < MaxPendingImagesCount && m_videoSource
++ && m_videoSource->isActive();
++
++ qCDebug(qLcImageCapture) << "updateReadyForCapture" << ready;
++
++ if (std::exchange(m_isReadyForCapture, ready) != ready)
++ Q_EMIT readyForCaptureChanged(ready);
++ }
++
++ void QFFmpegImageCapture::newVideoFrame(const QVideoFrame &frame)
++ {
++ if (m_pendingImages.empty())
++ return;
++
++ auto pending = m_pendingImages.dequeue();
++
++ qCDebug(qLcImageCapture) << "Taking image" << pending.id;
++
++ Q_EMIT imageExposed(pending.id);
++ // ### Add metadata from the AVFrame
++ Q_EMIT imageMetadataAvailable(pending.id, pending.metaData);
++ Q_EMIT imageAvailable(pending.id, frame);
++ QImage image = frame.toImage();
++ // Assume image is rotated if its orientation differs from the requested resolution
++ const QSize resolution = m_settings.resolution();
++ const bool isRotated = image.width() != image.height()
++ && resolution.width() != resolution.height()
++ && (image.width() > image.height()) != (resolution.width() > resolution.height());
++ const QSize targetSize = isRotated ? resolution.transposed() : resolution;
++ if (targetSize.isValid() && image.size() != targetSize) {
++ qCWarning(qLcImageCapture)
++ << "Image needed to be scaled to match requested resolution (" << image.size()
++ << " -> " << targetSize
++ << ", consider using a resolution supported natively by the camera";
++ image = image.scaled(targetSize);
++ }
++
++ Q_EMIT imageCaptured(pending.id, image);
++ if (!pending.filename.isEmpty()) {
++ const char *fmt = nullptr;
++ switch (m_settings.format()) {
++ case QImageCapture::UnspecifiedFormat:
++ case QImageCapture::JPEG:
++ fmt = "jpeg";
++ break;
++ case QImageCapture::PNG:
++ fmt = "png";
++ break;
++ case QImageCapture::WebP:
++ fmt = "webp";
++ break;
++ case QImageCapture::Tiff:
++ fmt = "tiff";
++ break;
++ }
++ int quality = -1;
++ switch (m_settings.quality()) {
++ case QImageCapture::VeryLowQuality:
++ quality = 25;
++ break;
++ case QImageCapture::LowQuality:
++ quality = 50;
++ break;
++ case QImageCapture::NormalQuality:
++ break;
++ case QImageCapture::HighQuality:
++ quality = 75;
++ break;
++ case QImageCapture::VeryHighQuality:
++ quality = 99;
++ break;
++ }
++
++ QImageWriter writer(pending.filename, fmt);
++ writer.setQuality(quality);
++
++ if (writer.write(image)) {
++ Q_EMIT imageSaved(pending.id, pending.filename);
++ } else {
++ QImageCapture::Error err = QImageCapture::ResourceError;
++ if (writer.error() == QImageWriter::UnsupportedFormatError)
++ err = QImageCapture::FormatError;
++ Q_EMIT error(pending.id, err, writer.errorString());
++ }
++ }
++
++ updateReadyForCapture();
++ }
++
++ void QFFmpegImageCapture::setupVideoSourceConnections()
++ {
++ connect(m_videoSource, &QPlatformCamera::newVideoFrame, this,
++ &QFFmpegImageCapture::newVideoFrame);
++ }
++
++ QPlatformVideoSource *QFFmpegImageCapture::videoSource() const
++ {
++ return m_videoSource;
++ }
++
++ void QFFmpegImageCapture::onVideoSourceChanged()
++ {
++ if (m_videoSource)
++ m_videoSource->disconnect(this);
++
++ m_videoSource = m_session ? m_session->primaryActiveVideoSource() : nullptr;
++
++ // TODO: optimize, setup the connection only when the capture is ready
++ if (m_videoSource) {
++ setupVideoSourceConnections();
++ applySettingsToVideoSource();
++ }
++
++ updateReadyForCapture();
++ }
++
++ QImageEncoderSettings QFFmpegImageCapture::imageSettings() const
++ {
++ return m_settings;
++ }
++
++ void QFFmpegImageCapture::setImageSettings(const QImageEncoderSettings &settings)
++ {
++ auto s = settings;
++ const auto supportedFormats = QPlatformMediaIntegration::instance()->formatInfo()->imageFormats;
++ if (supportedFormats.isEmpty()) {
++ Q_EMIT error(-1, QImageCapture::FormatError, u"No image formats supported, can't capture."_s);
++ return;
++ }
++ if (s.format() == QImageCapture::UnspecifiedFormat) {
++ auto f = QImageCapture::JPEG;
++ if (!supportedFormats.contains(f))
++ f = supportedFormats.first();
++ s.setFormat(f);
++ } else if (!supportedFormats.contains(settings.format())) {
++ Q_EMIT error(-1, QImageCapture::FormatError, u"Image format not supported."_s);
++ return;
++ }
++
++ m_settings = settings;
++ applySettingsToVideoSource();
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qffmpegimagecapture_p.cpp"