summaryrefslogtreecommitdiffstats
path: root/tests/manual
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual')
-rw-r--r--tests/manual/CMakeLists.txt22
-rw-r--r--tests/manual/audiodecoder/CMakeLists.txt45
-rw-r--r--tests/manual/audiodecoder/audiodecoder.cpp162
-rw-r--r--tests/manual/audiodecoder/audiodecoder.h55
-rw-r--r--tests/manual/audiodecoder/audiodecoder.pro13
-rw-r--r--tests/manual/audiodecoder/main.cpp83
-rw-r--r--tests/manual/devices/CMakeLists.txt40
-rw-r--r--tests/manual/devices/devices.pro12
-rw-r--r--tests/manual/devices/main.cpp136
-rw-r--r--tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt39
-rw-r--r--tests/manual/gstreamer-custom-camera-rtp/Info.plist.in46
-rw-r--r--tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp57
-rw-r--r--tests/manual/gstreamer-custom-camera/CMakeLists.txt37
-rw-r--r--tests/manual/gstreamer-custom-camera/Info.plist.in46
-rw-r--r--tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp50
-rw-r--r--tests/manual/mediaformats/CMakeLists.txt37
-rw-r--r--tests/manual/mediaformats/main.cpp87
-rw-r--r--tests/manual/minimal-player/CMakeLists.txt35
-rw-r--r--tests/manual/minimal-player/Info.plist.in46
-rw-r--r--tests/manual/minimal-player/minimal-player.cpp94
-rw-r--r--tests/manual/qml-gstreamer-rtp/CMakeLists.txt42
-rw-r--r--tests/manual/qml-gstreamer-rtp/Info.plist.in46
-rw-r--r--tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.cpp32
-rw-r--r--tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.qml29
-rw-r--r--tests/manual/qml-minimal-camera/CMakeLists.txt42
-rw-r--r--tests/manual/qml-minimal-camera/Info.plist.in46
-rw-r--r--tests/manual/qml-minimal-camera/qml-minimal-camera.cpp17
-rw-r--r--tests/manual/qml-minimal-camera/qml-minimal-camera.qml34
-rw-r--r--tests/manual/qml-minimal-player/CMakeLists.txt42
-rw-r--r--tests/manual/qml-minimal-player/Info.plist.in46
-rw-r--r--tests/manual/qml-minimal-player/qml-minimal-player.cpp17
-rw-r--r--tests/manual/qml-minimal-player/qml-minimal-player.qml42
-rw-r--r--tests/manual/wasm/CMakeLists.txt6
-rw-r--r--tests/manual/wasm/camera/CMakeLists.txt42
-rw-r--r--tests/manual/wasm/camera/camera-test.pro69
-rw-r--r--tests/manual/wasm/camera/main.cpp17
-rw-r--r--tests/manual/wasm/camera/mainwindow.cpp261
-rw-r--r--tests/manual/wasm/camera/mainwindow.h53
-rw-r--r--tests/manual/wasm/camera/mainwindow.ui107
39 files changed, 2132 insertions, 0 deletions
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
new file mode 100644
index 000000000..b37b86192
--- /dev/null
+++ b/tests/manual/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+add_subdirectory(audiodecoder)
+add_subdirectory(devices)
+add_subdirectory(mediaformats)
+add_subdirectory(minimal-player)
+add_subdirectory(wasm)
+
+if(QT_FEATURE_gstreamer)
+ add_subdirectory(gstreamer-custom-camera)
+ add_subdirectory(gstreamer-custom-camera-rtp)
+endif()
+
+if(TARGET Qt::Quick)
+ add_subdirectory(qml-minimal-camera)
+ add_subdirectory(qml-minimal-player)
+
+ if(QT_FEATURE_gstreamer)
+ add_subdirectory(qml-gstreamer-rtp)
+ endif()
+endif()
diff --git a/tests/manual/audiodecoder/CMakeLists.txt b/tests/manual/audiodecoder/CMakeLists.txt
new file mode 100644
index 000000000..758c3a913
--- /dev/null
+++ b/tests/manual/audiodecoder/CMakeLists.txt
@@ -0,0 +1,45 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(audiodecoder LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/audiodecoder")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Widgets)
+
+if(ANDROID)
+
+endif()
+
+qt_add_executable(audiodecoder
+ audiodecoder.cpp audiodecoder.h
+ main.cpp
+)
+
+set_target_properties(audiodecoder PROPERTIES
+ WIN32_EXECUTABLE FALSE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(audiodecoder PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Multimedia
+)
+
+if(ANDROID)
+ target_link_libraries(audiodecoder PUBLIC Qt::Widgets)
+endif()
+
+install(TARGETS audiodecoder
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/audiodecoder/audiodecoder.cpp b/tests/manual/audiodecoder/audiodecoder.cpp
new file mode 100644
index 000000000..a8d127ab5
--- /dev/null
+++ b/tests/manual/audiodecoder/audiodecoder.cpp
@@ -0,0 +1,162 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "audiodecoder.h"
+
+#include <QFile>
+
+#include <stdio.h>
+
+AudioDecoder::AudioDecoder(bool isPlayback, bool isDelete, const QString &targetFileName)
+ : m_cout(stdout, QIODevice::WriteOnly), m_targetFilename(targetFileName)
+{
+ m_isPlayback = isPlayback;
+ m_isDelete = isDelete;
+
+ connect(&m_decoder, &QAudioDecoder::bufferReady, this, &AudioDecoder::bufferReady);
+ connect(&m_decoder, QOverload<QAudioDecoder::Error>::of(&QAudioDecoder::error), this,
+ QOverload<QAudioDecoder::Error>::of(&AudioDecoder::error));
+ connect(&m_decoder, &QAudioDecoder::isDecodingChanged, this, &AudioDecoder::isDecodingChanged);
+ connect(&m_decoder, &QAudioDecoder::finished, this, &AudioDecoder::finished);
+ connect(&m_decoder, &QAudioDecoder::positionChanged, this, &AudioDecoder::updateProgress);
+ connect(&m_decoder, &QAudioDecoder::durationChanged, this, &AudioDecoder::updateProgress);
+
+ connect(&m_soundEffect, &QSoundEffect::statusChanged, this,
+ &AudioDecoder::playbackStatusChanged);
+ connect(&m_soundEffect, &QSoundEffect::playingChanged, this, &AudioDecoder::playingChanged);
+
+ m_progress = -1.0;
+}
+
+AudioDecoder::~AudioDecoder()
+{
+ delete m_waveDecoder;
+}
+
+void AudioDecoder::setSource(const QString &fileName)
+{
+ m_decoder.setSource(QUrl::fromLocalFile(fileName));
+}
+
+void AudioDecoder::start()
+{
+ m_decoder.start();
+}
+
+void AudioDecoder::stop()
+{
+ m_decoder.stop();
+}
+
+QAudioDecoder::Error AudioDecoder::getError()
+{
+ return m_decoder.error();
+}
+
+void AudioDecoder::setTargetFilename(const QString &fileName)
+{
+ m_targetFilename = fileName;
+}
+
+void AudioDecoder::bufferReady()
+{
+ // read a buffer from audio decoder
+ QAudioBuffer buffer = m_decoder.read();
+ if (!buffer.isValid())
+ return;
+
+ if (!m_waveDecoder) {
+ QIODevice *target = new QFile(m_targetFilename, this);
+ if (!target->open(QIODevice::WriteOnly)) {
+ qWarning() << "target file is not writable";
+ m_decoder.stop();
+ return;
+ }
+ m_waveDecoder = new QWaveDecoder(target, buffer.format());
+ }
+
+ if (!m_waveDecoder
+ || (!m_waveDecoder->isOpen() && !m_waveDecoder->open(QIODevice::WriteOnly))) {
+ m_decoder.stop();
+ return;
+ }
+
+ m_waveDecoder->write(buffer.constData<char>(), buffer.byteCount());
+}
+
+void AudioDecoder::error(QAudioDecoder::Error error)
+{
+ switch (error) {
+ case QAudioDecoder::NoError:
+ return;
+ case QAudioDecoder::ResourceError:
+ m_cout << "Resource error\n";
+ break;
+ case QAudioDecoder::FormatError:
+ m_cout << "Format error\n";
+ break;
+ case QAudioDecoder::AccessDeniedError:
+ m_cout << "Access denied error\n";
+ break;
+ case QAudioDecoder::NotSupportedError:
+ m_cout << "Service missing error\n";
+ break;
+ }
+
+ emit done();
+}
+
+void AudioDecoder::isDecodingChanged(bool isDecoding)
+{
+ if (isDecoding)
+ m_cout << "Decoding...\n";
+ else
+ m_cout << "Decoding stopped\n";
+}
+
+void AudioDecoder::finished()
+{
+ m_waveDecoder->close();
+ m_cout << "Decoding finished\n";
+
+ if (m_isPlayback) {
+ m_cout << "Starting playback\n";
+ m_soundEffect.setSource(QUrl::fromLocalFile(m_targetFilename));
+ m_soundEffect.play();
+ } else {
+ emit done();
+ }
+}
+
+void AudioDecoder::playbackStatusChanged()
+{
+ if (m_soundEffect.status() == QSoundEffect::Error) {
+ m_cout << "Playback error\n";
+ emit done();
+ }
+}
+
+void AudioDecoder::playingChanged()
+{
+ if (!m_soundEffect.isPlaying()) {
+ m_cout << "Playback finished\n";
+ if (m_isDelete)
+ QFile::remove(m_targetFilename);
+ emit done();
+ }
+}
+
+void AudioDecoder::updateProgress()
+{
+ qint64 position = m_decoder.position();
+ qint64 duration = m_decoder.duration();
+ qreal progress = m_progress;
+ if (position >= 0 && duration > 0)
+ progress = position / (qreal)duration;
+
+ if (progress > m_progress + 0.1) {
+ m_cout << "Decoding progress: " << (int)(progress * 100.0) << "%\n";
+ m_progress = progress;
+ }
+}
+
diff --git a/tests/manual/audiodecoder/audiodecoder.h b/tests/manual/audiodecoder/audiodecoder.h
new file mode 100644
index 000000000..25ce6501b
--- /dev/null
+++ b/tests/manual/audiodecoder/audiodecoder.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef AUDIODECODER_H
+#define AUDIODECODER_H
+
+#include <QAudioDecoder>
+#include <QSoundEffect>
+#include <QTextStream>
+#include <QWaveDecoder>
+
+class AudioDecoder : public QObject
+{
+ Q_OBJECT
+
+public:
+ AudioDecoder(bool isPlayback, bool isDelete, const QString &targetFileName);
+ ~AudioDecoder();
+
+ void setSource(const QString &fileName);
+ void start();
+ void stop();
+ QAudioDecoder::Error getError();
+
+ void setTargetFilename(const QString &fileName);
+
+signals:
+ void done();
+
+public slots:
+ void bufferReady();
+ void error(QAudioDecoder::Error error);
+ void isDecodingChanged(bool isDecoding);
+ void finished();
+
+ void playbackStatusChanged();
+ void playingChanged();
+
+private slots:
+ void updateProgress();
+
+private:
+ bool m_isPlayback;
+ bool m_isDelete;
+ QAudioDecoder m_decoder;
+ QTextStream m_cout;
+
+ QString m_targetFilename;
+ QWaveDecoder *m_waveDecoder = nullptr;
+ QSoundEffect m_soundEffect;
+
+ qreal m_progress;
+};
+
+#endif // AUDIODECODER_H
diff --git a/tests/manual/audiodecoder/audiodecoder.pro b/tests/manual/audiodecoder/audiodecoder.pro
new file mode 100644
index 000000000..7870d340e
--- /dev/null
+++ b/tests/manual/audiodecoder/audiodecoder.pro
@@ -0,0 +1,13 @@
+TEMPLATE = app
+TARGET = audiodecoder
+
+HEADERS = \
+ audiodecoder.h
+SOURCES = main.cpp \
+ audiodecoder.cpp
+
+QT += multimedia
+CONFIG += console
+
+target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/audiodecoder
+INSTALLS += target
diff --git a/tests/manual/audiodecoder/main.cpp b/tests/manual/audiodecoder/main.cpp
new file mode 100644
index 000000000..c02bdfabf
--- /dev/null
+++ b/tests/manual/audiodecoder/main.cpp
@@ -0,0 +1,83 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "audiodecoder.h"
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QFileInfo>
+#include <QTextStream>
+#ifdef Q_OS_ANDROID
+# include <QApplication>
+# include <QFileDialog>
+# include <QMessageBox>
+# include <QStandardPaths>
+#endif
+
+#include <stdio.h>
+
+int main(int argc, char *argv[])
+{
+#ifdef Q_OS_ANDROID
+ QApplication app(argc, argv);
+#else
+ QCoreApplication app(argc, argv);
+#endif
+
+ QFileInfo sourceFile;
+ QFileInfo targetFile;
+ bool isPlayback = false;
+ bool isDelete = false;
+
+#ifndef Q_OS_ANDROID
+ QTextStream cout(stdout, QIODevice::WriteOnly);
+ if (app.arguments().size() < 2) {
+ cout << "Usage: audiodecoder [-p] [-pd] SOURCEFILE [TARGETFILE]\n";
+ cout << "Set -p option if you want to play output file.\n";
+ cout << "Set -pd option if you want to play output file and delete it after successful "
+ "playback.\n";
+ cout << "Default TARGETFILE name is \"out.wav\" in the same directory as the source "
+ "file.\n";
+ return 0;
+ }
+
+ if (app.arguments().at(1) == "-p")
+ isPlayback = true;
+ else if (app.arguments().at(1) == "-pd") {
+ isPlayback = true;
+ isDelete = true;
+ }
+
+ int sourceFileIndex = (isPlayback || isDelete) ? 2 : 1;
+ if (app.arguments().size() <= sourceFileIndex) {
+ cout << "Error: source filename is not specified.\n";
+ return 0;
+ }
+
+ sourceFile.setFile(app.arguments().at(sourceFileIndex));
+ if (app.arguments().size() > sourceFileIndex + 1)
+ targetFile.setFile(app.arguments().at(sourceFileIndex + 1));
+ else
+ targetFile.setFile(sourceFile.dir().absoluteFilePath("out.wav"));
+
+#else
+
+ const QString message = "You will be prompted to select an audio file (e.g. mp3 or ogg format) "
+ "which will be decoded and played back to you.";
+ QMessageBox messageBox(QMessageBox::Information, "Audio Decoder", message, QMessageBox::Ok);
+ messageBox.exec();
+ sourceFile =
+ QFileInfo(QFileDialog::getOpenFileName(messageBox.parentWidget(), "Select Audio File"));
+ auto musicPath = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
+ targetFile = QFileInfo(musicPath.append("/out.wav"));
+ isPlayback = true;
+#endif
+ AudioDecoder decoder(isPlayback, isDelete, targetFile.absoluteFilePath());
+ QObject::connect(&decoder, &AudioDecoder::done, &app, &QCoreApplication::quit);
+ decoder.setSource(sourceFile.absoluteFilePath());
+ decoder.start();
+ if (decoder.getError() != QAudioDecoder::NoError)
+ return 0;
+
+ return app.exec();
+}
diff --git a/tests/manual/devices/CMakeLists.txt b/tests/manual/devices/CMakeLists.txt
new file mode 100644
index 000000000..cae0c577f
--- /dev/null
+++ b/tests/manual/devices/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(devices LANGUAGES CXX)
+
+if(ANDROID OR IOS)
+ message(FATAL_ERROR "This is a commandline tool that is not supported on mobile platforms")
+endif()
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/devices")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia)
+
+qt_add_executable(devices
+ main.cpp
+)
+
+set_target_properties(devices PROPERTIES
+ WIN32_EXECUTABLE FALSE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(devices PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Multimedia
+)
+
+install(TARGETS devices
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/devices/devices.pro b/tests/manual/devices/devices.pro
new file mode 100644
index 000000000..bbc5ecea8
--- /dev/null
+++ b/tests/manual/devices/devices.pro
@@ -0,0 +1,12 @@
+android|ios: error("This is a commandline tool that is not supported on mobile platforms")
+
+TEMPLATE = app
+TARGET = devices
+
+SOURCES = main.cpp
+
+QT += multimedia
+CONFIG += console
+
+target.path = $$[QT_INSTALL_EXAMPLES]/multimedia/devices
+INSTALLS += target
diff --git a/tests/manual/devices/main.cpp b/tests/manual/devices/main.cpp
new file mode 100644
index 000000000..67b277812
--- /dev/null
+++ b/tests/manual/devices/main.cpp
@@ -0,0 +1,136 @@
+// Copyright (C) 2020 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QAudioDevice>
+#include <QAudioFormat>
+#include <QCameraDevice>
+#include <QCoreApplication>
+#include <QMediaDevices>
+#include <QString>
+#include <QTextStream>
+
+#include <stdio.h>
+
+static QString formatToString(QAudioFormat::SampleFormat sampleFormat)
+{
+ switch (sampleFormat) {
+ case QAudioFormat::UInt8:
+ return "UInt8";
+ case QAudioFormat::Int16:
+ return "Int16";
+ case QAudioFormat::Int32:
+ return "Int32";
+ case QAudioFormat::Float:
+ return "Float";
+ default:
+ return "Unknown";
+ }
+}
+
+static QString positionToString(QCameraDevice::Position position)
+{
+ switch (position) {
+ case QCameraDevice::BackFace:
+ return "BackFace";
+ case QCameraDevice::FrontFace:
+ return "FrontFace";
+ default:
+ return "Unspecified";
+ }
+}
+
+static void printAudioDeviceInfo(QTextStream &out, const QAudioDevice &deviceInfo)
+{
+ const auto isDefault = deviceInfo.isDefault() ? "Yes" : "No";
+ const auto preferredFormat = deviceInfo.preferredFormat();
+ const auto supportedFormats = deviceInfo.supportedSampleFormats();
+ out.setFieldWidth(30);
+ out.setFieldAlignment(QTextStream::AlignLeft);
+ out << "Name: " << deviceInfo.description() << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Id: " << QString::fromLatin1(deviceInfo.id()) << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Default: " << isDefault << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Preferred Format: " << formatToString(preferredFormat.sampleFormat())
+ << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Preferred Rate: " << preferredFormat.sampleRate() << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Preferred Channels: " << preferredFormat.channelCount() << qSetFieldWidth(0)
+ << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Supported Formats: ";
+ for (auto &format : supportedFormats)
+ out << qSetFieldWidth(0) << formatToString(format) << " ";
+ out << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Supported Rates: " << qSetFieldWidth(0) << deviceInfo.minimumSampleRate() << " - "
+ << deviceInfo.maximumSampleRate() << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Supported Channels: " << qSetFieldWidth(0) << deviceInfo.minimumChannelCount() << " - "
+ << deviceInfo.maximumChannelCount() << Qt::endl;
+
+ out << Qt::endl;
+}
+
+static void printVideoDeviceInfo(QTextStream &out, const QCameraDevice &cameraDevice)
+{
+ const auto isDefault = cameraDevice.isDefault() ? "Yes" : "No";
+ const auto position = cameraDevice.position();
+ const auto photoResolutions = cameraDevice.photoResolutions();
+ const auto videoFormats = cameraDevice.videoFormats();
+
+ out.setFieldWidth(30);
+ out.setFieldAlignment(QTextStream::AlignLeft);
+ out << "Name: " << cameraDevice.description() << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Id: " << QString::fromLatin1(cameraDevice.id()) << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Default: " << isDefault << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Position: " << positionToString(position) << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Photo Resolutions: ";
+ for (auto &resolution : photoResolutions) {
+ QString s = QStringLiteral("%1x%2").arg(resolution.width()).arg(resolution.height());
+ out << qSetFieldWidth(0) << s << ", ";
+ }
+ out.setFieldWidth(10);
+ out << Qt::endl << Qt::endl;
+ out << "Supported Video Formats: " << qSetFieldWidth(0) << Qt::endl;
+ for (auto &format : videoFormats) {
+ out.setFieldWidth(30);
+ QString s =
+ QStringLiteral("%1x%2").arg(format.resolution().width()).arg(format.resolution().height());
+ out << "Resolution: " << s << qSetFieldWidth(0) << Qt::endl;
+ out.setFieldWidth(30);
+ out << "Frame Rate: " << qSetFieldWidth(0) << "Min:" << format.minFrameRate()
+ << " Max:" << format.maxFrameRate() << Qt::endl;
+ }
+
+ out << Qt::endl;
+}
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv); // QtMultimedia needs an application singleton
+
+ QTextStream out(stdout);
+
+ const auto audioInputDevices = QMediaDevices::audioInputs();
+ const auto audioOutputDevices = QMediaDevices::audioOutputs();
+ const auto videoInputDevices = QMediaDevices::videoInputs();
+
+ out << "Audio devices detected: " << Qt::endl;
+ out << Qt::endl << "Input" << Qt::endl;
+ for (auto &deviceInfo : audioInputDevices)
+ printAudioDeviceInfo(out, deviceInfo);
+ out << Qt::endl << "Output" << Qt::endl;
+ for (auto &deviceInfo : audioOutputDevices)
+ printAudioDeviceInfo(out, deviceInfo);
+
+ out << Qt::endl << "Video devices detected: " << Qt::endl;
+ for (auto &cameraDevice : videoInputDevices)
+ printVideoDeviceInfo(out, cameraDevice);
+}
diff --git a/tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt b/tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt
new file mode 100644
index 000000000..58608db5c
--- /dev/null
+++ b/tests/manual/gstreamer-custom-camera-rtp/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(gstreamer-custom-camera-rtp LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/gstreamer-custom-camera-rtp")
+
+find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets QGstreamerMediaPluginPrivate)
+
+qt_add_executable( gstreamer-custom-camera-rtp WIN32 MACOSX_BUNDLE
+ gstreamer-custom-camera-rtp.cpp
+ Info.plist.in
+)
+
+target_link_libraries( gstreamer-custom-camera-rtp PUBLIC
+ Qt::Widgets
+ Qt::Multimedia
+ Qt::MultimediaPrivate
+ Qt::MultimediaWidgets
+
+ Qt::QGstreamerMediaPluginPrivate
+)
+
+install(TARGETS gstreamer-custom-camera-rtp
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+set_target_properties( gstreamer-custom-camera-rtp PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+)
diff --git a/tests/manual/gstreamer-custom-camera-rtp/Info.plist.in b/tests/manual/gstreamer-custom-camera-rtp/Info.plist.in
new file mode 100644
index 000000000..46a9ecf2d
--- /dev/null
+++ b/tests/manual/gstreamer-custom-camera-rtp/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp b/tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp
new file mode 100644
index 000000000..b61df7ce9
--- /dev/null
+++ b/tests/manual/gstreamer-custom-camera-rtp/gstreamer-custom-camera-rtp.cpp
@@ -0,0 +1,57 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtMultimedia/QAudioOutput>
+#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h>
+#include <QtMultimediaWidgets/QtMultimediaWidgets>
+#include <QtWidgets/QApplication>
+#include <QtCore/QCommandLineParser>
+
+#include <QtQGstreamerMediaPlugin/private/qgst_p.h>
+#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h>
+
+using namespace std::chrono_literals;
+using namespace Qt::Literals;
+
+struct GStreamerRtpStreamSender
+{
+ GStreamerRtpStreamSender()
+ {
+ element = QGstElement::createFromPipelineDescription(
+ "videotestsrc ! jpegenc ! rtpjpegpay ! udpsink host=127.0.0.1 port=50004"_ba);
+
+ pipeline.add(element);
+ pipeline.setStateSync(GstState::GST_STATE_PLAYING);
+ pipeline.dumpGraph("sender");
+ }
+
+ ~GStreamerRtpStreamSender() { pipeline.setStateSync(GstState::GST_STATE_NULL); }
+
+ QGstPipeline pipeline = QGstPipeline::create("UdpSend");
+ QGstElement element;
+};
+
+int main(int argc, char **argv)
+{
+ qputenv("QT_MEDIA_BACKEND", "gstreamer");
+
+ gst_init(&argc, &argv);
+ GStreamerRtpStreamSender sender;
+
+ QApplication app(argc, argv);
+
+ QByteArray pipelineString =
+ R"(udpsrc port=50004 ! application/x-rtp,encoding=JPEG,payload=26 ! rtpjpegdepay ! jpegdec ! videoconvert)"_ba;
+ QVideoWidget wid;
+ wid.show();
+
+ QMediaCaptureSession session;
+ session.setVideoSink(wid.videoSink());
+
+ QCamera *cam = QGStreamerPlatformSpecificInterface::instance()->makeCustomGStreamerCamera(
+ pipelineString, &session);
+ session.setCamera(cam);
+ cam->start();
+
+ return QApplication::exec();
+}
diff --git a/tests/manual/gstreamer-custom-camera/CMakeLists.txt b/tests/manual/gstreamer-custom-camera/CMakeLists.txt
new file mode 100644
index 000000000..f161a0630
--- /dev/null
+++ b/tests/manual/gstreamer-custom-camera/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(gstreamer-custom-camera-rtp LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/gstreamer-custom-camera")
+
+find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets)
+
+qt_add_executable( gstreamer-custom-camera WIN32 MACOSX_BUNDLE
+ gstreamer-custom-camera.cpp
+ Info.plist.in
+)
+
+target_link_libraries( gstreamer-custom-camera PUBLIC
+ Qt::Widgets
+ Qt::Multimedia
+ Qt::MultimediaPrivate
+ Qt::MultimediaWidgets
+)
+
+install(TARGETS gstreamer-custom-camera
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+set_target_properties( gstreamer-custom-camera PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+)
diff --git a/tests/manual/gstreamer-custom-camera/Info.plist.in b/tests/manual/gstreamer-custom-camera/Info.plist.in
new file mode 100644
index 000000000..46a9ecf2d
--- /dev/null
+++ b/tests/manual/gstreamer-custom-camera/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp b/tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp
new file mode 100644
index 000000000..dbd729d15
--- /dev/null
+++ b/tests/manual/gstreamer-custom-camera/gstreamer-custom-camera.cpp
@@ -0,0 +1,50 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtMultimedia/QAudioOutput>
+#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h>
+#include <QtMultimediaWidgets/QtMultimediaWidgets>
+#include <QtWidgets/QApplication>
+#include <QtCore/QCommandLineParser>
+
+using namespace std::chrono_literals;
+using namespace Qt::Literals;
+
+int main(int argc, char **argv)
+{
+ qputenv("QT_MEDIA_BACKEND", "gstreamer");
+
+ QApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription("GStreamer Custom Camera");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument(
+ "pipeline", "Pipeline string, e.g. `videotestsrc pattern=smpte-rp-219 is-live=true`");
+
+ parser.process(app);
+
+ QByteArray pipelineString;
+
+ if (parser.positionalArguments().isEmpty()) {
+ // pipelineString = "videotestsrc pattern=smpte-rp-219 is-live=true";
+ pipelineString = "videotestsrc is-live=true ! gamma gamma=2.0";
+ } else {
+ pipelineString = parser.positionalArguments()[0].toLatin1();
+ }
+
+ QVideoWidget wid;
+
+ QMediaCaptureSession session;
+ session.setVideoSink(wid.videoSink());
+
+ QCamera *cam = QGStreamerPlatformSpecificInterface::instance()->makeCustomGStreamerCamera(
+ pipelineString, &session);
+ session.setCamera(cam);
+ cam->start();
+
+ wid.show();
+
+ return QApplication::exec();
+}
diff --git a/tests/manual/mediaformats/CMakeLists.txt b/tests/manual/mediaformats/CMakeLists.txt
new file mode 100644
index 000000000..ed58e628e
--- /dev/null
+++ b/tests/manual/mediaformats/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(devices LANGUAGES CXX)
+
+if(ANDROID OR IOS)
+ message(FATAL_ERROR "This is a commandline tool that is not supported on mobile platforms")
+endif()
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/mediaformats")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Multimedia)
+
+qt_add_executable(mediaformats
+ main.cpp
+)
+
+set_target_properties(mediaformats PROPERTIES
+ WIN32_EXECUTABLE FALSE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(mediaformats PUBLIC
+ Qt::Core
+ Qt::Multimedia
+)
+
+install(TARGETS mediaformats
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/mediaformats/main.cpp b/tests/manual/mediaformats/main.cpp
new file mode 100644
index 000000000..57d0ad831
--- /dev/null
+++ b/tests/manual/mediaformats/main.cpp
@@ -0,0 +1,87 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QMimeType>
+#include <QtCore/QTextStream>
+#include <QtMultimedia/QMediaFormat>
+
+#include <stdio.h>
+
+namespace {
+
+void printFileFormatEntry(QMediaFormat::FileFormat format, QTextStream &out)
+{
+ out << " " << QMediaFormat::fileFormatName(format) << " - "
+ << QMediaFormat::fileFormatDescription(format);
+
+ QMimeType mimeType = QMediaFormat(format).mimeType();
+ if (mimeType.isValid()) {
+ out << " (" << mimeType.name() << ")\n";
+ out << " " << mimeType.suffixes().join(", ") << "\n";
+ }
+}
+
+void printCodecEntry(QMediaFormat::AudioCodec codec, QTextStream &out)
+{
+ out << " " << QMediaFormat::audioCodecName(codec) << " - "
+ << QMediaFormat::audioCodecDescription(codec) << "\n";
+}
+
+void printCodecEntry(QMediaFormat::VideoCodec codec, QTextStream &out)
+{
+ out << " " << QMediaFormat::videoCodecName(codec) << " - "
+ << QMediaFormat::videoCodecDescription(codec) << "\n";
+}
+
+void printFileFormats(QTextStream &out)
+{
+ out << "Supported file formats for decoding: \n";
+ for (QMediaFormat::FileFormat format :
+ QMediaFormat().supportedFileFormats(QMediaFormat::Decode))
+ printFileFormatEntry(format, out);
+
+ out << "\nSupported file formats for encoding: \n";
+ for (QMediaFormat::FileFormat format :
+ QMediaFormat().supportedFileFormats(QMediaFormat::Encode))
+ printFileFormatEntry(format, out);
+}
+
+void printAudioCodecs(QTextStream &out)
+{
+ out << "Supported audio codecs for decoding: \n";
+ for (QMediaFormat::AudioCodec codec : QMediaFormat().supportedAudioCodecs(QMediaFormat::Decode))
+ printCodecEntry(codec, out);
+
+ out << "\nSupported audio codecs for encoding: \n";
+ for (QMediaFormat::AudioCodec codec : QMediaFormat().supportedAudioCodecs(QMediaFormat::Encode))
+ printCodecEntry(codec, out);
+}
+
+void printVideoCodecs(QTextStream &out)
+{
+ out << "Supported video codecs for decoding: \n";
+ for (QMediaFormat::VideoCodec codec : QMediaFormat().supportedVideoCodecs(QMediaFormat::Decode))
+ printCodecEntry(codec, out);
+
+ out << "\nSupported video codecs for encoding: \n";
+ for (QMediaFormat::VideoCodec codec : QMediaFormat().supportedVideoCodecs(QMediaFormat::Encode))
+ printCodecEntry(codec, out);
+}
+
+} // namespace
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv); // QtMultimedia needs an application singleton
+
+ QTextStream out(stdout);
+
+ printFileFormats(out);
+ out << "\n";
+ printAudioCodecs(out);
+ out << "\n";
+ printVideoCodecs(out);
+
+ return 0;
+}
diff --git a/tests/manual/minimal-player/CMakeLists.txt b/tests/manual/minimal-player/CMakeLists.txt
new file mode 100644
index 000000000..6a5f5dfb0
--- /dev/null
+++ b/tests/manual/minimal-player/CMakeLists.txt
@@ -0,0 +1,35 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(sidepanel LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/minimal-player")
+
+find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia MultimediaWidgets)
+
+qt_add_executable( minimal-player WIN32 MACOSX_BUNDLE
+ minimal-player.cpp
+)
+
+target_link_libraries( minimal-player PUBLIC
+ Qt::Widgets
+ Qt::Multimedia
+ Qt::MultimediaWidgets
+)
+
+install(TARGETS minimal-player
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+set_target_properties( minimal-player PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+)
diff --git a/tests/manual/minimal-player/Info.plist.in b/tests/manual/minimal-player/Info.plist.in
new file mode 100644
index 000000000..46a9ecf2d
--- /dev/null
+++ b/tests/manual/minimal-player/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/minimal-player/minimal-player.cpp b/tests/manual/minimal-player/minimal-player.cpp
new file mode 100644
index 000000000..17a11b050
--- /dev/null
+++ b/tests/manual/minimal-player/minimal-player.cpp
@@ -0,0 +1,94 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QTimer>
+#include <QtCore/QCommandLineParser>
+#include <QtMultimedia/QAudioOutput>
+#include <QtMultimedia/QMediaPlayer>
+#include <QtMultimediaWidgets/QVideoWidget>
+#include <QtWidgets/QApplication>
+#include <QtWidgets/QWidget>
+
+using namespace std::chrono_literals;
+using namespace Qt::Literals;
+
+int mainToggleWidgets(QString filename)
+{
+ QMediaPlayer player;
+ QVideoWidget widget1;
+ QVideoWidget widget2;
+ QAudioOutput audioOutput;
+ player.setVideoOutput(&widget1);
+ player.setAudioOutput(&audioOutput);
+ player.setSource(filename);
+
+ QTimer toggleOutput;
+ bool toggled = {};
+
+ toggleOutput.callOnTimeout([&] {
+ toggled = !toggled;
+ if (toggled)
+ player.setVideoOutput(&widget2);
+ else
+ player.setVideoOutput(&widget1);
+ });
+
+ toggleOutput.setInterval(1s);
+ toggleOutput.start();
+
+ widget1.show();
+ widget2.show();
+ player.play();
+ return QApplication::exec();
+}
+
+int mainSimple(QString filename, bool loop)
+{
+ QMediaPlayer player;
+ QVideoWidget widget1;
+ QAudioOutput audioOutput;
+ player.setVideoOutput(&widget1);
+ player.setAudioOutput(&audioOutput);
+ player.setSource(filename);
+
+ widget1.show();
+
+ if (loop)
+ player.setLoops(QMediaPlayer::Infinite);
+
+ player.play();
+ return QApplication::exec();
+}
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription("Minimal Player");
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument("media", "File to play");
+
+ QCommandLineOption toggleWidgetsOption{ "toggle-widgets", "Toggle between widgets." };
+ parser.addOption(toggleWidgetsOption);
+
+ QCommandLineOption loopOption{ "loop", "Loop." };
+ parser.addOption(loopOption);
+
+ parser.process(app);
+
+ if (parser.positionalArguments().isEmpty()) {
+ qInfo() << "Please specify a video source";
+ return 0;
+ }
+
+ QString filename = parser.positionalArguments()[0];
+
+ if (parser.isSet(toggleWidgetsOption))
+ return mainToggleWidgets(filename);
+
+ bool loop = parser.isSet(loopOption);
+
+ return mainSimple(filename, loop);
+}
diff --git a/tests/manual/qml-gstreamer-rtp/CMakeLists.txt b/tests/manual/qml-gstreamer-rtp/CMakeLists.txt
new file mode 100644
index 000000000..29ea2a46a
--- /dev/null
+++ b/tests/manual/qml-gstreamer-rtp/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(sidepanel LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/qml-gstreamer-rtp")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Qml Quick)
+
+qt_add_executable( qml-gstreamer-rtp WIN32 MACOSX_BUNDLE
+ qml-gstreamer-rtp.cpp
+)
+
+qt_add_qml_module( qml-gstreamer-rtp
+ URI QmlMinimalplayer
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ "qml-gstreamer-rtp.qml"
+)
+
+target_link_libraries( qml-gstreamer-rtp PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Quick
+)
+
+install(TARGETS qml-gstreamer-rtp
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+set_target_properties( qml-gstreamer-rtp PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+)
diff --git a/tests/manual/qml-gstreamer-rtp/Info.plist.in b/tests/manual/qml-gstreamer-rtp/Info.plist.in
new file mode 100644
index 000000000..46a9ecf2d
--- /dev/null
+++ b/tests/manual/qml-gstreamer-rtp/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.cpp b/tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.cpp
new file mode 100644
index 000000000..44e8f1659
--- /dev/null
+++ b/tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QGuiApplication>
+#include <QProcess>
+#include <QQmlApplicationEngine>
+#include <QtEnvironmentVariables>
+
+int main(int argc, char *argv[])
+{
+ qputenv("QT_MEDIA_BACKEND", "gstreamer");
+
+ QProcess process;
+ process.startCommand(
+ "gst-launch-1.0 videotestsrc ! x264enc ! udpsink host=127.0.0.1 port=50004");
+
+ auto scopeGuard = qScopeGuard([&] {
+ process.terminate();
+ process.waitForFinished();
+ });
+
+ QGuiApplication app(argc, argv);
+ QQmlApplicationEngine engine;
+
+ process.waitForStarted();
+
+ engine.load(QUrl("qrc:/qml-gstreamer-rtp.qml"));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.qml b/tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.qml
new file mode 100644
index 000000000..73ec976fb
--- /dev/null
+++ b/tests/manual/qml-gstreamer-rtp/qml-gstreamer-rtp.qml
@@ -0,0 +1,29 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+import QtQuick
+import QtQuick.Controls
+import QtMultimedia
+
+ApplicationWindow {
+ id: window
+ width: 800
+ height: 600
+ visible: true
+ title: qsTr("GStreamer RTP receiver")
+
+ MediaPlayer {
+ id: player
+ videoOutput: output
+
+ source: "udp://127.0.0.1:50004"
+ }
+
+ Component.onCompleted: {
+ player.play()
+ }
+
+ VideoOutput {
+ id: output
+ anchors.fill: parent
+ }
+}
diff --git a/tests/manual/qml-minimal-camera/CMakeLists.txt b/tests/manual/qml-minimal-camera/CMakeLists.txt
new file mode 100644
index 000000000..fcd4676b3
--- /dev/null
+++ b/tests/manual/qml-minimal-camera/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(sidepanel LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/qml-minimal-camera")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Qml Quick)
+
+qt_add_executable( qml-minimal-camera WIN32 MACOSX_BUNDLE
+ qml-minimal-camera.cpp
+)
+
+qt_add_qml_module( qml-minimal-camera
+ URI QmlMinimalCamera
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ "qml-minimal-camera.qml"
+)
+
+target_link_libraries( qml-minimal-camera PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Quick
+)
+
+install(TARGETS qml-minimal-camera
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+set_target_properties( qml-minimal-camera PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+)
diff --git a/tests/manual/qml-minimal-camera/Info.plist.in b/tests/manual/qml-minimal-camera/Info.plist.in
new file mode 100644
index 000000000..46a9ecf2d
--- /dev/null
+++ b/tests/manual/qml-minimal-camera/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/qml-minimal-camera/qml-minimal-camera.cpp b/tests/manual/qml-minimal-camera/qml-minimal-camera.cpp
new file mode 100644
index 000000000..c61b92992
--- /dev/null
+++ b/tests/manual/qml-minimal-camera/qml-minimal-camera.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl("qrc:/qml-minimal-camera.qml"));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/tests/manual/qml-minimal-camera/qml-minimal-camera.qml b/tests/manual/qml-minimal-camera/qml-minimal-camera.qml
new file mode 100644
index 000000000..3965c663e
--- /dev/null
+++ b/tests/manual/qml-minimal-camera/qml-minimal-camera.qml
@@ -0,0 +1,34 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtQuick.Controls
+import QtMultimedia
+
+ApplicationWindow {
+ id: window
+ width: 800
+ height: 600
+ visible: true
+ title: qsTr("QmlMinimalCamera")
+
+ MediaDevices {
+ id: mediaDevices
+ }
+
+ CaptureSession {
+ id: captureSession
+ videoOutput: output
+ camera: Camera {
+ id: camera
+ cameraDevice: mediaDevices.defaultVideoInput
+ }
+
+ Component.onCompleted: camera.start()
+ }
+
+ VideoOutput {
+ id: output
+ visible: true
+ }
+}
diff --git a/tests/manual/qml-minimal-player/CMakeLists.txt b/tests/manual/qml-minimal-player/CMakeLists.txt
new file mode 100644
index 000000000..be8f61861
--- /dev/null
+++ b/tests/manual/qml-minimal-player/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(sidepanel LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/multimedia/qml-minimal-player")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Multimedia Qml Quick)
+
+qt_add_executable( qml-minimal-player WIN32 MACOSX_BUNDLE
+ qml-minimal-player.cpp
+)
+
+qt_add_qml_module( qml-minimal-player
+ URI QmlMinimalplayer
+ NO_RESOURCE_TARGET_PATH
+ QML_FILES
+ "qml-minimal-player.qml"
+)
+
+target_link_libraries( qml-minimal-player PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Quick
+)
+
+install(TARGETS qml-minimal-player
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
+
+set_target_properties( qml-minimal-player PROPERTIES
+ MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
+)
diff --git a/tests/manual/qml-minimal-player/Info.plist.in b/tests/manual/qml-minimal-player/Info.plist.in
new file mode 100644
index 000000000..46a9ecf2d
--- /dev/null
+++ b/tests/manual/qml-minimal-player/Info.plist.in
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+
+ <key>CFBundleName</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
+ <key>CFBundleIdentifier</key>
+ <string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
+ <key>CFBundleExecutable</key>
+ <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
+
+ <key>CFBundleVersion</key>
+ <string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
+ <key>CFBundleShortVersionString</key>
+ <string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
+ <key>CFBundleLongVersionString</key>
+ <string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
+
+ <key>LSMinimumSystemVersion</key>
+ <string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
+
+ <key>CFBundleGetInfoString</key>
+ <string>${MACOSX_BUNDLE_INFO_STRING}</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>${MACOSX_BUNDLE_COPYRIGHT}</string>
+
+ <key>CFBundleIconFile</key>
+ <string>${MACOSX_BUNDLE_ICON_FILE}</string>
+
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+
+ <key>NSCameraUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Example</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/manual/qml-minimal-player/qml-minimal-player.cpp b/tests/manual/qml-minimal-player/qml-minimal-player.cpp
new file mode 100644
index 000000000..f7714f016
--- /dev/null
+++ b/tests/manual/qml-minimal-player/qml-minimal-player.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QGuiApplication>
+#include <QQmlApplicationEngine>
+
+int main(int argc, char *argv[])
+{
+ QGuiApplication app(argc, argv);
+
+ QQmlApplicationEngine engine;
+ engine.load(QUrl("qrc:/qml-minimal-player.qml"));
+ if (engine.rootObjects().isEmpty())
+ return -1;
+
+ return app.exec();
+}
diff --git a/tests/manual/qml-minimal-player/qml-minimal-player.qml b/tests/manual/qml-minimal-player/qml-minimal-player.qml
new file mode 100644
index 000000000..4767d7e7a
--- /dev/null
+++ b/tests/manual/qml-minimal-player/qml-minimal-player.qml
@@ -0,0 +1,42 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+import QtQuick
+import QtQuick.Controls
+import QtMultimedia
+
+ApplicationWindow {
+ id: window
+ width: 800
+ height: 600
+ visible: true
+ title: qsTr("QmlMinimalPlayer")
+
+ MediaPlayer {
+ id: player
+ audioOutput: AudioOutput {
+ onMutedChanged: {
+ console.log("muted", player.audioOutput.muted)
+ }
+ }
+ videoOutput: output
+
+ onMediaStatusChanged: {
+ console.log("status", player.mediaStatus);
+
+ if (player.mediaStatus === MediaPlayer.LoadedMedia)
+ player.play()
+ }
+ }
+
+ Component.onCompleted: {
+ if (Qt.application.arguments.length > 1)
+ player.setSource(Qt.application.arguments[1])
+ else
+ console.log("Please specify a video source")
+ }
+
+ VideoOutput {
+ id: output
+ visible: true
+ }
+}
diff --git a/tests/manual/wasm/CMakeLists.txt b/tests/manual/wasm/CMakeLists.txt
new file mode 100644
index 000000000..1d3c623c4
--- /dev/null
+++ b/tests/manual/wasm/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(QT_FEATURE_widgets)
+ add_subdirectory(camera)
+endif()
diff --git a/tests/manual/wasm/camera/CMakeLists.txt b/tests/manual/wasm/camera/CMakeLists.txt
new file mode 100644
index 000000000..f63c60bbf
--- /dev/null
+++ b/tests/manual/wasm/camera/CMakeLists.txt
@@ -0,0 +1,42 @@
+# Generated from camera-test.pro.
+
+cmake_minimum_required(VERSION 3.16)
+project(camera-test VERSION 1.0 LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+set(CMAKE_BUILD_TYPE Release)
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(QT NAMES Qt5 Qt6 REQUIRED COMPONENTS Core)
+find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui Multimedia MultimediaWidgets)
+find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS Widgets)
+
+qt_add_executable(camera-test WIN32 MACOSX_BUNDLE
+ main.cpp
+ mainwindow.cpp mainwindow.h mainwindow.ui
+)
+target_link_libraries(camera-test PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Multimedia
+ Qt::MultimediaWidgets
+ Qt::CorePrivate
+ Qt::Widgets
+)
+
+set(CMAKE_BUILD_TYPE Debug)
+# uncomment to enable asyncify
+# target_link_options(wasm-camera PUBLIC -sASYNCIFY -Os)
+
+install(TARGETS camera-test
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/tests/manual/wasm/camera/camera-test.pro b/tests/manual/wasm/camera/camera-test.pro
new file mode 100644
index 000000000..f13ba3596
--- /dev/null
+++ b/tests/manual/wasm/camera/camera-test.pro
@@ -0,0 +1,69 @@
+QT += core gui multimedia multimediawidgets
+
+greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
+
+CONFIG += c++17 debug
+
+# uncomment this to use asyncify
+#wasm: QMAKE_LFLAGS += -s ASYNCIFY -Os
+
+# You can make your code fail to compile if it uses deprecated APIs.
+# In order to do so, uncomment the following line.
+#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+
+SOURCES += \
+ main.cpp \
+ mainwindow.cpp
+
+HEADERS += \
+ mainwindow.h
+
+FORMS += \
+ mainwindow.ui
+
+# Default rules for deployment.
+qnx: target.path = /tmp/$${TARGET}/bin
+else: unix:!android: target.path = /opt/$${TARGET}/bin
+!isEmpty(target.path): INSTALLS += target
+
+macos {
+ PRODUCT_NAME = $$TARGET
+ macx-xcode: PRODUCT_NAME = $${LITERAL_DOLLAR}{PRODUCT_NAME}
+ INFOPLIST = \
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" \
+ "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" \
+ "<plist version=\"1.0\">" \
+ "<dict>" \
+ " <key>CFBundleIconFile</key>" \
+ " <string></string>" \
+ " <key>CFBundlePackageType</key>" \
+ " <string>APPL</string>" \
+ " <key>CFBundleGetInfoString</key>" \
+ " <string>Created by Qt/QMake</string>" \
+ " <key>CFBundleSignature</key>" \
+ " <string>????</string>" \
+ " <key>CFBundleExecutable</key>" \
+ " <string>$$TARGET</string>" \
+ " <key>CFBundleIdentifier</key>" \
+ " <string>com.digia.$${LITERAL_DOLLAR}{PRODUCT_NAME:rfc1034identifier}</string>" \
+ " <key>CFBundleDisplayName</key>" \
+ " <string>$$PRODUCT_NAME</string>" \
+ " <key>CFBundleName</key>" \
+ " <string>$$PRODUCT_NAME</string>" \
+ " <key>CFBundleShortVersionString</key>" \
+ " <string>1.0</string>" \
+ " <key>CFBundleVersion</key>" \
+ " <string>1.0</string>" \
+ " <key>NSPrincipalClass</key>" \
+ " <string>NSApplication</string>" \
+ " <key>NSCameraUsageDescription</key>" \
+ " <string>Qt Multimedia Example</string>" \
+ " <key>NSMicrophoneUsageDescription</key>" \
+ " <string>Qt Multimedia Example</string>" \
+ " <key>NOTE</key>" \
+ " <string>This file was generated by Qt/QMake.</string>" \
+ "</dict>" \
+ "</plist>"
+ write_file($$OUT_PWD/Info.plist, INFOPLIST)|error()
+ QMAKE_INFO_PLIST = $$OUT_PWD/Info.plist
+}
diff --git a/tests/manual/wasm/camera/main.cpp b/tests/manual/wasm/camera/main.cpp
new file mode 100644
index 000000000..8370ffb87
--- /dev/null
+++ b/tests/manual/wasm/camera/main.cpp
@@ -0,0 +1,17 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "mainwindow.h"
+
+#include <QApplication>
+#include <QLoggingCategory>
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+ QLoggingCategory::setFilterRules("*.debug=false\n"
+ "qt.multimedia.wasm.*=true");
+ MainWindow w;
+ w.show();
+ return a.exec();
+}
diff --git a/tests/manual/wasm/camera/mainwindow.cpp b/tests/manual/wasm/camera/mainwindow.cpp
new file mode 100644
index 000000000..78c6a7584
--- /dev/null
+++ b/tests/manual/wasm/camera/mainwindow.cpp
@@ -0,0 +1,261 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+#include <QAudioInput>
+#include <QCamera>
+#include <QCameraDevice>
+#include <QGraphicsScene>
+#include <QGraphicsVideoItem>
+#include <QImageCapture>
+#include <QMediaCaptureSession>
+#include <QMediaDevices>
+#include <QMediaFormat>
+#include <QApplication>
+#include <QTimer>
+#include <QVideoWidget>
+#include <QLabel>
+#include <QFileDialog>
+#include <QScreen>
+#include <QMediaPlayer>
+
+#if QT_CONFIG(permissions)
+ #include <QPermission>
+#endif
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent),
+ ui(new Ui::MainWindow),
+ m_recorder(nullptr)
+{
+ ui->setupUi(this);
+ init();
+}
+
+MainWindow::~MainWindow()
+{
+ if (m_recorder)
+ delete m_recorder;
+ delete ui;
+}
+
+void MainWindow::init()
+{
+#if QT_CONFIG(permissions)
+ // camera
+ QCameraPermission cameraPermission;
+ switch (qApp->checkPermission(cameraPermission)) {
+ case Qt::PermissionStatus::Undetermined:
+ qApp->requestPermission(cameraPermission, this, &MainWindow::init);
+ return;
+ case Qt::PermissionStatus::Denied:
+ qWarning("Camera permission is not granted!");
+ return;
+ case Qt::PermissionStatus::Granted:
+ break;
+ }
+ // microphone
+ QMicrophonePermission microphonePermission;
+ switch (qApp->checkPermission(microphonePermission)) {
+ case Qt::PermissionStatus::Undetermined:
+ qApp->requestPermission(microphonePermission, this, &MainWindow::init);
+ return;
+ case Qt::PermissionStatus::Denied:
+ qWarning("Microphone permission is not granted!");
+ return;
+ case Qt::PermissionStatus::Granted:
+ break;
+ }
+#endif
+ m_captureSession = new QMediaCaptureSession(this);
+
+ m_mediaDevices = new QMediaDevices(this);
+ // wait until devices list is ready
+ connect(m_mediaDevices, &QMediaDevices::videoInputsChanged,
+ [this]() { doCamera(); });
+}
+
+void MainWindow::doCamera()
+{
+ m_audioInput.reset(new QAudioInput);
+ m_captureSession->setAudioInput(m_audioInput.get());
+ const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+
+ ui->camerasComboBox->clear();
+ ui->camerasComboBox->setPlaceholderText("select camera");
+
+ for (const QCameraDevice &cameraDevice : cameras) {
+ if (ui->camerasComboBox->findText(cameraDevice.description()) == -1)
+ ui->camerasComboBox->addItem(cameraDevice.description(), cameraDevice.id());
+ }
+
+ if (cameras.count() == 0) {
+ qWarning() << "No camera found";
+ } else {
+
+ QGraphicsVideoItem *videoItem = new QGraphicsVideoItem();
+ QGraphicsScene *scene = new QGraphicsScene(this);
+ m_captureSession->setVideoOutput(videoItem); // sets videoSink
+
+ ui->graphicsView->setScene(scene);
+ ui->graphicsView->scene()->addItem(videoItem);
+ ui->graphicsView->show();
+ }
+}
+
+void MainWindow::on_startButton_clicked()
+{
+ m_camera.data()->start();
+}
+
+void MainWindow::on_stopButton_clicked()
+{
+ m_camera.data()->stop();
+}
+
+void MainWindow::on_captureButton_clicked()
+{
+ connect(m_camera.data(), &QCamera::errorOccurred,
+ [](QCamera::Error error, const QString &errorString) {
+ qWarning() << "Error occurred" << error << errorString;
+ });
+
+ QImageCapture *m_imageCapture = new QImageCapture(this);
+ connect(m_imageCapture, &QImageCapture::readyForCaptureChanged, [] (bool ready) {
+
+ qWarning() << "MainWindow::readyForCaptureChanged" << ready;
+ });
+
+ connect(m_imageCapture,
+ &QImageCapture::imageCaptured,
+ [] (int id, const QImage &preview) {
+ qWarning() << "MainWindow::imageCaptured" << id << preview;
+
+ });
+
+ connect(m_imageCapture, &QImageCapture::imageSaved,
+ [this] (int id, const QString &fileName) {
+ Q_UNUSED(id)
+ QFileInfo fi(fileName);
+ if (!fi.exists()) {
+ qWarning() << fileName << "Does not exist";
+ } else {
+ QDialog *dlg = new QDialog(this);
+ dlg->setWindowTitle(fi.fileName());
+ QHBoxLayout *l = new QHBoxLayout(dlg);
+ QLabel *label = new QLabel(this);
+ l->addWidget(label);
+ label->setPixmap(QPixmap(fileName));
+ dlg->show();
+ }
+
+ });
+ connect(m_imageCapture,
+ &QImageCapture::errorOccurred, []
+ (int id, QImageCapture::Error error, const QString &errorString) {
+ qWarning() << "MainWindow::errorOccurred" << id << error << errorString;
+ });
+
+ m_captureSession->setImageCapture(m_imageCapture);
+
+ // take photo
+ if (m_imageCapture->isReadyForCapture())
+ m_imageCapture->captureToFile(QStringLiteral("/home/web_user/image.png"));
+}
+
+void MainWindow::on_openButton_clicked()
+{
+ // open
+ QFileDialog *dialog = new QFileDialog(this);
+ dialog->setNameFilter(tr("All Files (*.*)"));
+ connect(dialog, &QFileDialog::fileSelected,
+ [this](const QString &file) {
+ qWarning() << "open this file" << file;
+ showFile(file);
+ });
+
+ dialog->show();
+}
+
+void MainWindow::on_camerasComboBox_textActivated(const QString &arg1)
+{
+ const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
+ for (const QCameraDevice &cameraDevice :cameras) {
+ if (arg1 == cameraDevice.description()) {
+ m_camera.reset(new QCamera(cameraDevice));
+ connect(m_captureSession, &QMediaCaptureSession::cameraChanged,
+ [this]() {
+ enableButtons(true);
+ });
+ m_captureSession->setCamera(m_camera.data());
+ break;
+ }
+ }
+}
+
+void MainWindow::on_recordButton_clicked()
+{
+ if (!isRecording) {
+ if (m_recorder)
+ delete m_recorder;
+ m_recorder = new QMediaRecorder();
+ connect(m_recorder, &QMediaRecorder::durationChanged,
+ [](qint64 duration) {
+ qWarning() << "MainWindow::durationChanged"
+ << duration;
+ });
+ m_captureSession->setRecorder(m_recorder);
+
+ m_recorder->setQuality(QMediaRecorder::HighQuality);
+ m_recorder->setOutputLocation(QUrl::fromLocalFile("test.mp4"));
+ m_recorder->record();
+ isRecording = true;
+ ui->recordButton->setText(QStringLiteral("Stop"));
+ } else {
+ m_recorder->stop();
+ isRecording = false;
+ m_recorder->deleteLater();
+ ui->recordButton->setText(QStringLiteral("Record"));
+ }
+}
+
+void MainWindow::enableButtons(bool ok)
+{
+ ui->captureButton->setEnabled(ok);
+ ui->startButton->setEnabled(ok);
+ ui->stopButton->setEnabled(ok);
+ ui->openButton->setEnabled(ok);
+ ui->recordButton->setEnabled(ok);
+}
+
+void MainWindow::showFile(const QString &fileName)
+{
+
+ const QSize screenGeometry = screen()->availableSize();
+ QFileInfo fi(fileName);
+ QDialog *dlg = new QDialog(this);
+ dlg->setWindowTitle(fi.fileName());
+ QHBoxLayout *layout = new QHBoxLayout(dlg);
+ QMediaPlayer *player = new QMediaPlayer(dlg);
+ connect(player, &QMediaPlayer::errorOccurred, [=] (QMediaPlayer::Error error, const QString &errorString) {
+ qWarning() << "MediaPlayer erro!:" << error << errorString;
+ });
+
+ QGraphicsVideoItem *m_videoItem = new QGraphicsVideoItem();
+ QSizeF dialogSize(screenGeometry.width() / 2, screenGeometry.height() / 2);
+ m_videoItem->setSize(dialogSize);
+ player->setVideoOutput(m_videoItem);
+ dlg->setGeometry(20, 20, dialogSize.width() + 40, dialogSize.height() + 40);
+
+ QGraphicsScene *scene = new QGraphicsScene(dlg);
+ QGraphicsView *graphicsView = new QGraphicsView(scene);
+ scene->addItem(m_videoItem);
+ layout->addWidget(graphicsView);
+
+ player->setSource(QUrl(fileName));
+
+ dlg->show();
+ player->play();
+}
diff --git a/tests/manual/wasm/camera/mainwindow.h b/tests/manual/wasm/camera/mainwindow.h
new file mode 100644
index 000000000..854c00935
--- /dev/null
+++ b/tests/manual/wasm/camera/mainwindow.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QImageCapture>
+#include <QMediaCaptureSession>
+#include <QGraphicsVideoItem>
+#include <QCamera>
+#include <QMediaDevices>
+#include <QMediaRecorder>
+
+QT_BEGIN_NAMESPACE
+namespace Ui { class MainWindow; }
+
+class QMediaCaptureSession;
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+ void init();
+
+private slots:
+ void doCamera();
+ void on_startButton_clicked();
+ void on_stopButton_clicked();
+ void on_captureButton_clicked();
+ void on_openButton_clicked();
+ void on_recordButton_clicked();
+ void on_camerasComboBox_textActivated(const QString &arg1);
+
+private:
+ Ui::MainWindow *ui;
+
+ void enableButtons(bool ok);
+ void showFile(const QString &filename);
+
+ QScopedPointer<QCamera> m_camera;
+ QMediaCaptureSession *m_captureSession;
+ QScopedPointer<QAudioInput> m_audioInput;
+ QMediaDevices *m_mediaDevices;
+ QMediaRecorder *m_recorder;
+ bool isRecording = false;
+};
+
+QT_END_NAMESPACE
+#endif // MAINWINDOW_H
diff --git a/tests/manual/wasm/camera/mainwindow.ui b/tests/manual/wasm/camera/mainwindow.ui
new file mode 100644
index 000000000..b5de38348
--- /dev/null
+++ b/tests/manual/wasm/camera/mainwindow.ui
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainWindow</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QComboBox" name="camerasComboBox">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ <item>
+ <property name="text">
+ <string/>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QGraphicsView" name="graphicsView"/>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="startButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>start camera</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stopButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>stop camera</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="3" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QPushButton" name="captureButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>capture photo</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="recordButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>record</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openButton">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>open</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>24</height>
+ </rect>
+ </property>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>