Revision control
++ // Copyright (C) 2021 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 "qplatformmediaintegration_p.h"
++
++ #include <QtMultimedia/private/qplatformaudiodevices_p.h>
++ #include <QtMultimedia/private/qplatformaudioinput_p.h>
++ #include <QtMultimedia/private/qplatformaudiooutput_p.h>
++ #include <QtMultimedia/private/qplatformaudioresampler_p.h>
++ #include <QtMultimedia/private/qplatformcapturablewindows_p.h>
++ #include <QtMultimedia/private/qplatformmediaformatinfo_p.h>
++ #include <QtMultimedia/private/qplatformmediaplugin_p.h>
++ #include <QtMultimedia/private/qplatformvideodevices_p.h>
++ #include <QtMultimedia/private/qtmultimediaglobal_p.h>
++ #include <QtMultimedia/qcameradevice.h>
++ #include <QtMultimedia/qmediadevices.h>
++ #include <QtGui/qwindow.h>
++ #include <QtCore/private/qcoreapplication_p.h>
++ #include <QtCore/private/qfactoryloader_p.h>
++ #include <QtCore/qapplicationstatic.h>
++ #include <QtCore/qatomic.h>
++ #include <QtCore/qcoreapplication.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qmutex.h>
++
++ #include <optional>
++
++ namespace {
++
++ class QFallbackIntegration : public QPlatformMediaIntegration
++ {
++ public:
++ QFallbackIntegration() : QPlatformMediaIntegration(QLatin1String("fallback"))
++ {
++ qWarning("No QtMultimedia backends found. Only QMediaDevices, QAudioDevice, QSoundEffect, QAudioSink, and QAudioSource are available.");
++ }
++ };
++
++ Q_STATIC_LOGGING_CATEGORY(qLcMediaPlugin, "qt.multimedia.plugin")
++
++ Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
++ (QPlatformMediaPlugin_iid,
++ QLatin1String("/multimedia")))
++
++ static const auto FFmpegBackend = QStringLiteral("ffmpeg");
++
++ [[nodiscard]] static std::optional<QString> &requestedBackend()
++ {
++ static std::optional<QString> backend;
++ return backend;
++ }
++
++ [[nodiscard]] static QString defaultBackend(const QStringList &backends)
++ {
++ if (backends.isEmpty())
++ return {};
++
++ #ifdef QT_DEFAULT_MEDIA_BACKEND
++ auto backend = QString::fromUtf8(QT_DEFAULT_MEDIA_BACKEND);
++ if (backends.contains(backend))
++ return backend;
++ #endif
++
++ #if defined(Q_OS_DARWIN) || defined(Q_OS_LINUX) || defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
++ // Return ffmpeg backend by default.
++ // Platform backends for the OS list are optionally available but have limited support.
++ if (backends.contains(FFmpegBackend))
++ return FFmpegBackend;
++ #else
++ // Return platform backend (non-ffmpeg) by default.
++ if (backends.size() > 1 && backends[0] == FFmpegBackend)
++ return backends[1];
++ #endif
++
++ return backends[0];
++ }
++
++ struct InstanceHolder
++ {
++ InstanceHolder()
++ {
++ init();
++ }
++
++ void init()
++ {
++ if (!QCoreApplication::instance())
++ qCCritical(qLcMediaPlugin()) << "Qt Multimedia requires a QCoreApplication instance";
++
++ QStringList backends = QPlatformMediaIntegration::availableBackends();
++
++ // A backend explicitly requested via setBackend() takes precedence over
++ // the QT_MEDIA_BACKEND environment variable and the built-in default.
++ const std::optional<QString> &requested = requestedBackend();
++ QString backend = requested ? *requested : QString();
++ if (backend.isEmpty())
++ backend = QString::fromUtf8(qgetenv("QT_MEDIA_BACKEND")).toLower();
++ if (backend.isEmpty() && !backends.isEmpty())
++ backend = defaultBackend(backends);
++
++ qCDebug(qLcMediaPlugin) << "Loading media backend" << backend;
++ instance.reset(
++ qLoadPlugin<QPlatformMediaIntegration, QPlatformMediaPlugin>(loader(), backend));
++
++ if (instance)
++ return;
++
++ backends.removeAll(backend);
++
++ // A backend requested via setBackend() disables the fallback to other
++ // backends.
++ if (!requested) {
++ for (const QString &backend : std::as_const(backends)) {
++ qCDebug(qLcMediaPlugin) << "Loading alternative backend" << backend;
++ instance.reset(qLoadPlugin<QPlatformMediaIntegration, QPlatformMediaPlugin>(
++ loader(),
++ backend));
++ if (instance)
++ return;
++ }
++ } else if (!backend.isEmpty()) {
++ qCWarning(qLcMediaPlugin)
++ << "Media backend" << backend
++ << "failed to load and was explicitly requested; no media backend loaded";
++ }
++
++ // No backends found. Use fallback to support basic functionality
++ instance = std::make_unique<QFallbackIntegration>();
++ }
++
++ ~InstanceHolder()
++ {
++ instance.reset();
++ qCDebug(qLcMediaPlugin) << "Released media backend";
++ }
++
++ std::unique_ptr<QPlatformMediaIntegration> instance;
++ };
++
++ Q_APPLICATION_STATIC(InstanceHolder, s_instanceHolder);
++
++ } // namespace
++
++ QT_BEGIN_NAMESPACE
++
++ QPlatformMediaIntegration *QPlatformMediaIntegration::instance()
++ {
++ return s_instanceHolder->instance.get();
++ }
++
++ void QPlatformMediaIntegration::resetInstance()
++ {
++ s_instanceHolder->init(); // tests only
++ }
++
++ q23::expected<std::unique_ptr<QPlatformAudioResampler>, QString>
++ QPlatformMediaIntegration::createAudioResampler(const QAudioFormat &, const QAudioFormat &)
++ {
++ return q23::unexpected(notAvailable);
++ }
++
++ q23::expected<QPlatformAudioInput *, QString> QPlatformMediaIntegration::createAudioInput(QAudioInput *q)
++ {
++ return new QPlatformAudioInput(q);
++ }
++
++ q23::expected<QPlatformAudioOutput *, QString> QPlatformMediaIntegration::createAudioOutput(QAudioOutput *q)
++ {
++ return new QPlatformAudioOutput(q);
++ }
++
++ QList<QCapturableWindow> QPlatformMediaIntegration::capturableWindowsList()
++ {
++ const auto capturableWindows = this->capturableWindows();
++ return capturableWindows ? capturableWindows->windows() : QList<QCapturableWindow>{};
++ }
++
++ bool QPlatformMediaIntegration::isCapturableWindowValid(const QCapturableWindowPrivate &window)
++ {
++ const auto capturableWindows = this->capturableWindows();
++ return capturableWindows && capturableWindows->isWindowValid(window);
++ }
++
++ q23::expected<QCapturableWindow, QString> QPlatformMediaIntegration::capturableWindowFromQWindow(QWindow *window)
++ {
++ const auto capturableWindows = this->capturableWindows();
++ if (!capturableWindows)
++ return q23::unexpected{ QStringLiteral("No windowcapture platform implementation") };
++ if (window == nullptr)
++ return q23::unexpected{ QStringLiteral("QWindow is nullptr") };
++ if (!window->isTopLevel())
++ return q23::unexpected{ QStringLiteral("QWindow is not top-level.") };
++ return capturableWindows->fromQWindow(window);
++ }
++
++ const QPlatformMediaFormatInfo *QPlatformMediaIntegration::formatInfo()
++ {
++ std::call_once(m_formatInfoOnceFlg, [this]() {
++ m_formatInfo.reset(createFormatInfo());
++ Q_ASSERT(m_formatInfo);
++ });
++ return m_formatInfo.get();
++ }
++
++ QPlatformMediaFormatInfo *QPlatformMediaIntegration::createFormatInfo()
++ {
++ return new QPlatformMediaFormatInfo;
++ }
++
++ std::unique_ptr<QPlatformAudioDevices> QPlatformMediaIntegration::createAudioDevices()
++ {
++ return QPlatformAudioDevices::create();
++ }
++
++ // clang-format off
++ QPlatformVideoDevices *QPlatformMediaIntegration::videoDevices()
++ {
++ std::call_once(m_videoDevicesOnceFlag,
++ [this]() {
++ m_videoDevices.reset(createVideoDevices());
++ });
++ return m_videoDevices.get();
++ }
++
++ QPlatformCapturableWindows *QPlatformMediaIntegration::capturableWindows()
++ {
++ std::call_once(m_capturableWindowsOnceFlag,
++ [this]() {
++ m_capturableWindows.reset(createCapturableWindows());
++ });
++ return m_capturableWindows.get();
++ }
++
++ QPlatformAudioDevices *QPlatformMediaIntegration::audioDevices()
++ {
++ std::call_once(m_audioDevicesOnceFlag, [this] {
++ m_audioDevices = createAudioDevices();
++ });
++ return m_audioDevices.get();
++ }
++
++ // clang-format on
++
++ QStringList QPlatformMediaIntegration::availableBackends()
++ {
++ QStringList list;
++
++ if (QFactoryLoader *fl = loader()) {
++ const auto keyMap = fl->keyMap();
++ for (auto it = keyMap.constBegin(); it != keyMap.constEnd(); ++it)
++ if (!list.contains(it.value()))
++ list << it.value();
++ }
++
++ qCDebug(qLcMediaPlugin) << "Available backends" << list;
++ return list;
++ }
++
++ // Returns the name of the backend that would be loaded by default on this
++ // platform when neither the QT_MEDIA_BACKEND environment variable nor
++ // setBackend() selects one. Returns an empty string if no backend is available.
++ QString QPlatformMediaIntegration::defaultBackend()
++ {
++ return ::defaultBackend(availableBackends());
++ }
++
++ // Selects the media backend to load, taking precedence over both the
++ // QT_MEDIA_BACKEND environment variable and the built-in default. No fallback to
++ // other available backends is performed if the requested backend fails to load.
++ // Must be called before any media backend is initialized.
++ void QPlatformMediaIntegration::setBackend(const QString &backend)
++ {
++ Q_ASSERT(!s_instanceHolder.exists());
++ requestedBackend() = backend.toLower();
++ }
++
++ QLatin1String QPlatformMediaIntegration::name()
++ {
++ return m_backendName;
++ }
++
++ QVideoFrame QPlatformMediaIntegration::convertVideoFrame(QVideoFrame &,
++ const QVideoFrameFormat &)
++ {
++ return {};
++ }
++
++ QLatin1String QPlatformMediaIntegration::audioBackendName()
++ {
++ return QPlatformMediaIntegration::instance()->audioDevices()->backendName();
++ }
++
++ QPlatformMediaIntegration::QPlatformMediaIntegration(QLatin1String name) : m_backendName(name) { }
++
++ QPlatformMediaIntegration::~QPlatformMediaIntegration() = default;
++
++ QT_END_NAMESPACE
++
++ #include "moc_qplatformmediaintegration_p.cpp"