summaryrefslogtreecommitdiffstats
path: root/tests/auto/integration
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/integration')
-rw-r--r--tests/auto/integration/CMakeLists.txt28
-rw-r--r--tests/auto/integration/backends/CMakeLists.txt9
-rw-r--r--tests/auto/integration/backends/tst_backends.cpp60
-rw-r--r--tests/auto/integration/integration.pro3
-rw-r--r--tests/auto/integration/multiapp/CMakeLists.txt21
-rw-r--r--tests/auto/integration/multiapp/double-drop.wavbin0 -> 20626 bytes
-rw-r--r--tests/auto/integration/multiapp/tst_multiapp.cpp161
-rw-r--r--tests/auto/integration/multimedia.pro19
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/BLACKLIST2
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt29
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/qaudiodecoderbackend.pro21
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/testdata/test-no-audio-track.mp4bin0 -> 1589 bytes
-rw-r--r--tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp740
-rw-r--r--tests/auto/integration/qaudiodevice/BLACKLIST (renamed from tests/auto/integration/qaudiodeviceinfo/BLACKLIST)0
-rw-r--r--tests/auto/integration/qaudiodevice/CMakeLists.txt16
-rw-r--r--tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp164
-rw-r--r--tests/auto/integration/qaudiodeviceinfo/qaudiodeviceinfo.pro9
-rw-r--r--tests/auto/integration/qaudiodeviceinfo/tst_qaudiodeviceinfo.cpp248
-rw-r--r--tests/auto/integration/qaudioinput/BLACKLIST7
-rw-r--r--tests/auto/integration/qaudioinput/qaudioinput.pro9
-rw-r--r--tests/auto/integration/qaudioinput/tst_qaudioinput.cpp901
-rw-r--r--tests/auto/integration/qaudioinput/wavheader.cpp192
-rw-r--r--tests/auto/integration/qaudioinput/wavheader.h67
-rw-r--r--tests/auto/integration/qaudiooutput/BLACKLIST1
-rw-r--r--tests/auto/integration/qaudiooutput/qaudiooutput.pro9
-rw-r--r--tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp988
-rw-r--r--tests/auto/integration/qaudiooutput/wavheader.cpp192
-rw-r--r--tests/auto/integration/qaudiooutput/wavheader.h67
-rw-r--r--tests/auto/integration/qaudiosink/BLACKLIST11
-rw-r--r--tests/auto/integration/qaudiosink/CMakeLists.txt14
-rw-r--r--tests/auto/integration/qaudiosink/tst_qaudiosink.cpp1053
-rw-r--r--tests/auto/integration/qaudiosource/CMakeLists.txt14
-rw-r--r--tests/auto/integration/qaudiosource/tst_qaudiosource.cpp817
-rw-r--r--tests/auto/integration/qcamerabackend/BLACKLIST12
-rw-r--r--tests/auto/integration/qcamerabackend/CMakeLists.txt27
-rw-r--r--tests/auto/integration/qcamerabackend/Info.plist46
-rw-r--r--tests/auto/integration/qcamerabackend/qcamerabackend.pro10
-rw-r--r--tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp889
-rw-r--r--tests/auto/integration/qdeclarativevideooutput/qdeclarativevideooutput.pro11
-rw-r--r--tests/auto/integration/qdeclarativevideooutput/qml.qrc5
-rw-r--r--tests/auto/integration/qdeclarativevideooutput/tst_qdeclarativevideooutput.cpp740
-rw-r--r--tests/auto/integration/qdeclarativevideooutput_window/qdeclarativevideooutput_window.pro11
-rw-r--r--tests/auto/integration/qdeclarativevideooutput_window/qml.qrc5
-rw-r--r--tests/auto/integration/qdeclarativevideooutput_window/tst_qdeclarativevideooutput_window.cpp271
-rw-r--r--tests/auto/integration/qmediacapturesession/BLACKLIST1
-rw-r--r--tests/auto/integration/qmediacapturesession/CMakeLists.txt26
-rw-r--r--tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp1278
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/CMakeLists.txt22
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.cpp88
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.h49
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/framegenerator.cpp119
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/framegenerator.h74
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/mediainfo.h71
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.cpp302
-rw-r--r--tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.h42
-rw-r--r--tests/auto/integration/qmediaplayerbackend/BLACKLIST26
-rw-r--r--tests/auto/integration/qmediaplayerbackend/CMakeLists.txt39
-rw-r--r--tests/auto/integration/qmediaplayerbackend/LazyLoad.qml52
-rw-r--r--tests/auto/integration/qmediaplayerbackend/fake.h29
-rw-r--r--tests/auto/integration/qmediaplayerbackend/fixture.h79
-rw-r--r--tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h167
-rw-r--r--tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro20
-rw-r--r--tests/auto/integration/qmediaplayerbackend/server.h48
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4bin0 -> 37261 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4bin0 -> 36724 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4bin0 -> 37930 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4bin0 -> 37436 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webmbin0 -> 16108 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4bin0 -> 25954 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4bin0 -> 21412 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webmbin0 -> 34940 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.movbin0 -> 20164 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3bin62715 -> 33988 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4bin0 -> 1589 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4bin0 -> 1656 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4bin0 -> 1652 bytes
-rw-r--r--tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp3812
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/CMakeLists.txt30
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/README.md35
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.avibin0 -> 11284 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkvbin0 -> 3019 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4bin0 -> 3280 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpegbin0 -> 34816 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmvbin0 -> 29587 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webpbin0 -> 2676 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gifbin0 -> 131710 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr0.mp4bin0 -> 12335 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr24.mp4bin0 -> 12335 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray.mp4bin0 -> 6289 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray10le.mp4bin0 -> 6326 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv12.mp4bin0 -> 7734 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv16.mp4bin0 -> 8646 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv21.mp4bin0 -> 7734 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_rgb24.mp4bin0 -> 12335 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p.mp4bin0 -> 7747 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10.mp4bin0 -> 7699 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10le.mp4bin0 -> 7699 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p.mp4bin0 -> 8646 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10.mp4bin0 -> 8675 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10le.mp4bin0 -> 8675 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p.mp4bin0 -> 7575 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p10.mp4bin0 -> 7549 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj420p.mp4bin0 -> 8081 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj422p.mp4bin0 -> 9059 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj444p.mp4bin0 -> 7939 bytes
-rw-r--r--tests/auto/integration/qmediaplayerformatsupport/tst_qmediaplayerformatsupport.cpp124
-rw-r--r--tests/auto/integration/qml/CMakeLists.txt23
-rw-r--r--tests/auto/integration/qml/qml.pro10
-rw-r--r--tests/auto/integration/qml/soundeffect/tst_soundeffect.qml35
-rw-r--r--tests/auto/integration/qml/tst_qml.cpp29
-rw-r--r--tests/auto/integration/qquickvideooutput/CMakeLists.txt34
-rw-r--r--tests/auto/integration/qquickvideooutput/main.qml (renamed from tests/auto/integration/qdeclarativevideooutput/main.qml)4
-rw-r--r--tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp392
-rw-r--r--tests/auto/integration/qquickvideooutput_window/CMakeLists.txt34
-rw-r--r--tests/auto/integration/qquickvideooutput_window/main.qml (renamed from tests/auto/integration/qdeclarativevideooutput_window/main.qml)4
-rw-r--r--tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp96
-rw-r--r--tests/auto/integration/qscreencapturebackend/BLACKLIST11
-rw-r--r--tests/auto/integration/qscreencapturebackend/CMakeLists.txt17
-rw-r--r--tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp505
-rw-r--r--tests/auto/integration/qsound/BLACKLIST3
-rw-r--r--tests/auto/integration/qsound/qsound.pro13
-rw-r--r--tests/auto/integration/qsound/resources.qrc5
-rw-r--r--tests/auto/integration/qsound/test.wavbin38316 -> 0 bytes
-rw-r--r--tests/auto/integration/qsound/tst_qsound.cpp148
-rw-r--r--tests/auto/integration/qsoundeffect/CMakeLists.txt20
-rw-r--r--tests/auto/integration/qsoundeffect/qsoundeffect.pro19
-rw-r--r--tests/auto/integration/qsoundeffect/resources.qrc8
-rw-r--r--tests/auto/integration/qsoundeffect/test24.wavbin16482 -> 0 bytes
-rw-r--r--tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp164
-rw-r--r--tests/auto/integration/qvideoframebackend/CMakeLists.txt26
-rw-r--r--tests/auto/integration/qvideoframebackend/testdata/colors.mp4bin0 -> 26042 bytes
-rw-r--r--tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4bin0 -> 1589 bytes
-rw-r--r--tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp258
-rw-r--r--tests/auto/integration/qwindowcapturebackend/BLACKLIST13
-rw-r--r--tests/auto/integration/qwindowcapturebackend/CMakeLists.txt20
-rw-r--r--tests/auto/integration/qwindowcapturebackend/fixture.cpp234
-rw-r--r--tests/auto/integration/qwindowcapturebackend/fixture.h147
-rw-r--r--tests/auto/integration/qwindowcapturebackend/grabber.cpp62
-rw-r--r--tests/auto/integration/qwindowcapturebackend/grabber.h43
-rw-r--r--tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp278
-rw-r--r--tests/auto/integration/qwindowcapturebackend/widget.cpp125
-rw-r--r--tests/auto/integration/qwindowcapturebackend/widget.h43
-rw-r--r--tests/auto/integration/shared/mediabackendutils.h64
-rw-r--r--tests/auto/integration/shared/mediafileselector.h204
-rw-r--r--tests/auto/integration/shared/testvideosink.h69
148 files changed, 11793 insertions, 5799 deletions
diff --git a/tests/auto/integration/CMakeLists.txt b/tests/auto/integration/CMakeLists.txt
new file mode 100644
index 000000000..9be80db63
--- /dev/null
+++ b/tests/auto/integration/CMakeLists.txt
@@ -0,0 +1,28 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from integration.pro.
+
+# special case begin
+add_subdirectory(qaudiodecoderbackend)
+add_subdirectory(qaudiodevice)
+add_subdirectory(qaudiosource)
+add_subdirectory(qaudiosink)
+add_subdirectory(qmediaplayerbackend)
+add_subdirectory(qmediaplayerformatsupport)
+add_subdirectory(qsoundeffect)
+add_subdirectory(qvideoframebackend)
+add_subdirectory(backends)
+add_subdirectory(multiapp)
+add_subdirectory(qmediaframeinputsbackend)
+if(TARGET Qt::Widgets)
+ add_subdirectory(qmediacapturesession)
+ add_subdirectory(qcamerabackend)
+ add_subdirectory(qscreencapturebackend)
+ add_subdirectory(qwindowcapturebackend)
+endif()
+if(TARGET Qt::Quick)
+ add_subdirectory(qquickvideooutput)
+ add_subdirectory(qquickvideooutput_window)
+endif()
+# special case end
diff --git a/tests/auto/integration/backends/CMakeLists.txt b/tests/auto/integration/backends/CMakeLists.txt
new file mode 100644
index 000000000..b65293b5e
--- /dev/null
+++ b/tests/auto/integration/backends/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_backends
+ SOURCES
+ tst_backends.cpp
+ LIBRARIES
+ Qt::MultimediaPrivate
+)
diff --git a/tests/auto/integration/backends/tst_backends.cpp b/tests/auto/integration/backends/tst_backends.cpp
new file mode 100644
index 000000000..2cc1df256
--- /dev/null
+++ b/tests/auto/integration/backends/tst_backends.cpp
@@ -0,0 +1,60 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <QDebug>
+#include <QtCore/qsysinfo.h>
+#include <private/qplatformmediaintegration_p.h>
+
+QT_USE_NAMESPACE
+
+class tst_backends : public QObject
+{
+ Q_OBJECT
+
+public slots:
+ void initTestCase()
+ {
+ // Log operating system name and currently supported backends
+ qDebug() << QSysInfo::prettyProductName() << "supports backends"
+ << QPlatformMediaIntegration::availableBackends().join(", ");
+ }
+
+private slots:
+ void availableBackends_returns_expectedBackends_data()
+ {
+ QTest::addColumn<QStringList>("expectedBackends");
+ QStringList backends;
+
+#if defined(Q_OS_WIN)
+ backends << "windows";
+ if (QSysInfo::currentCpuArchitecture() == "x86_64")
+ backends << "ffmpeg";
+#elif defined(Q_OS_ANDROID)
+ backends << "android" << "ffmpeg";
+#elif defined(Q_OS_DARWIN)
+ backends << "darwin" << "ffmpeg";
+#elif defined(Q_OS_WASM)
+ backends << "wasm";
+#elif defined(Q_OS_QNX)
+ backends << "qnx";
+#else
+ backends << "ffmpeg" << "gstreamer";
+#endif
+
+ QTest::addRow("backends") << backends;
+ }
+
+ void availableBackends_returns_expectedBackends()
+ {
+ QFETCH(QStringList, expectedBackends);
+ QStringList actualBackends = QPlatformMediaIntegration::availableBackends();
+ for (const auto &expectedBackend : expectedBackends) {
+ QVERIFY(actualBackends.contains(expectedBackend));
+ }
+ }
+};
+
+QTEST_MAIN(tst_backends)
+
+#include "tst_backends.moc"
diff --git a/tests/auto/integration/integration.pro b/tests/auto/integration/integration.pro
deleted file mode 100644
index c61fbd4ee..000000000
--- a/tests/auto/integration/integration.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-TEMPLATE = subdirs
-
-SUBDIRS += multimedia.pro
diff --git a/tests/auto/integration/multiapp/CMakeLists.txt b/tests/auto/integration/multiapp/CMakeLists.txt
new file mode 100644
index 000000000..8a297cafc
--- /dev/null
+++ b/tests/auto/integration/multiapp/CMakeLists.txt
@@ -0,0 +1,21 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_multiapp
+ SOURCES
+ tst_multiapp.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::MultimediaPrivate
+)
+
+set(resources_resource_files
+ "double-drop.wav"
+)
+
+qt_add_resources(tst_multiapp "resources"
+ PREFIX
+ "/"
+ FILES
+ ${resources_resource_files}
+)
diff --git a/tests/auto/integration/multiapp/double-drop.wav b/tests/auto/integration/multiapp/double-drop.wav
new file mode 100644
index 000000000..bd9a507c7
--- /dev/null
+++ b/tests/auto/integration/multiapp/double-drop.wav
Binary files differ
diff --git a/tests/auto/integration/multiapp/tst_multiapp.cpp b/tests/auto/integration/multiapp/tst_multiapp.cpp
new file mode 100644
index 000000000..793a56e9d
--- /dev/null
+++ b/tests/auto/integration/multiapp/tst_multiapp.cpp
@@ -0,0 +1,161 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <QtCore/qdebug.h>
+#include <QtCore/qprocess.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qstring.h>
+#include <QtCore/qmetaobject.h>
+#include <QtMultimedia/qsoundeffect.h>
+#include <QtMultimedia/qmediadevices.h>
+#include <QtMultimedia/qaudiodevice.h>
+
+using namespace Qt::StringLiterals;
+
+QT_USE_NAMESPACE
+
+namespace {
+bool executeTestOutOfProcess(const QString &testName);
+void playSound();
+} // namespace
+
+class tst_multiapp : public QObject
+{
+ Q_OBJECT
+
+public slots:
+ void initTestCase()
+ {
+#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
+ QSKIP("Out-of-process testing does not behave correctly on mobile OS");
+#endif
+ }
+
+private slots:
+ void mediaDevices_doesNotCrash_whenRecreatingApplication()
+ {
+ QVERIFY(executeTestOutOfProcess(
+ "mediaDevices_doesNotCrash_whenRecreatingApplication_impl"_L1));
+ }
+
+ bool mediaDevices_doesNotCrash_whenRecreatingApplication_impl(int argc, char ** argv)
+ {
+ {
+ QCoreApplication app{ argc, argv };
+ QMediaDevices::defaultAudioOutput();
+ }
+ {
+ QCoreApplication app{ argc, argv };
+ QMediaDevices::defaultAudioOutput();
+ }
+
+ return true;
+ }
+
+ void soundEffect_doesNotCrash_whenRecreatingApplication()
+ {
+ QVERIFY(executeTestOutOfProcess(
+ "soundEffect_doesNotCrash_whenRecreatingApplication_impl"_L1));
+ }
+
+ bool soundEffect_doesNotCrash_whenRecreatingApplication_impl(int argc, char **argv)
+ {
+ Q_ASSERT(!qApp);
+
+ // Play a sound twice under two different application objects
+ // This verifies that QSoundEffect works in use cases where
+ // client application recreates Qt application instances,
+ // for example when the client application loads plugins
+ // implemented using Qt.
+ {
+ QCoreApplication app{ argc, argv };
+ playSound();
+ }
+ {
+ QCoreApplication app{ argc, argv };
+ playSound();
+ }
+
+ return true;
+ }
+
+};
+
+namespace {
+
+void playSound()
+{
+ const QUrl url{ "qrc:double-drop.wav"_L1 };
+
+ QSoundEffect effect;
+ effect.setSource(url);
+ effect.play();
+
+ QObject::connect(&effect, &QSoundEffect::playingChanged, qApp, [&]() {
+ if (!effect.isPlaying())
+ qApp->quit();
+ });
+
+ // In some CI configurations, we do not have any audio devices. We must therefore
+ // close the qApp on error signal instead of on playingChanged.
+ QObject::connect(&effect, &QSoundEffect::statusChanged, qApp, [&]() {
+ if (effect.status() == QSoundEffect::Status::Error) {
+ qDebug() << "Failed to play sound effect";
+ qApp->quit();
+ }
+ });
+
+ qApp->exec();
+}
+
+bool executeTestOutOfProcess(const QString &testName)
+{
+ const QStringList args{ "--run-test"_L1, testName };
+ const QString processName = QCoreApplication::applicationFilePath();
+ const int status = QProcess::execute(processName, args);
+ return status == 0;
+}
+
+} // namespace
+
+// This main function executes tests like normal qTest, and adds support
+// for executing specific test functions when called out of process. In this
+// case we don't create a QApplication, because the intent is to test how features
+// behave when no QApplication exists.
+int main(int argc, char *argv[])
+{
+ QCommandLineParser cmd;
+ const QCommandLineOption runTest{ QStringList{ "run-test" }, "Executes a named test",
+ "runTest" };
+ cmd.addOption(runTest);
+ cmd.parse({ argv, argv + argc });
+
+ if (cmd.isSet(runTest)) {
+ // We are requested to run a test case in a separate process without a Qt application
+ const QString testName = cmd.value(runTest);
+
+ bool returnValue = false;
+ tst_multiapp tc;
+
+ // Call the requested function on the test class
+ const bool invokeResult =
+ QMetaObject::invokeMethod(&tc, testName.toLatin1(), Qt::DirectConnection,
+ qReturnArg(returnValue), argc, argv);
+
+ return (invokeResult && returnValue) ? 0 : 1;
+ }
+
+ // If no special arguments are set, enter the regular QTest main routine
+ // The below lines are the same that QTEST_GUILESS_MAIN would stamp out,
+ // except the `int main(...)`
+ TESTLIB_SELFCOVERAGE_START("tst_multiapp")
+ QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<tst_multiapp>();
+ QCoreApplication app(argc, argv);
+ app.setAttribute(Qt::AA_Use96Dpi, true);
+ tst_multiapp tc;
+ QTEST_SET_MAIN_SOURCE_PATH
+ return QTest::qExec(&tc, argc, argv);
+}
+
+#include "tst_multiapp.moc"
diff --git a/tests/auto/integration/multimedia.pro b/tests/auto/integration/multimedia.pro
deleted file mode 100644
index 88960ec03..000000000
--- a/tests/auto/integration/multimedia.pro
+++ /dev/null
@@ -1,19 +0,0 @@
-
-TEMPLATE = subdirs
-SUBDIRS += \
- qaudiodecoderbackend \
- qaudiodeviceinfo \
- qaudioinput \
- qaudiooutput \
- qmediaplayerbackend \
- qcamerabackend \
- qsoundeffect \
- qsound
-
-qtHaveModule(quick) {
- SUBDIRS += \
- qdeclarativevideooutput \
- qdeclarativevideooutput_window
-}
-
-!qtHaveModule(widgets): SUBDIRS -= qcamerabackend
diff --git a/tests/auto/integration/qaudiodecoderbackend/BLACKLIST b/tests/auto/integration/qaudiodecoderbackend/BLACKLIST
deleted file mode 100644
index 316c5a083..000000000
--- a/tests/auto/integration/qaudiodecoderbackend/BLACKLIST
+++ /dev/null
@@ -1,2 +0,0 @@
-# QTBUG-56796
-windows
diff --git a/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt b/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt
new file mode 100644
index 000000000..d2206182f
--- /dev/null
+++ b/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt
@@ -0,0 +1,29 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qaudiodecoderbackend.pro.
+
+#####################################################################
+## tst_qaudiodecoderbackend Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ testdata/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qaudiodecoderbackend
+ SOURCES
+ tst_qaudiodecoderbackend.cpp
+ ../shared/mediafileselector.h
+ ../shared/mediabackendutils.h
+ INCLUDE_DIRECTORIES
+ ../shared/
+ ../../../../src/multimedia/audio
+ LIBRARIES
+ Qt::Gui
+ Qt::Multimedia
+ Qt::MultimediaPrivate
+ TESTDATA ${test_data}
+)
diff --git a/tests/auto/integration/qaudiodecoderbackend/qaudiodecoderbackend.pro b/tests/auto/integration/qaudiodecoderbackend/qaudiodecoderbackend.pro
deleted file mode 100644
index 672bcfa6a..000000000
--- a/tests/auto/integration/qaudiodecoderbackend/qaudiodecoderbackend.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-TARGET = tst_qaudiodecoderbackend
-
-QT += multimedia multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-TESTDATA += testdata/*
-
-INCLUDEPATH += \
- ../../../../src/multimedia/audio
-
-HEADERS += \
- ../shared/mediafileselector.h
-
-SOURCES += \
- tst_qaudiodecoderbackend.cpp
-
-boot2qt: {
- # Yocto sysroot does not have gstreamer/wav
- QMAKE_CXXFLAGS += -DWAV_SUPPORT_NOT_FORCED
-}
diff --git a/tests/auto/integration/qaudiodecoderbackend/testdata/test-no-audio-track.mp4 b/tests/auto/integration/qaudiodecoderbackend/testdata/test-no-audio-track.mp4
new file mode 100644
index 000000000..6b67a3433
--- /dev/null
+++ b/tests/auto/integration/qaudiodecoderbackend/testdata/test-no-audio-track.mp4
Binary files differ
diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
index 1e582d14b..5a48b4457 100644
--- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
+++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp
@@ -1,40 +1,30 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QDebug>
#include "qaudiodecoder.h"
-#include "../shared/mediafileselector.h"
+#include "mediafileselector.h"
+#include "mediabackendutils.h"
-#define TEST_FILE_NAME "testdata/test.wav"
-#define TEST_UNSUPPORTED_FILE_NAME "testdata/test-unsupported.avi"
-#define TEST_CORRUPTED_FILE_NAME "testdata/test-corrupted.wav"
+constexpr char TEST_FILE_NAME[] = "testdata/test.wav";
+constexpr char TEST_UNSUPPORTED_FILE_NAME[] = "testdata/test-unsupported.avi";
+constexpr char TEST_CORRUPTED_FILE_NAME[] = "testdata/test-corrupted.wav";
+constexpr char TEST_INVALID_SOURCE[] = "invalid";
+constexpr char TEST_NO_AUDIO_TRACK[] = "testdata/test-no-audio-track.mp4";
+
+constexpr int testFileSampleCount = 44094;
+constexpr int testFileSampleRate = 44100;
+
+constexpr std::chrono::microseconds testFileDuration = [] {
+ using namespace std::chrono;
+ using namespace std::chrono_literals;
+ auto duration = nanoseconds(1s) * testFileSampleCount / testFileSampleRate;
+ return round<microseconds>(duration);
+}();
+
+constexpr qint64 testFileDurationUs = qint64(testFileDuration.count());
QT_USE_NAMESPACE
@@ -54,13 +44,29 @@ public slots:
void initTestCase();
private slots:
+ void testMediaFilesAreSupported();
+ void directBruteForceReading();
+ void indirectReadingByBufferReadySignal();
+ void indirectReadingByBufferAvailableSignal();
+ void stopOnBufferReady();
+ void restartOnBufferReady();
+ void restartOnFinish();
void fileTest();
void unsupportedFileTest();
void corruptedFileTest();
+ void invalidSource();
void deviceTest();
+ void play_emitsFormatError_whenMediaHasNoAudioTrack();
private:
- bool isWavSupported();
+ QUrl testFileUrl(const QString filePath);
+ void checkNoMoreChanges(QAudioDecoder &decoder);
+#ifdef Q_OS_ANDROID
+ QTemporaryFile *temporaryFile = nullptr;
+#endif
+
+ MediaFileSelector m_mediaSelector;
+ MaybeUrl m_wavFile = QUnexpect{};
};
void tst_QAudioDecoderBackend::init()
@@ -70,81 +76,345 @@ void tst_QAudioDecoderBackend::init()
void tst_QAudioDecoderBackend::initTestCase()
{
QAudioDecoder d;
- if (!d.isAvailable())
+ if (!d.isSupported())
QSKIP("Audio decoder service is not available");
- qRegisterMetaType<QMediaContent>();
+ m_wavFile = m_mediaSelector.select(QFINDTESTDATA(TEST_FILE_NAME));
}
void tst_QAudioDecoderBackend::cleanup()
{
+#ifdef Q_OS_ANDROID
+ if (temporaryFile) {
+ delete temporaryFile;
+ temporaryFile = nullptr;
+ }
+#endif
}
-bool tst_QAudioDecoderBackend::isWavSupported()
+QUrl tst_QAudioDecoderBackend::testFileUrl(const QString filePath)
{
-#ifdef WAV_SUPPORT_NOT_FORCED
- return !MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA(TEST_FILE_NAME)).isNull();
+ QUrl url;
+#ifndef Q_OS_ANDROID
+ QFileInfo fileInfo(QFINDTESTDATA(filePath));
+ url = QUrl::fromLocalFile(fileInfo.absoluteFilePath());
#else
- return true;
+ QFile file(":/" + filePath);
+ if (temporaryFile) {
+ delete temporaryFile;
+ temporaryFile = nullptr;
+ }
+ if (file.open(QIODevice::ReadOnly)) {
+ temporaryFile = QTemporaryFile::createNativeFile(file);
+ url = QUrl(temporaryFile->fileName());
+ }
#endif
+ return url;
+}
+
+void tst_QAudioDecoderBackend::checkNoMoreChanges(QAudioDecoder &decoder)
+{
+ QSignalSpy finishedSpy(&decoder, &QAudioDecoder::finished);
+ QSignalSpy bufferReadySpy(&decoder, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferAvailableSpy(&decoder, &QAudioDecoder::bufferAvailableChanged);
+
+ QTest::qWait(50); // wait a bit to check nothing happened after finish
+
+ QCOMPARE(finishedSpy.size(), 0);
+ QCOMPARE(bufferReadySpy.size(), 0);
+ QCOMPARE(bufferAvailableSpy.size(), 0);
+}
+
+void tst_QAudioDecoderBackend::testMediaFilesAreSupported()
+{
+ QCOMPARE(m_mediaSelector.dumpErrors(), "");
+}
+
+void tst_QAudioDecoderBackend::directBruteForceReading()
+{
+ CHECK_SELECTED_URL(m_wavFile);
+
+ QAudioDecoder decoder;
+ if (decoder.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+
+ int sampleCount = 0;
+
+ decoder.setSource(*m_wavFile);
+ QVERIFY(!decoder.isDecoding());
+ QVERIFY(!decoder.bufferAvailable());
+
+ decoder.start();
+ QTRY_VERIFY(decoder.isDecoding());
+
+ auto waitAndCheck = [](auto &&predicate) { QVERIFY(QTest::qWaitFor(predicate)); };
+
+ auto waitForBufferAvailable = [&]() {
+ waitAndCheck([&]() { return !decoder.isDecoding() || decoder.bufferAvailable(); });
+
+ return decoder.bufferAvailable();
+ };
+
+ while (waitForBufferAvailable()) {
+ auto buffer = decoder.read();
+ QVERIFY(buffer.isValid());
+
+ sampleCount += buffer.sampleCount();
+ }
+
+ checkNoMoreChanges(decoder);
+
+ QCOMPARE(sampleCount, testFileSampleCount);
+}
+
+void tst_QAudioDecoderBackend::indirectReadingByBufferReadySignal()
+{
+ CHECK_SELECTED_URL(m_wavFile);
+
+ QAudioDecoder decoder;
+ if (decoder.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+
+ int sampleCount = 0;
+
+ connect(&decoder, &QAudioDecoder::bufferReady, this, [&]() {
+ QVERIFY(decoder.bufferAvailable());
+
+ auto buffer = decoder.read();
+ QVERIFY(buffer.isValid());
+ QVERIFY(!decoder.bufferAvailable());
+
+ sampleCount += buffer.sampleCount();
+ });
+
+ QSignalSpy decodingSpy(&decoder, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy finishSpy(&decoder, &QAudioDecoder::finished);
+
+ decoder.setSource(*m_wavFile);
+ QVERIFY(!decoder.isDecoding());
+ QVERIFY(!decoder.bufferAvailable());
+
+ decoder.start();
+ QTRY_VERIFY(decodingSpy.size() >= 1);
+
+ QTRY_VERIFY(finishSpy.size() == 1);
+ QVERIFY(!decoder.isDecoding());
+
+ checkNoMoreChanges(decoder);
+
+ QCOMPARE(sampleCount, testFileSampleCount);
+ QCOMPARE(finishSpy.size(), 1);
+}
+
+void tst_QAudioDecoderBackend::indirectReadingByBufferAvailableSignal() {
+ CHECK_SELECTED_URL(m_wavFile);
+
+ QAudioDecoder decoder;
+ if (decoder.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+
+ int sampleCount = 0;
+
+ connect(&decoder, &QAudioDecoder::bufferAvailableChanged, this, [&](bool available) {
+ QCOMPARE(decoder.bufferAvailable(), available);
+
+ if (!available)
+ return;
+
+ while (decoder.bufferAvailable()) {
+ auto buffer = decoder.read();
+ QVERIFY(buffer.isValid());
+
+ sampleCount += buffer.sampleCount();
+ }
+ });
+
+ QSignalSpy decodingSpy(&decoder, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy finishSpy(&decoder, &QAudioDecoder::finished);
+
+ decoder.setSource(*m_wavFile);
+ QVERIFY(!decoder.isDecoding());
+ QVERIFY(!decoder.bufferAvailable());
+
+ decoder.start();
+ QTRY_VERIFY(decodingSpy.size() >= 1);
+
+ QTRY_VERIFY(finishSpy.size() == 1);
+ QVERIFY(!decoder.isDecoding());
+
+ checkNoMoreChanges(decoder);
+
+ QCOMPARE(sampleCount, testFileSampleCount);
+ QCOMPARE(finishSpy.size(), 1);
+}
+
+void tst_QAudioDecoderBackend::stopOnBufferReady()
+{
+ CHECK_SELECTED_URL(m_wavFile);
+
+ QAudioDecoder decoder;
+ if (decoder.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+
+ connect(&decoder, &QAudioDecoder::bufferReady, this, [&]() {
+ decoder.read(); // run next reading
+ decoder.stop();
+ });
+
+ QSignalSpy finishSpy(&decoder, &QAudioDecoder::finished);
+ QSignalSpy bufferReadySpy(&decoder, &QAudioDecoder::bufferReady);
+
+ decoder.setSource(*m_wavFile);
+ decoder.start();
+
+ bufferReadySpy.wait();
+ QVERIFY(!decoder.isDecoding());
+
+ checkNoMoreChanges(decoder);
+
+ QCOMPARE(bufferReadySpy.size(), 1);
+}
+
+void tst_QAudioDecoderBackend::restartOnBufferReady()
+{
+ QSKIP_GSTREAMER("QTBUG-124005: failures on gstreamer");
+
+ CHECK_SELECTED_URL(m_wavFile);
+
+ QAudioDecoder decoder;
+ if (decoder.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+
+ int sampleCount = 0;
+
+ std::once_flag restartOnce;
+ connect(&decoder, &QAudioDecoder::bufferReady, this, [&]() {
+ QVERIFY(decoder.bufferAvailable());
+
+ auto buffer = decoder.read();
+ QVERIFY(buffer.isValid());
+ QVERIFY(!decoder.bufferAvailable());
+
+ sampleCount += buffer.sampleCount();
+
+ std::call_once(restartOnce, [&]() {
+ sampleCount = 0;
+ decoder.stop();
+ decoder.start();
+ });
+ });
+
+ QSignalSpy finishSpy(&decoder, &QAudioDecoder::finished);
+
+ decoder.setSource(*m_wavFile);
+ decoder.start();
+
+ QTRY_VERIFY2(finishSpy.size() == 2, "Wait for signals after restart and after finishing");
+ QVERIFY(!decoder.isDecoding());
+
+ checkNoMoreChanges(decoder);
+
+ QCOMPARE(sampleCount, testFileSampleCount);
+}
+
+void tst_QAudioDecoderBackend::restartOnFinish()
+{
+ CHECK_SELECTED_URL(m_wavFile);
+
+ QAudioDecoder decoder;
+ if (decoder.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+
+ int sampleCount = 0;
+
+ connect(&decoder, &QAudioDecoder::bufferReady, this, [&]() {
+ auto buffer = decoder.read();
+ QVERIFY(buffer.isValid());
+
+ sampleCount += buffer.sampleCount();
+ });
+
+ QSignalSpy finishSpy(&decoder, &QAudioDecoder::finished);
+
+ std::once_flag restartOnce;
+ connect(&decoder, &QAudioDecoder::finished, this, [&]() {
+ QVERIFY(!decoder.bufferAvailable());
+ QVERIFY(!decoder.isDecoding());
+
+ std::call_once(restartOnce, [&]() {
+ sampleCount = 0;
+ decoder.start();
+ });
+ });
+
+ decoder.setSource(*m_wavFile);
+ decoder.start();
+
+ QTRY_VERIFY(finishSpy.size() == 2);
+
+ QVERIFY(!decoder.isDecoding());
+
+ checkNoMoreChanges(decoder);
+ QCOMPARE(sampleCount, testFileSampleCount);
}
void tst_QAudioDecoderBackend::fileTest()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_wavFile);
QAudioDecoder d;
- if (d.error() == QAudioDecoder::ServiceMissingError)
+ if (d.error() == QAudioDecoder::NotSupportedError)
QSKIP("There is no audio decoding support on this platform.");
QAudioBuffer buffer;
quint64 duration = 0;
int byteCount = 0;
int sampleCount = 0;
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QCOMPARE(d.sourceFilename(), QString(""));
+ QCOMPARE(d.source(), QStringLiteral(""));
QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file
- QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
- d.setSourceFilename(fileInfo.absoluteFilePath());
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+
+ d.setSource(*m_wavFile);
+ QVERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
- QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
+ QCOMPARE(d.source(), *m_wavFile);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
- QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
- QTRY_VERIFY(!stateSpy.isEmpty());
+
+ QTRY_VERIFY(!isDecodingSpy.isEmpty());
QTRY_VERIFY(!readySpy.isEmpty());
QTRY_VERIFY(!bufferChangedSpy.isEmpty());
QVERIFY(d.bufferAvailable());
QTRY_VERIFY(!durationSpy.isEmpty());
- QVERIFY(qAbs(d.duration() - 1000) < 20);
+
+ QVERIFY(qAbs(durationSpy.front().front().value<qint64>() - 1000) < 20);
+ if (finishedSpy.empty())
+ QVERIFY(qAbs(d.duration() - 1000) < 20);
+ else
+ QCOMPARE(d.duration(), -1);
buffer = d.read();
QVERIFY(buffer.isValid());
// Test file is 44.1K 16bit mono, 44094 samples
QCOMPARE(buffer.format().channelCount(), 1);
- QCOMPARE(buffer.format().sampleRate(), 44100);
- QCOMPARE(buffer.format().sampleSize(), 16);
- QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt);
- QCOMPARE(buffer.format().codec(), QString("audio/pcm"));
+ QCOMPARE(buffer.format().sampleRate(), testFileSampleRate);
+ QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16);
QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono
// The decoder should still have no format set
QVERIFY(d.audioFormat() == QAudioFormat());
-
QVERIFY(errorSpy.isEmpty());
duration += buffer.duration();
@@ -152,53 +422,61 @@ void tst_QAudioDecoderBackend::fileTest()
byteCount += buffer.byteCount();
// Now drain the decoder
- if (sampleCount < 44094) {
+ if (sampleCount < testFileSampleCount) {
QTRY_COMPARE(d.bufferAvailable(), true);
}
+ auto durationToMs = [](uint64_t dur) {
+ if (isGStreamerPlatform())
+ return std::round(dur / 1000.0);
+ else
+ return dur / 1000.0;
+ };
+
while (d.bufferAvailable()) {
buffer = d.read();
QVERIFY(buffer.isValid());
QTRY_VERIFY(!positionSpy.isEmpty());
- QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
+ QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qint64(durationToMs(duration)));
duration += buffer.duration();
sampleCount += buffer.sampleCount();
byteCount += buffer.byteCount();
- if (sampleCount < 44094) {
+ if (sampleCount < testFileSampleCount) {
QTRY_COMPARE(d.bufferAvailable(), true);
}
}
// Make sure the duration is roughly correct (+/- 20ms)
- QCOMPARE(sampleCount, 44094);
- QCOMPARE(byteCount, 44094 * 2);
+ QCOMPARE(sampleCount, testFileSampleCount);
+ QCOMPARE(byteCount, testFileSampleCount * 2);
QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
- QTRY_COMPARE(finishedSpy.count(), 1);
+ QTRY_COMPARE(finishedSpy.size(), 1);
QVERIFY(!d.bufferAvailable());
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
d.stop();
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
- QTRY_COMPARE(durationSpy.count(), 2);
+ QTRY_VERIFY(!d.isDecoding());
+ QTRY_COMPARE(durationSpy.size(), 2);
QCOMPARE(d.duration(), qint64(-1));
QVERIFY(!d.bufferAvailable());
readySpy.clear();
bufferChangedSpy.clear();
- stateSpy.clear();
+ isDecodingSpy.clear();
durationSpy.clear();
finishedSpy.clear();
positionSpy.clear();
+#ifdef Q_OS_ANDROID
+ QSKIP("Setting a desired audio format is not yet supported on Android", QTest::SkipSingle);
+#endif
// change output audio format
QAudioFormat format;
format.setChannelCount(2);
- format.setSampleSize(8);
format.setSampleRate(11050);
- format.setCodec("audio/pcm");
- format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleFormat(QAudioFormat::UInt8);
d.setAudioFormat(format);
@@ -213,13 +491,16 @@ void tst_QAudioDecoderBackend::fileTest()
byteCount = 0;
d.start();
- QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
- QTRY_VERIFY(!stateSpy.isEmpty());
+ QTRY_VERIFY(!isDecodingSpy.isEmpty());
QTRY_VERIFY(!readySpy.isEmpty());
QTRY_VERIFY(!bufferChangedSpy.isEmpty());
QVERIFY(d.bufferAvailable());
QTRY_VERIFY(!durationSpy.isEmpty());
- QVERIFY(qAbs(d.duration() - 1000) < 20);
+ QVERIFY(qAbs(durationSpy.front().front().value<qint64>() - 1000) < 20);
+ if (finishedSpy.empty())
+ QVERIFY(qAbs(d.duration() - 1000) < 20);
+ else
+ QCOMPARE(d.duration(), -1);
buffer = d.read();
QVERIFY(buffer.isValid());
@@ -235,40 +516,35 @@ void tst_QAudioDecoderBackend::fileTest()
sampleCount += buffer.sampleCount();
byteCount += buffer.byteCount();
- // Now drain the decoder
- if (duration < 998000) {
- QTRY_COMPARE(d.bufferAvailable(), true);
- }
+ while (finishedSpy.isEmpty() || d.bufferAvailable()) {
+ if (!d.bufferAvailable()) {
+ QTest::qWait(std::chrono::milliseconds(10));
+ continue;
+ }
- while (d.bufferAvailable()) {
buffer = d.read();
QVERIFY(buffer.isValid());
QTRY_VERIFY(!positionSpy.isEmpty());
- QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
- QVERIFY(d.position() - (duration / 1000) < 20);
+ QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qlonglong(durationToMs(duration)));
+ QCOMPARE_LT(d.position() - durationToMs(duration), 20u);
duration += buffer.duration();
sampleCount += buffer.sampleCount();
byteCount += buffer.byteCount();
-
- if (duration < 998000) {
- QTRY_COMPARE(d.bufferAvailable(), true);
- }
}
// Resampling might end up with fewer or more samples
// so be a bit sloppy
- QVERIFY(qAbs(sampleCount - 22047) < 100);
- QVERIFY(qAbs(byteCount - 22047) < 100);
- QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
- QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
- QTRY_COMPARE(finishedSpy.count(), 1);
+ QCOMPARE_LT(qAbs(sampleCount - 22047), 100);
+ QCOMPARE_LT(qAbs(byteCount - 22047), 100);
+ QCOMPARE_LT(qAbs(qint64(duration) - testFileDurationUs), 20000);
+ QCOMPARE_LT(qAbs((d.position() + (buffer.duration() / 1000)) - 1000), 20);
QVERIFY(!d.bufferAvailable());
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QVERIFY(!d.isDecoding());
d.stop();
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
- QTRY_COMPARE(durationSpy.count(), 2);
+ QTRY_VERIFY(!d.isDecoding());
+ QTRY_COMPARE(durationSpy.size(), 2);
QCOMPARE(d.duration(), qint64(-1));
QVERIFY(!d.bufferAvailable());
}
@@ -279,32 +555,32 @@ void tst_QAudioDecoderBackend::fileTest()
void tst_QAudioDecoderBackend::unsupportedFileTest()
{
QAudioDecoder d;
- if (d.error() == QAudioDecoder::ServiceMissingError)
+ if (d.error() == QAudioDecoder::NotSupportedError)
QSKIP("There is no audio decoding support on this platform.");
QAudioBuffer buffer;
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QCOMPARE(d.sourceFilename(), QString(""));
+ QCOMPARE(d.source(), QStringLiteral(""));
QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file
- QFileInfo fileInfo(QFINDTESTDATA(TEST_UNSUPPORTED_FILE_NAME));
- d.setSourceFilename(fileInfo.absoluteFilePath());
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QUrl url = testFileUrl(TEST_UNSUPPORTED_FILE_NAME);
+ d.setSource(url);
+ QVERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
- QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
+ QCOMPARE(d.source(), url);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
- QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.audioFormat(), QAudioFormat());
QCOMPARE(d.duration(), qint64(-1));
@@ -321,16 +597,17 @@ void tst_QAudioDecoderBackend::unsupportedFileTest()
// Check all other spies.
QVERIFY(readySpy.isEmpty());
QVERIFY(bufferChangedSpy.isEmpty());
- QVERIFY(stateSpy.isEmpty());
+ QVERIFY(isDecodingSpy.isEmpty());
QVERIFY(finishedSpy.isEmpty());
QVERIFY(positionSpy.isEmpty());
- QVERIFY(durationSpy.isEmpty());
+ // Either reject the file directly, or set the duration to 5secs on setUrl() and back to -1 on start()
+ QVERIFY(durationSpy.isEmpty() || durationSpy.size() == 2);
errorSpy.clear();
// Try read even if the file is not supported to test robustness.
buffer = d.read();
- QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QVERIFY(!buffer.isValid());
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.position(), qint64(-1));
@@ -338,14 +615,14 @@ void tst_QAudioDecoderBackend::unsupportedFileTest()
QVERIFY(errorSpy.isEmpty());
QVERIFY(readySpy.isEmpty());
QVERIFY(bufferChangedSpy.isEmpty());
- QVERIFY(stateSpy.isEmpty());
+ QVERIFY(isDecodingSpy.isEmpty());
QVERIFY(finishedSpy.isEmpty());
QVERIFY(positionSpy.isEmpty());
- QVERIFY(durationSpy.isEmpty());
+ QVERIFY(durationSpy.isEmpty() || durationSpy.size() == 2);
d.stop();
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QCOMPARE(d.duration(), qint64(-1));
QVERIFY(!d.bufferAvailable());
}
@@ -357,32 +634,32 @@ void tst_QAudioDecoderBackend::unsupportedFileTest()
void tst_QAudioDecoderBackend::corruptedFileTest()
{
QAudioDecoder d;
- if (d.error() == QAudioDecoder::ServiceMissingError)
+ if (d.error() == QAudioDecoder::NotSupportedError)
QSKIP("There is no audio decoding support on this platform.");
QAudioBuffer buffer;
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QCOMPARE(d.sourceFilename(), QString(""));
+ QCOMPARE(d.source(), QUrl());
QVERIFY(d.audioFormat() == QAudioFormat());
// Test local file
- QFileInfo fileInfo(QFINDTESTDATA(TEST_CORRUPTED_FILE_NAME));
- d.setSourceFilename(fileInfo.absoluteFilePath());
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QUrl url = testFileUrl(TEST_CORRUPTED_FILE_NAME);
+ d.setSource(url);
+ QVERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
- QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
+ QCOMPARE(d.source(), url);
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
d.start();
- QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.audioFormat(), QAudioFormat());
QCOMPARE(d.duration(), qint64(-1));
@@ -399,7 +676,7 @@ void tst_QAudioDecoderBackend::corruptedFileTest()
// Check all other spies.
QVERIFY(readySpy.isEmpty());
QVERIFY(bufferChangedSpy.isEmpty());
- QVERIFY(stateSpy.isEmpty());
+ QVERIFY(isDecodingSpy.isEmpty());
QVERIFY(finishedSpy.isEmpty());
QVERIFY(positionSpy.isEmpty());
QVERIFY(durationSpy.isEmpty());
@@ -408,7 +685,7 @@ void tst_QAudioDecoderBackend::corruptedFileTest()
// Try read even if the file is corrupted to test the robustness.
buffer = d.read();
- QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QVERIFY(!buffer.isValid());
QVERIFY(!d.bufferAvailable());
QCOMPARE(d.position(), qint64(-1));
@@ -416,72 +693,161 @@ void tst_QAudioDecoderBackend::corruptedFileTest()
QVERIFY(errorSpy.isEmpty());
QVERIFY(readySpy.isEmpty());
QVERIFY(bufferChangedSpy.isEmpty());
- QVERIFY(stateSpy.isEmpty());
+ QVERIFY(isDecodingSpy.isEmpty());
QVERIFY(finishedSpy.isEmpty());
QVERIFY(positionSpy.isEmpty());
QVERIFY(durationSpy.isEmpty());
+ d.stop();
+ QTRY_VERIFY(!d.isDecoding());
+ QCOMPARE(d.duration(), qint64(-1));
+ QVERIFY(!d.bufferAvailable());
+}
+
+void tst_QAudioDecoderBackend::invalidSource()
+{
+ QAudioDecoder d;
+ if (d.error() == QAudioDecoder::NotSupportedError)
+ QSKIP("There is no audio decoding support on this platform.");
+ QAudioBuffer buffer;
+
+ QVERIFY(!d.isDecoding());
+ QVERIFY(d.bufferAvailable() == false);
+ QCOMPARE(d.source(), QUrl());
+ QVERIFY(d.audioFormat() == QAudioFormat());
+
+ // Test invalid file source
+ QFileInfo fileInfo(TEST_INVALID_SOURCE);
+ QUrl url = QUrl::fromLocalFile(fileInfo.absoluteFilePath());
+ d.setSource(url);
+ QVERIFY(!d.isDecoding());
+ QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.source(), url);
+
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
+ QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
+
+ d.start();
+ QTRY_VERIFY(!d.isDecoding());
+ QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.audioFormat(), QAudioFormat());
+ QCOMPARE(d.duration(), qint64(-1));
+ QCOMPARE(d.position(), qint64(-1));
+
+ // Check the error code.
+ QTRY_VERIFY(!errorSpy.isEmpty());
+
+ // Have to use qvariant_cast, toInt will return 0 because unrecognized type;
+ QAudioDecoder::Error errorCode = qvariant_cast<QAudioDecoder::Error>(errorSpy.takeLast().at(0));
+ QCOMPARE(errorCode, QAudioDecoder::ResourceError);
+ QCOMPARE(d.error(), QAudioDecoder::ResourceError);
+
+ // Check all other spies.
+ QVERIFY(readySpy.isEmpty());
+ QVERIFY(bufferChangedSpy.isEmpty());
+ QVERIFY(isDecodingSpy.isEmpty());
+ QVERIFY(finishedSpy.isEmpty());
+ QVERIFY(positionSpy.isEmpty());
+ QVERIFY(durationSpy.isEmpty());
+
+ errorSpy.clear();
+
+ d.stop();
+ QTRY_VERIFY(!d.isDecoding());
+ QCOMPARE(d.duration(), qint64(-1));
+ QVERIFY(!d.bufferAvailable());
+
+ QFile file;
+ file.setFileName(TEST_INVALID_SOURCE);
+ file.open(QIODevice::ReadOnly);
+ d.setSourceDevice(&file);
+
+ d.start();
+ QTRY_VERIFY(!d.isDecoding());
+ QVERIFY(!d.bufferAvailable());
+ QCOMPARE(d.audioFormat(), QAudioFormat());
+ QCOMPARE(d.duration(), qint64(-1));
+ QCOMPARE(d.position(), qint64(-1));
+
+ // Check the error code.
+ QTRY_VERIFY(!errorSpy.isEmpty());
+ errorCode = qvariant_cast<QAudioDecoder::Error>(errorSpy.takeLast().at(0));
+ QCOMPARE(errorCode, QAudioDecoder::ResourceError);
+ QCOMPARE(d.error(), QAudioDecoder::ResourceError);
+ // Check all other spies.
+ QVERIFY(readySpy.isEmpty());
+ QVERIFY(bufferChangedSpy.isEmpty());
+ QVERIFY(isDecodingSpy.isEmpty());
+ QVERIFY(finishedSpy.isEmpty());
+ QVERIFY(positionSpy.isEmpty());
+ QVERIFY(durationSpy.isEmpty());
+
+ errorSpy.clear();
d.stop();
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QCOMPARE(d.duration(), qint64(-1));
QVERIFY(!d.bufferAvailable());
}
void tst_QAudioDecoderBackend::deviceTest()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ using namespace std::chrono;
+ CHECK_SELECTED_URL(m_wavFile);
QAudioDecoder d;
- if (d.error() == QAudioDecoder::ServiceMissingError)
+ if (d.error() == QAudioDecoder::NotSupportedError)
QSKIP("There is no audio decoding support on this platform.");
QAudioBuffer buffer;
quint64 duration = 0;
int sampleCount = 0;
- QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
- QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
+ QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady);
+ QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged);
QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
- QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
- QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
- QSignalSpy finishedSpy(&d, SIGNAL(finished()));
- QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
+ QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged);
+ QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged);
+ QSignalSpy finishedSpy(&d, &QAudioDecoder::finished);
+ QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged);
- QVERIFY(d.state() == QAudioDecoder::StoppedState);
+ QVERIFY(!d.isDecoding());
QVERIFY(d.bufferAvailable() == false);
- QCOMPARE(d.sourceFilename(), QString(""));
+ QCOMPARE(d.source(), QStringLiteral(""));
QVERIFY(d.audioFormat() == QAudioFormat());
-
- QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
- QFile file(fileInfo.absoluteFilePath());
+ QFile file(m_wavFile->toString());
QVERIFY(file.open(QIODevice::ReadOnly));
d.setSourceDevice(&file);
QVERIFY(d.sourceDevice() == &file);
- QVERIFY(d.sourceFilename().isEmpty());
+ QVERIFY(d.source().isEmpty());
// We haven't set the format yet
QVERIFY(d.audioFormat() == QAudioFormat());
d.start();
- QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
- QTRY_VERIFY(!stateSpy.isEmpty());
+
+ QTRY_VERIFY(!isDecodingSpy.isEmpty());
QTRY_VERIFY(!readySpy.isEmpty());
QTRY_VERIFY(!bufferChangedSpy.isEmpty());
QVERIFY(d.bufferAvailable());
QTRY_VERIFY(!durationSpy.isEmpty());
- QVERIFY(qAbs(d.duration() - 1000) < 20);
+ if (finishedSpy.empty())
+ QVERIFY(qAbs(d.duration() - 1000) < 20);
+ else
+ QCOMPARE(d.duration(), -1);
buffer = d.read();
QVERIFY(buffer.isValid());
// Test file is 44.1K 16bit mono
QCOMPARE(buffer.format().channelCount(), 1);
- QCOMPARE(buffer.format().sampleRate(), 44100);
- QCOMPARE(buffer.format().sampleSize(), 16);
- QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt);
- QCOMPARE(buffer.format().codec(), QString("audio/pcm"));
+ QCOMPARE(buffer.format().sampleRate(), testFileSampleRate);
+ QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16);
QVERIFY(errorSpy.isEmpty());
@@ -489,7 +855,7 @@ void tst_QAudioDecoderBackend::deviceTest()
sampleCount += buffer.sampleCount();
// Now drain the decoder
- if (sampleCount < 44094) {
+ if (sampleCount < testFileSampleCount) {
QTRY_COMPARE(d.bufferAvailable(), true);
}
@@ -497,43 +863,50 @@ void tst_QAudioDecoderBackend::deviceTest()
buffer = d.read();
QVERIFY(buffer.isValid());
QTRY_VERIFY(!positionSpy.isEmpty());
- QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
+ if (isGStreamerPlatform())
+ QCOMPARE_EQ(positionSpy.takeLast().at(0).toLongLong(),
+ round<milliseconds>(microseconds{ duration }).count());
+ else
+ QCOMPARE_EQ(positionSpy.takeLast().at(0).toLongLong(),
+ floor<milliseconds>(microseconds{ duration }).count());
+
QVERIFY(d.position() - (duration / 1000) < 20);
duration += buffer.duration();
sampleCount += buffer.sampleCount();
- if (sampleCount < 44094) {
+ if (sampleCount < testFileSampleCount) {
QTRY_COMPARE(d.bufferAvailable(), true);
}
}
// Make sure the duration is roughly correct (+/- 20ms)
- QCOMPARE(sampleCount, 44094);
+ QCOMPARE(sampleCount, testFileSampleCount);
QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
- QTRY_COMPARE(finishedSpy.count(), 1);
+ QTRY_COMPARE(finishedSpy.size(), 1);
QVERIFY(!d.bufferAvailable());
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
d.stop();
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
- QTRY_COMPARE(durationSpy.count(), 2);
+ QTRY_COMPARE(durationSpy.size(), 2);
QCOMPARE(d.duration(), qint64(-1));
readySpy.clear();
bufferChangedSpy.clear();
- stateSpy.clear();
+ isDecodingSpy.clear();
durationSpy.clear();
finishedSpy.clear();
positionSpy.clear();
+#ifdef Q_OS_ANDROID
+ QSKIP("Setting a desired audio format is not yet supported on Android", QTest::SkipSingle);
+#endif
// Now try changing formats
QAudioFormat format;
format.setChannelCount(2);
- format.setSampleSize(8);
format.setSampleRate(8000);
- format.setCodec("audio/pcm");
- format.setSampleType(QAudioFormat::SignedInt);
+ format.setSampleFormat(QAudioFormat::UInt8);
d.setAudioFormat(format);
@@ -541,13 +914,18 @@ void tst_QAudioDecoderBackend::deviceTest()
QVERIFY(d.audioFormat() == format);
d.start();
- QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
- QTRY_VERIFY(!stateSpy.isEmpty());
+ QVERIFY(d.error() == QAudioDecoder::NoError);
+ QTRY_VERIFY(!isDecodingSpy.isEmpty());
QTRY_VERIFY(!readySpy.isEmpty());
QTRY_VERIFY(!bufferChangedSpy.isEmpty());
QVERIFY(d.bufferAvailable());
QTRY_VERIFY(!durationSpy.isEmpty());
- QVERIFY(qAbs(d.duration() - 1000) < 20);
+
+ QVERIFY(qAbs(durationSpy.front().front().value<qint64>() - 1000) < 20);
+ if (finishedSpy.empty())
+ QVERIFY(qAbs(d.duration() - 1000) < 20);
+ else
+ QCOMPARE(d.duration(), -1);
buffer = d.read();
QVERIFY(buffer.isValid());
@@ -560,12 +938,28 @@ void tst_QAudioDecoderBackend::deviceTest()
QVERIFY(errorSpy.isEmpty());
d.stop();
- QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
+ QTRY_VERIFY(!d.isDecoding());
QVERIFY(!d.bufferAvailable());
- QTRY_COMPARE(durationSpy.count(), 2);
+ QTRY_COMPARE(durationSpy.size(), 2);
QCOMPARE(d.duration(), qint64(-1));
}
+void tst_QAudioDecoderBackend::play_emitsFormatError_whenMediaHasNoAudioTrack()
+{
+ QSKIP_GSTREAMER("QTBUG-124206: gstreamer does not emit errors");
+
+ QAudioDecoder decoder;
+
+ QSignalSpy errors{ &decoder, qOverload<QAudioDecoder::Error>(&QAudioDecoder::error) };
+
+ decoder.setSource(testFileUrl(TEST_NO_AUDIO_TRACK));
+ decoder.start();
+
+ QTRY_VERIFY(!errors.empty());
+
+ QCOMPARE_EQ(decoder.error(), QAudioDecoder::Error::FormatError);
+}
+
QTEST_MAIN(tst_QAudioDecoderBackend)
#include "tst_qaudiodecoderbackend.moc"
diff --git a/tests/auto/integration/qaudiodeviceinfo/BLACKLIST b/tests/auto/integration/qaudiodevice/BLACKLIST
index 40dc63a09..40dc63a09 100644
--- a/tests/auto/integration/qaudiodeviceinfo/BLACKLIST
+++ b/tests/auto/integration/qaudiodevice/BLACKLIST
diff --git a/tests/auto/integration/qaudiodevice/CMakeLists.txt b/tests/auto/integration/qaudiodevice/CMakeLists.txt
new file mode 100644
index 000000000..93f0d49dd
--- /dev/null
+++ b/tests/auto/integration/qaudiodevice/CMakeLists.txt
@@ -0,0 +1,16 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qaudiodevice.pro.
+
+#####################################################################
+## tst_qaudiodevice Test:
+#####################################################################
+
+qt_internal_add_test(tst_qaudiodevice
+ SOURCES
+ tst_qaudiodevice.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+)
diff --git a/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp
new file mode 100644
index 000000000..cd686bd08
--- /dev/null
+++ b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp
@@ -0,0 +1,164 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+
+#include <QtTest/QtTest>
+#include <QtCore/qlocale.h>
+#include <qaudiodevice.h>
+
+#include <QStringList>
+#include <QList>
+#include <QMediaDevices>
+
+class tst_QAudioDevice : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QAudioDevice(QObject* parent=nullptr) : QObject(parent) {}
+
+private slots:
+ void initTestCase();
+ void checkAvailableDefaultInput();
+ void checkAvailableDefaultOutput();
+ void channels();
+ void sampleFormat();
+ void sampleRates();
+ void isFormatSupported();
+ void preferred();
+ void assignOperator();
+ void id();
+ void defaultConstructor();
+ void equalityOperator();
+
+private:
+ std::unique_ptr<QAudioDevice> device;
+};
+
+void tst_QAudioDevice::initTestCase()
+{
+ // Only perform tests if audio output device exists!
+ QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
+ if (devices.size() == 0) {
+ QSKIP("NOTE: no audio output device found, no tests will be performed");
+ } else {
+ device = std::make_unique<QAudioDevice>(devices.at(0));
+ }
+}
+
+void tst_QAudioDevice::checkAvailableDefaultInput()
+{
+ // Only perform tests if audio input device exists!
+ QList<QAudioDevice> devices = QMediaDevices::audioInputs();
+ if (devices.size() > 0) {
+ auto defaultInput = QMediaDevices::defaultAudioInput();
+ QVERIFY(!defaultInput.isNull());
+ QCOMPARE(std::count(devices.begin(), devices.end(), defaultInput), 1);
+ }
+}
+
+void tst_QAudioDevice::checkAvailableDefaultOutput()
+{
+ // Only perform tests if audio input device exists!
+ QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
+ if (devices.size() > 0) {
+ auto defaultOutput = QMediaDevices::defaultAudioOutput();
+ QVERIFY(!defaultOutput.isNull());
+ QCOMPARE(std::count(devices.begin(), devices.end(), defaultOutput), 1);
+ }
+}
+
+void tst_QAudioDevice::channels()
+{
+ QVERIFY(device->minimumChannelCount() > 0);
+ QVERIFY(device->maximumChannelCount() >= device->minimumChannelCount());
+}
+
+void tst_QAudioDevice::sampleFormat()
+{
+ QList<QAudioFormat::SampleFormat> avail = device->supportedSampleFormats();
+ QVERIFY(avail.size() > 0);
+}
+
+void tst_QAudioDevice::sampleRates()
+{
+ QVERIFY(device->minimumSampleRate() > 0);
+ QVERIFY(device->maximumSampleRate() >= device->minimumSampleRate());
+}
+
+void tst_QAudioDevice::isFormatSupported()
+{
+ QAudioFormat format;
+ format.setSampleRate(44100);
+ format.setChannelCount(2);
+ format.setSampleFormat(QAudioFormat::Int16);
+
+ // Should always be true for these format
+ QVERIFY(device->isFormatSupported(format));
+}
+
+void tst_QAudioDevice::preferred()
+{
+ QAudioFormat format = device->preferredFormat();
+ QVERIFY(format.isValid());
+ QVERIFY(device->isFormatSupported(format));
+}
+
+// QAudioDevice's assignOperator method
+void tst_QAudioDevice::assignOperator()
+{
+ QAudioDevice dev;
+ QVERIFY(dev.id().isNull());
+ QVERIFY(dev.isNull() == true);
+
+ QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
+ QVERIFY(devices.size() > 0);
+ QAudioDevice dev1(devices.at(0));
+ dev = dev1;
+ QVERIFY(dev.isNull() == false);
+ QVERIFY(dev.id() == dev1.id());
+}
+
+void tst_QAudioDevice::id()
+{
+ QVERIFY(!device->id().isNull());
+ QVERIFY(device->id() == QMediaDevices::audioOutputs().at(0).id());
+}
+
+// QAudioDevice's defaultConstructor method
+void tst_QAudioDevice::defaultConstructor()
+{
+ QAudioDevice dev;
+ QVERIFY(dev.isNull() == true);
+ QVERIFY(dev.id().isNull());
+}
+
+void tst_QAudioDevice::equalityOperator()
+{
+ // Get some default device infos
+ QAudioDevice dev1;
+ QAudioDevice dev2;
+
+ QVERIFY(dev1 == dev2);
+ QVERIFY(!(dev1 != dev2));
+
+ // Make sure each available device is not equal to null
+ const auto infos = QMediaDevices::audioOutputs();
+ for (const QAudioDevice &info : infos) {
+ QVERIFY(dev1 != info);
+ QVERIFY(!(dev1 == info));
+
+ dev2 = info;
+
+ QVERIFY(dev2 == info);
+ QVERIFY(!(dev2 != info));
+
+ QVERIFY(dev1 != dev2);
+ QVERIFY(!(dev1 == dev2));
+ }
+
+ // XXX Perhaps each available device should not be equal to any other
+}
+
+QTEST_MAIN(tst_QAudioDevice)
+
+#include "tst_qaudiodevice.moc"
diff --git a/tests/auto/integration/qaudiodeviceinfo/qaudiodeviceinfo.pro b/tests/auto/integration/qaudiodeviceinfo/qaudiodeviceinfo.pro
deleted file mode 100644
index 3eb0905c7..000000000
--- a/tests/auto/integration/qaudiodeviceinfo/qaudiodeviceinfo.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-TARGET = tst_qaudiodeviceinfo
-
-QT += core multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-SOURCES += tst_qaudiodeviceinfo.cpp
-
diff --git a/tests/auto/integration/qaudiodeviceinfo/tst_qaudiodeviceinfo.cpp b/tests/auto/integration/qaudiodeviceinfo/tst_qaudiodeviceinfo.cpp
deleted file mode 100644
index c946c0894..000000000
--- a/tests/auto/integration/qaudiodeviceinfo/tst_qaudiodeviceinfo.cpp
+++ /dev/null
@@ -1,248 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#include <QtTest/QtTest>
-#include <QtCore/qlocale.h>
-#include <qaudiodeviceinfo.h>
-
-#include <QStringList>
-#include <QList>
-
-//TESTED_COMPONENT=src/multimedia
-
-class tst_QAudioDeviceInfo : public QObject
-{
- Q_OBJECT
-public:
- tst_QAudioDeviceInfo(QObject* parent=0) : QObject(parent) {}
-
-private slots:
- void initTestCase();
- void checkAvailableDefaultInput();
- void checkAvailableDefaultOutput();
- void codecs();
- void channels();
- void sampleSizes();
- void byteOrders();
- void sampleTypes();
- void sampleRates();
- void isFormatSupported();
- void preferred();
- void nearest();
- void supportedChannelCounts();
- void supportedSampleRates();
- void assignOperator();
- void deviceName();
- void defaultConstructor();
- void equalityOperator();
-
-private:
- QAudioDeviceInfo* device;
-};
-
-void tst_QAudioDeviceInfo::initTestCase()
-{
- // Only perform tests if audio output device exists!
- QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
- if (devices.size() == 0) {
- QSKIP("NOTE: no audio output device found, no tests will be performed");
- } else {
- device = new QAudioDeviceInfo(devices.at(0));
- }
-}
-
-void tst_QAudioDeviceInfo::checkAvailableDefaultInput()
-{
- // Only perform tests if audio input device exists!
- QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
- if (devices.size() > 0) {
- QVERIFY(!QAudioDeviceInfo::defaultInputDevice().isNull());
- }
-}
-
-void tst_QAudioDeviceInfo::checkAvailableDefaultOutput()
-{
- QVERIFY(!QAudioDeviceInfo::defaultOutputDevice().isNull());
-}
-
-void tst_QAudioDeviceInfo::codecs()
-{
- QStringList avail = device->supportedCodecs();
- QVERIFY(avail.size() > 0);
-}
-
-void tst_QAudioDeviceInfo::channels()
-{
- QList<int> avail = device->supportedChannelCounts();
- QVERIFY(avail.size() > 0);
-}
-
-void tst_QAudioDeviceInfo::sampleSizes()
-{
- QList<int> avail = device->supportedSampleSizes();
- QVERIFY(avail.size() > 0);
-}
-
-void tst_QAudioDeviceInfo::byteOrders()
-{
- QList<QAudioFormat::Endian> avail = device->supportedByteOrders();
- QVERIFY(avail.size() > 0);
-}
-
-void tst_QAudioDeviceInfo::sampleTypes()
-{
- QList<QAudioFormat::SampleType> avail = device->supportedSampleTypes();
- QVERIFY(avail.size() > 0);
-}
-
-void tst_QAudioDeviceInfo::sampleRates()
-{
- QList<int> avail = device->supportedSampleRates();
- QVERIFY(avail.size() > 0);
-}
-
-void tst_QAudioDeviceInfo::isFormatSupported()
-{
- QAudioFormat format;
- format.setSampleRate(44100);
- format.setChannelCount(2);
- format.setSampleType(QAudioFormat::SignedInt);
- format.setByteOrder(QAudioFormat::LittleEndian);
- format.setSampleSize(16);
- format.setCodec("audio/pcm");
-
- // Should always be true for these format
- QVERIFY(device->isFormatSupported(format));
-}
-
-void tst_QAudioDeviceInfo::preferred()
-{
- QAudioFormat format = device->preferredFormat();
- QVERIFY(format.isValid());
- QVERIFY(device->isFormatSupported(format));
- QVERIFY(device->nearestFormat(format) == format);
-}
-
-// Returns closest QAudioFormat to settings that system audio supports.
-void tst_QAudioDeviceInfo::nearest()
-{
- /*
- QAudioFormat format1, format2;
- format1.setSampleRate(8000);
- format2 = device->nearestFormat(format1);
- QVERIFY(format2.sampleRate() == 44100);
- */
- QAudioFormat format;
- format.setSampleRate(44100);
- format.setChannelCount(2);
- format.setSampleType(QAudioFormat::SignedInt);
- format.setByteOrder(QAudioFormat::LittleEndian);
- format.setSampleSize(16);
- format.setCodec("audio/pcm");
-
- QAudioFormat format2 = device->nearestFormat(format);
-
- // This is definitely dependent on platform support (but isFormatSupported tests that above)
- QVERIFY(format2.sampleRate() == 44100);
-}
-
-// Returns a list of supported channel counts.
-void tst_QAudioDeviceInfo::supportedChannelCounts()
-{
- QList<int> avail = device->supportedChannelCounts();
- QVERIFY(avail.size() > 0);
-}
-
-// Returns a list of supported sample rates.
-void tst_QAudioDeviceInfo::supportedSampleRates()
-{
- QList<int> avail = device->supportedSampleRates();
- QVERIFY(avail.size() > 0);
-}
-
-// QAudioDeviceInfo's assignOperator method
-void tst_QAudioDeviceInfo::assignOperator()
-{
- QAudioDeviceInfo dev;
- QVERIFY(dev.deviceName().isNull());
- QVERIFY(dev.isNull() == true);
-
- QList<QAudioDeviceInfo> devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
- QVERIFY(devices.size() > 0);
- QAudioDeviceInfo dev1(devices.at(0));
- dev = dev1;
- QVERIFY(dev.isNull() == false);
- QVERIFY(dev.deviceName() == dev1.deviceName());
-}
-
-// Returns human readable name of audio device
-void tst_QAudioDeviceInfo::deviceName()
-{
- QVERIFY(!device->deviceName().isNull());
- QVERIFY(device->deviceName() == QAudioDeviceInfo::availableDevices(QAudio::AudioOutput).at(0).deviceName());
-}
-
-// QAudioDeviceInfo's defaultConstructor method
-void tst_QAudioDeviceInfo::defaultConstructor()
-{
- QAudioDeviceInfo dev;
- QVERIFY(dev.isNull() == true);
- QVERIFY(dev.deviceName().isNull());
-}
-
-void tst_QAudioDeviceInfo::equalityOperator()
-{
- // Get some default device infos
- QAudioDeviceInfo dev1;
- QAudioDeviceInfo dev2;
-
- QVERIFY(dev1 == dev2);
- QVERIFY(!(dev1 != dev2));
-
- // Make sure each available device is not equal to null
- const auto infos = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
- for (const QAudioDeviceInfo info : infos) {
- QVERIFY(dev1 != info);
- QVERIFY(!(dev1 == info));
-
- dev2 = info;
-
- QVERIFY(dev2 == info);
- QVERIFY(!(dev2 != info));
-
- QVERIFY(dev1 != dev2);
- QVERIFY(!(dev1 == dev2));
- }
-
- // XXX Perhaps each available device should not be equal to any other
-}
-
-QTEST_MAIN(tst_QAudioDeviceInfo)
-
-#include "tst_qaudiodeviceinfo.moc"
diff --git a/tests/auto/integration/qaudioinput/BLACKLIST b/tests/auto/integration/qaudioinput/BLACKLIST
deleted file mode 100644
index b7b86283b..000000000
--- a/tests/auto/integration/qaudioinput/BLACKLIST
+++ /dev/null
@@ -1,7 +0,0 @@
-#QTBUG-49736
-[pushSuspendResume]
-linux
-[pull]
-linux
-[pullSuspendResume]
-linux
diff --git a/tests/auto/integration/qaudioinput/qaudioinput.pro b/tests/auto/integration/qaudioinput/qaudioinput.pro
deleted file mode 100644
index 31de98eb0..000000000
--- a/tests/auto/integration/qaudioinput/qaudioinput.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-TARGET = tst_qaudioinput
-
-QT += core multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-HEADERS += wavheader.h
-SOURCES += wavheader.cpp tst_qaudioinput.cpp
diff --git a/tests/auto/integration/qaudioinput/tst_qaudioinput.cpp b/tests/auto/integration/qaudioinput/tst_qaudioinput.cpp
deleted file mode 100644
index bcc50f78a..000000000
--- a/tests/auto/integration/qaudioinput/tst_qaudioinput.cpp
+++ /dev/null
@@ -1,901 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtTest/QtTest>
-#include <QtCore/qlocale.h>
-#include <QtCore/QTemporaryDir>
-#include <QtCore/QSharedPointer>
-#include <QtCore/QScopedPointer>
-
-#include <qaudioinput.h>
-#include <qaudiodeviceinfo.h>
-#include <qaudioformat.h>
-#include <qaudio.h>
-
-#include "wavheader.h"
-
-//TESTED_COMPONENT=src/multimedia
-
-#define AUDIO_BUFFER 192000
-#define RANGE_ERR 0.5
-
-template<typename T> inline bool qTolerantCompare(T value, T expected)
-{
- return qAbs(value - expected) < (RANGE_ERR * expected);
-}
-
-#ifndef QTRY_VERIFY2
-#define QTRY_VERIFY2(__expr,__msg) \
- do { \
- const int __step = 50; \
- const int __timeout = 5000; \
- if (!(__expr)) { \
- QTest::qWait(0); \
- } \
- for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \
- QTest::qWait(__step); \
- } \
- QVERIFY2(__expr,__msg); \
- } while(0)
-#endif
-
-class tst_QAudioInput : public QObject
-{
- Q_OBJECT
-public:
- tst_QAudioInput(QObject* parent=0) : QObject(parent) {}
-
-private slots:
- void initTestCase();
-
- void format();
- void invalidFormat_data();
- void invalidFormat();
-
- void bufferSize();
- void notifyInterval();
- void disableNotifyInterval();
-
- void stopWhileStopped();
- void suspendWhileStopped();
- void resumeWhileStopped();
-
- void pull_data(){generate_audiofile_testrows();}
- void pull();
-
- void pullSuspendResume_data(){generate_audiofile_testrows();}
- void pullSuspendResume();
-
- void push_data(){generate_audiofile_testrows();}
- void push();
-
- void pushSuspendResume_data(){generate_audiofile_testrows();}
- void pushSuspendResume();
-
- void reset_data(){generate_audiofile_testrows();}
- void reset();
-
- void volume_data(){generate_audiofile_testrows();}
- void volume();
-
-private:
- typedef QSharedPointer<QFile> FilePtr;
-
- QString formatToFileName(const QAudioFormat &format);
-
- void generate_audiofile_testrows();
-
- QAudioDeviceInfo audioDevice;
- QList<QAudioFormat> testFormats;
- QList<FilePtr> audioFiles;
- QScopedPointer<QTemporaryDir> m_temporaryDir;
-
- QScopedPointer<QByteArray> m_byteArray;
- QScopedPointer<QBuffer> m_buffer;
-
- bool m_inCISystem;
-};
-
-void tst_QAudioInput::generate_audiofile_testrows()
-{
- QTest::addColumn<FilePtr>("audioFile");
- QTest::addColumn<QAudioFormat>("audioFormat");
-
- for (int i=0; i<audioFiles.count(); i++) {
- QTest::newRow(QString("Audio File %1").arg(i).toLocal8Bit().constData())
- << audioFiles.at(i) << testFormats.at(i);
-
- // Only run first format in CI system to reduce test times
- if (m_inCISystem)
- break;
- }
-}
-
-QString tst_QAudioInput::formatToFileName(const QAudioFormat &format)
-{
- const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
- ? QString("LE") : QString("BE");
-
- const QString formatSigned = (format.sampleType() == QAudioFormat::SignedInt)
- ? QString("signed") : QString("unsigned");
-
- return QString("%1_%2_%3_%4_%5")
- .arg(format.sampleRate())
- .arg(format.sampleSize())
- .arg(formatSigned)
- .arg(formatEndian)
- .arg(format.channelCount());
-}
-
-void tst_QAudioInput::initTestCase()
-{
- qRegisterMetaType<QAudioFormat>();
-
- // Only perform tests if audio output device exists
- const QList<QAudioDeviceInfo> devices =
- QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
-
- if (devices.size() <= 0)
- QSKIP("No audio backend");
-
- audioDevice = QAudioDeviceInfo::defaultInputDevice();
-
-
- QAudioFormat format;
-
- format.setCodec("audio/pcm");
-
- if (audioDevice.isFormatSupported(audioDevice.preferredFormat()))
- testFormats.append(audioDevice.preferredFormat());
-
- // PCM 8000 mono S8
- format.setSampleRate(8000);
- format.setSampleSize(8);
- format.setSampleType(QAudioFormat::SignedInt);
- format.setByteOrder(QAudioFormat::LittleEndian);
- format.setChannelCount(1);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 11025 mono S16LE
- format.setSampleRate(11025);
- format.setSampleSize(16);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 22050 mono S16LE
- format.setSampleRate(22050);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 22050 stereo S16LE
- format.setChannelCount(2);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 44100 stereo S16LE
- format.setSampleRate(44100);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 48000 stereo S16LE
- format.setSampleRate(48000);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- QVERIFY(testFormats.size());
-
- const QChar slash = QLatin1Char('/');
- QString temporaryPattern = QDir::tempPath();
- if (!temporaryPattern.endsWith(slash))
- temporaryPattern += slash;
- temporaryPattern += "tst_qaudioinputXXXXXX";
- m_temporaryDir.reset(new QTemporaryDir(temporaryPattern));
- m_temporaryDir->setAutoRemove(true);
- QVERIFY(m_temporaryDir->isValid());
-
- const QString temporaryAudioPath = m_temporaryDir->path() + slash;
- for (const QAudioFormat &format : qAsConst(testFormats)) {
- const QString fileName = temporaryAudioPath + formatToFileName(format) + QStringLiteral(".wav");
- audioFiles.append(FilePtr::create(fileName));
- }
- qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10);
-}
-
-void tst_QAudioInput::format()
-{
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QAudioFormat requested = audioDevice.preferredFormat();
- QAudioFormat actual = audioInput.format();
-
- QVERIFY2((requested.channelCount() == actual.channelCount()),
- QString("channels: requested=%1, actual=%2").arg(requested.channelCount()).arg(actual.channelCount()).toLocal8Bit().constData());
- QVERIFY2((requested.sampleRate() == actual.sampleRate()),
- QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toLocal8Bit().constData());
- QVERIFY2((requested.sampleSize() == actual.sampleSize()),
- QString("sampleSize: requested=%1, actual=%2").arg(requested.sampleSize()).arg(actual.sampleSize()).toLocal8Bit().constData());
- QVERIFY2((requested.codec() == actual.codec()),
- QString("codec: requested=%1, actual=%2").arg(requested.codec()).arg(actual.codec()).toLocal8Bit().constData());
- QVERIFY2((requested.byteOrder() == actual.byteOrder()),
- QString("byteOrder: requested=%1, actual=%2").arg(requested.byteOrder()).arg(actual.byteOrder()).toLocal8Bit().constData());
- QVERIFY2((requested.sampleType() == actual.sampleType()),
- QString("sampleType: requested=%1, actual=%2").arg(requested.sampleType()).arg(actual.sampleType()).toLocal8Bit().constData());
-}
-
-void tst_QAudioInput::invalidFormat_data()
-{
- QTest::addColumn<QAudioFormat>("invalidFormat");
-
- QAudioFormat format;
-
- QTest::newRow("Null Format")
- << format;
-
- format = audioDevice.preferredFormat();
- format.setChannelCount(0);
- QTest::newRow("Channel count 0")
- << format;
-
- format = audioDevice.preferredFormat();
- format.setSampleRate(0);
- QTest::newRow("Sample rate 0")
- << format;
-
- format = audioDevice.preferredFormat();
- format.setSampleSize(0);
- QTest::newRow("Sample size 0")
- << format;
-}
-
-void tst_QAudioInput::invalidFormat()
-{
- QFETCH(QAudioFormat, invalidFormat);
-
- QVERIFY2(!audioDevice.isFormatSupported(invalidFormat),
- "isFormatSupported() is returning true on an invalid format");
-
- QAudioInput audioInput(invalidFormat, this);
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- audioInput.start();
-
- // Check that error is raised
- QTRY_VERIFY2((audioInput.error() == QAudio::OpenError),"error() was not set to QAudio::OpenError after start()");
-}
-
-void tst_QAudioInput::bufferSize()
-{
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
-
- audioInput.setBufferSize(512);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize(512)");
- QVERIFY2((audioInput.bufferSize() == 512),
- QString("bufferSize: requested=512, actual=%2").arg(audioInput.bufferSize()).toLocal8Bit().constData());
-
- audioInput.setBufferSize(4096);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize(4096)");
- QVERIFY2((audioInput.bufferSize() == 4096),
- QString("bufferSize: requested=4096, actual=%2").arg(audioInput.bufferSize()).toLocal8Bit().constData());
-
- audioInput.setBufferSize(8192);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize(8192)");
- QVERIFY2((audioInput.bufferSize() == 8192),
- QString("bufferSize: requested=8192, actual=%2").arg(audioInput.bufferSize()).toLocal8Bit().constData());
-}
-
-void tst_QAudioInput::notifyInterval()
-{
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
-
- audioInput.setNotifyInterval(50);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(50)");
- QVERIFY2((audioInput.notifyInterval() == 50),
- QString("notifyInterval: requested=50, actual=%2").arg(audioInput.notifyInterval()).toLocal8Bit().constData());
-
- audioInput.setNotifyInterval(100);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(100)");
- QVERIFY2((audioInput.notifyInterval() == 100),
- QString("notifyInterval: requested=100, actual=%2").arg(audioInput.notifyInterval()).toLocal8Bit().constData());
-
- audioInput.setNotifyInterval(250);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(250)");
- QVERIFY2((audioInput.notifyInterval() == 250),
- QString("notifyInterval: requested=250, actual=%2").arg(audioInput.notifyInterval()).toLocal8Bit().constData());
-
- audioInput.setNotifyInterval(1000);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(1000)");
- QVERIFY2((audioInput.notifyInterval() == 1000),
- QString("notifyInterval: requested=1000, actual=%2").arg(audioInput.notifyInterval()).toLocal8Bit().constData());
-}
-
-void tst_QAudioInput::disableNotifyInterval()
-{
- // Sets an invalid notification interval (QAudioInput::setNotifyInterval(0))
- // Checks that
- // - No error is raised (QAudioInput::error() returns QAudio::NoError)
- // - if <= 0, set to zero and disable notify signal
-
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
-
- audioInput.setNotifyInterval(0);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(0)");
- QVERIFY2((audioInput.notifyInterval() == 0),
- "notifyInterval() is not zero after setNotifyInterval(0)");
-
- audioInput.setNotifyInterval(-1);
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(-1)");
- QVERIFY2((audioInput.notifyInterval() == 0),
- "notifyInterval() is not zero after setNotifyInterval(-1)");
-
- //start and run to check if notify() is emitted
- if (audioFiles.size() > 0) {
- QAudioInput audioInputCheck(testFormats.at(0), this);
- audioInputCheck.setNotifyInterval(0);
- QSignalSpy notifySignal(&audioInputCheck, SIGNAL(notify()));
- QFile *audioFile = audioFiles.at(0).data();
- audioFile->open(QIODevice::WriteOnly);
- audioInputCheck.start(audioFile);
- QTest::qWait(3000); // 3 seconds should be plenty
- audioInputCheck.stop();
- QVERIFY2((notifySignal.count() == 0),
- QString("didn't disable notify interval: shouldn't have got any but got %1").arg(notifySignal.count()).toLocal8Bit().constData());
- audioFile->close();
- }
-}
-
-void tst_QAudioInput::stopWhileStopped()
-{
- // Calls QAudioInput::stop() when object is already in StoppedState
- // Checks that
- // - No state change occurs
- // - No error is raised (QAudioInput::error() returns QAudio::NoError)
-
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
- audioInput.stop();
-
- // Check that no state transition occurred
- QVERIFY2((stateSignal.count() == 0), "stop() while stopped is emitting a signal and it shouldn't");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
-}
-
-void tst_QAudioInput::suspendWhileStopped()
-{
- // Calls QAudioInput::suspend() when object is already in StoppedState
- // Checks that
- // - No state change occurs
- // - No error is raised (QAudioInput::error() returns QAudio::NoError)
-
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
- audioInput.suspend();
-
- // Check that no state transition occurred
- QVERIFY2((stateSignal.count() == 0), "stop() while suspended is emitting a signal and it shouldn't");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
-}
-
-void tst_QAudioInput::resumeWhileStopped()
-{
- // Calls QAudioInput::resume() when object is already in StoppedState
- // Checks that
- // - No state change occurs
- // - No error is raised (QAudioInput::error() returns QAudio::NoError)
-
- QAudioInput audioInput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
- audioInput.resume();
-
- // Check that no state transition occurred
- QVERIFY2((stateSignal.count() == 0), "resume() while stopped is emitting a signal and it shouldn't");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after resume()");
-}
-
-void tst_QAudioInput::pull()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioInput audioInput(audioFormat, this);
-
- audioInput.setNotifyInterval(100);
-
- QSignalSpy notifySignal(&audioInput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::WriteOnly);
- WavHeader wavHeader(audioFormat);
- QVERIFY(wavHeader.write(*audioFile));
-
- audioInput.start(audioFile.data());
-
- // Check that QAudioInput immediately transitions to ActiveState or IdleState
- QTRY_VERIFY2((stateSignal.count() > 0),"didn't emit signals on start()");
- QVERIFY2((audioInput.state() == QAudio::ActiveState || audioInput.state() == QAudio::IdleState),
- "didn't transition to ActiveState or IdleState after start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioInput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
-
- // Allow some recording to happen
- QTest::qWait(3000); // 3 seconds should be plenty
-
- stateSignal.clear();
-
- qint64 processedUs = audioInput.processedUSecs();
-
- audioInput.stop();
- QTest::qWait(40);
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2(qTolerantCompare(processedUs, 3040000LL),
- QString("processedUSecs() doesn't fall in acceptable range, should be 3040000 (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
- QVERIFY2(notifySignal.count() > 0, "not emitting notify() signal");
-
- WavHeader::writeDataLength(*audioFile, audioFile->pos() - WavHeader::headerLength());
- audioFile->close();
-
-}
-
-void tst_QAudioInput::pullSuspendResume()
-{
-#ifdef Q_OS_LINUX
- if (m_inCISystem)
- QSKIP("QTBUG-26504 Fails 20% of time with pulseaudio backend");
-#endif
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioInput audioInput(audioFormat, this);
-
- audioInput.setNotifyInterval(100);
-
- QSignalSpy notifySignal(&audioInput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::WriteOnly);
- WavHeader wavHeader(audioFormat);
- QVERIFY(wavHeader.write(*audioFile));
-
- audioInput.start(audioFile.data());
-
- // Check that QAudioInput immediately transitions to ActiveState or IdleState
- QTRY_VERIFY2((stateSignal.count() > 0),"didn't emit signals on start()");
- QVERIFY2((audioInput.state() == QAudio::ActiveState || audioInput.state() == QAudio::IdleState),
- "didn't transition to ActiveState or IdleState after start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioInput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
-
- // Allow some recording to happen
- QTest::qWait(3000); // 3 seconds should be plenty
-
- QVERIFY2((audioInput.state() == QAudio::ActiveState),
- "didn't transition to ActiveState after some recording");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after some recording");
-
- stateSignal.clear();
-
- audioInput.suspend();
-
- // Give backends running in separate threads a chance to suspend.
- QTest::qWait(100);
-
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::SuspendedState), "didn't transitions to SuspendedState after stop()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- stateSignal.clear();
-
- // Check that only 'elapsed', and not 'processed' increases while suspended
- qint64 elapsedUs = audioInput.elapsedUSecs();
- qint64 processedUs = audioInput.processedUSecs();
- QTest::qWait(1000);
- QVERIFY(audioInput.elapsedUSecs() > elapsedUs);
- QVERIFY(audioInput.processedUSecs() == processedUs);
-
- audioInput.resume();
-
- // Give backends running in separate threads a chance to resume.
- QTest::qWait(100);
-
- // Check that QAudioInput immediately transitions to ActiveState
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
- stateSignal.clear();
-
- processedUs = audioInput.processedUSecs();
-
- audioInput.stop();
- QTest::qWait(40);
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2(qTolerantCompare(processedUs, 3040000LL),
- QString("processedUSecs() doesn't fall in acceptable range, should be 3040000 (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
- QVERIFY2(notifySignal.count() > 0, "not emitting notify() signal");
-
- WavHeader::writeDataLength(*audioFile,audioFile->pos()-WavHeader::headerLength());
- audioFile->close();
-}
-
-void tst_QAudioInput::push()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioInput audioInput(audioFormat, this);
-
- audioInput.setNotifyInterval(100);
-
- QSignalSpy notifySignal(&audioInput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::WriteOnly);
- WavHeader wavHeader(audioFormat);
- QVERIFY(wavHeader.write(*audioFile));
-
- // Set a large buffer to avoid underruns during QTest::qWaits
- audioInput.setBufferSize(audioFormat.bytesForDuration(1000000));
-
- QIODevice* feed = audioInput.start();
-
- // Check that QAudioInput immediately transitions to IdleState
- QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit IdleState signal on start()");
- QVERIFY2((audioInput.state() == QAudio::IdleState),
- "didn't transition to IdleState after start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioInput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
-
- qint64 totalBytesRead = 0;
- bool firstBuffer = true;
- QByteArray buffer(AUDIO_BUFFER, 0);
- qint64 len = (audioFormat.sampleRate()*audioFormat.channelCount()*(audioFormat.sampleSize()/8)*2); // 2 seconds
- while (totalBytesRead < len) {
- QTRY_VERIFY_WITH_TIMEOUT(audioInput.bytesReady() >= audioInput.periodSize(), 10000);
- qint64 bytesRead = feed->read(buffer.data(), audioInput.periodSize());
- audioFile->write(buffer.constData(),bytesRead);
- totalBytesRead+=bytesRead;
- if (firstBuffer && bytesRead) {
- // Check for transition to ActiveState when data is provided
- QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit ActiveState signal on data");
- QVERIFY2((audioInput.state() == QAudio::ActiveState),
- "didn't transition to ActiveState after data");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- firstBuffer = false;
- }
- }
-
- QTest::qWait(1000);
-
- stateSignal.clear();
-
- qint64 processedUs = audioInput.processedUSecs();
-
- audioInput.stop();
- QTest::qWait(40);
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2(qTolerantCompare(processedUs, 2040000LL),
- QString("processedUSecs() doesn't fall in acceptable range, should be 2040000 (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
- QVERIFY2(notifySignal.count() > 0, "not emitting notify() signal");
-
- WavHeader::writeDataLength(*audioFile,audioFile->pos()-WavHeader::headerLength());
- audioFile->close();
-}
-
-void tst_QAudioInput::pushSuspendResume()
-{
-#ifdef Q_OS_LINUX
- if (m_inCISystem)
- QSKIP("QTBUG-26504 Fails 20% of time with pulseaudio backend");
-#endif
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
- QAudioInput audioInput(audioFormat, this);
-
- audioInput.setNotifyInterval(100);
- audioInput.setBufferSize(audioFormat.bytesForDuration(1000000));
-
- QSignalSpy notifySignal(&audioInput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::WriteOnly);
- WavHeader wavHeader(audioFormat);
- QVERIFY(wavHeader.write(*audioFile));
-
- QIODevice* feed = audioInput.start();
-
- // Check that QAudioInput immediately transitions to IdleState
- QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit IdleState signal on start()");
- QVERIFY2((audioInput.state() == QAudio::IdleState),
- "didn't transition to IdleState after start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioInput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QTRY_VERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
-
- qint64 totalBytesRead = 0;
- bool firstBuffer = true;
- QByteArray buffer(AUDIO_BUFFER, 0);
- qint64 len = (audioFormat.sampleRate()*audioFormat.channelCount()*(audioFormat.sampleSize()/8)); // 1 seconds
- while (totalBytesRead < len) {
- QTRY_VERIFY_WITH_TIMEOUT(audioInput.bytesReady() >= audioInput.periodSize(), 10000);
- qint64 bytesRead = feed->read(buffer.data(), audioInput.periodSize());
- audioFile->write(buffer.constData(),bytesRead);
- totalBytesRead+=bytesRead;
- if (firstBuffer && bytesRead) {
- // Check for transition to ActiveState when data is provided
- QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit ActiveState signal on data");
- QVERIFY2((audioInput.state() == QAudio::ActiveState),
- "didn't transition to ActiveState after data");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- firstBuffer = false;
- }
- }
- stateSignal.clear();
-
- audioInput.suspend();
-
- // Give backends running in separate threads a chance to suspend
- QTest::qWait(100);
-
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::SuspendedState), "didn't transitions to SuspendedState after stop()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- stateSignal.clear();
-
- // Check that only 'elapsed', and not 'processed' increases while suspended
- qint64 elapsedUs = audioInput.elapsedUSecs();
- qint64 processedUs = audioInput.processedUSecs();
- QTest::qWait(1000);
- QVERIFY(audioInput.elapsedUSecs() > elapsedUs);
- QVERIFY(audioInput.processedUSecs() == processedUs);
-
- // Drain any data, in case we run out of space when resuming
- const int reads = audioInput.bytesReady() / audioInput.periodSize();
- for (int r = 0; r < reads; ++r)
- feed->read(buffer.data(), audioInput.periodSize());
-
- audioInput.resume();
-
- // Check that QAudioInput immediately transitions to Active or IdleState
- QTRY_VERIFY2((stateSignal.count() > 0),"didn't emit signals on resume()");
- QVERIFY2((audioInput.state() == QAudio::ActiveState || audioInput.state() == QAudio::IdleState),
- "didn't transition to ActiveState or IdleState after resume()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
- QVERIFY(audioInput.periodSize() > 0);
-
- // Let it play out what is in buffer and go to Idle before continue
- QTest::qWait(1000);
- stateSignal.clear();
-
- // Read another seconds worth
- totalBytesRead = 0;
- firstBuffer = true;
- while (totalBytesRead < len && audioInput.state() != QAudio::StoppedState) {
- QTRY_VERIFY_WITH_TIMEOUT(audioInput.bytesReady() >= audioInput.periodSize(), 10000);
- qint64 bytesRead = feed->read(buffer.data(), audioInput.periodSize());
- audioFile->write(buffer.constData(),bytesRead);
- totalBytesRead+=bytesRead;
- }
- stateSignal.clear();
-
- processedUs = audioInput.processedUSecs();
-
- audioInput.stop();
- QTest::qWait(40);
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2(qTolerantCompare(processedUs, 2040000LL),
- QString("processedUSecs() doesn't fall in acceptable range, should be 2040000 (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
-
- WavHeader::writeDataLength(*audioFile,audioFile->pos()-WavHeader::headerLength());
- audioFile->close();
-}
-
-void tst_QAudioInput::reset()
-{
- QFETCH(QAudioFormat, audioFormat);
-
- // Try both push/pull.. the vagaries of Active vs Idle are tested elsewhere
- {
- QAudioInput audioInput(audioFormat, this);
-
- audioInput.setNotifyInterval(100);
-
- QSignalSpy notifySignal(&audioInput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- QIODevice* device = audioInput.start();
- // Check that QAudioInput immediately transitions to IdleState
- QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit IdleState signal on start()");
- QVERIFY2((audioInput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioInput.periodSize() > 0);
- QTRY_VERIFY2_WITH_TIMEOUT((audioInput.bytesReady() > audioInput.periodSize()), "no bytes available after starting", 10000);
-
- // Trigger a read
- QByteArray data = device->read(audioInput.periodSize());
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- stateSignal.clear();
-
- audioInput.reset();
- QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit StoppedState signal after reset()");
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after reset()");
- QVERIFY2((audioInput.bytesReady() == 0), "buffer not cleared after reset()");
- }
-
- {
- QAudioInput audioInput(audioFormat, this);
- QBuffer buffer;
-
- audioInput.setNotifyInterval(100);
-
- QSignalSpy notifySignal(&audioInput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioInput.start(&buffer);
-
- // Check that QAudioInput immediately transitions to ActiveState
- QTRY_VERIFY2((stateSignal.count() >= 1),"didn't emit state changed signal on start()");
- QTRY_VERIFY2((audioInput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
- QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioInput.periodSize() > 0);
- stateSignal.clear();
-
- audioInput.reset();
- QTRY_VERIFY2((stateSignal.count() >= 1),"didn't emit StoppedState signal after reset()");
- QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after reset()");
- QVERIFY2((audioInput.bytesReady() == 0), "buffer not cleared after reset()");
- }
-}
-
-void tst_QAudioInput::volume()
-{
- QFETCH(QAudioFormat, audioFormat);
-
- const qreal half(0.5f);
- const qreal one(1.0f);
-
- QAudioInput audioInput(audioFormat, this);
-
- qreal volume = audioInput.volume();
- audioInput.setVolume(half);
- QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 5);
- // Wait a while to see if this changes
- QTest::qWait(500);
- QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 5);
-
- audioInput.setVolume(one);
- QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 10);
- // Wait a while to see if this changes
- QTest::qWait(500);
- QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 10);
-
- audioInput.setVolume(half);
- audioInput.start();
- QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 5);
- audioInput.setVolume(one);
- QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 10);
-
- audioInput.setVolume(volume);
-}
-
-QTEST_MAIN(tst_QAudioInput)
-
-#include "tst_qaudioinput.moc"
diff --git a/tests/auto/integration/qaudioinput/wavheader.cpp b/tests/auto/integration/qaudioinput/wavheader.cpp
deleted file mode 100644
index 869d74d5b..000000000
--- a/tests/auto/integration/qaudioinput/wavheader.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtCore/qendian.h>
-#include "wavheader.h"
-
-
-struct chunk
-{
- char id[4];
- quint32 size;
-};
-
-struct RIFFHeader
-{
- chunk descriptor; // "RIFF"
- char type[4]; // "WAVE"
-};
-
-struct WAVEHeader
-{
- chunk descriptor;
- quint16 audioFormat;
- quint16 numChannels;
- quint32 sampleRate;
- quint32 byteRate;
- quint16 blockAlign;
- quint16 bitsPerSample;
-};
-
-struct DATAHeader
-{
- chunk descriptor;
-};
-
-struct CombinedHeader
-{
- RIFFHeader riff;
- WAVEHeader wave;
- DATAHeader data;
-};
-
-static const int HeaderLength = sizeof(CombinedHeader);
-
-
-WavHeader::WavHeader(const QAudioFormat &format, qint64 dataLength)
- : m_format(format)
- , m_dataLength(dataLength)
-{
-
-}
-
-bool WavHeader::read(QIODevice &device)
-{
- bool result = true;
-
- if (!device.isSequential())
- result = device.seek(0);
- // else, assume that current position is the start of the header
-
- if (result) {
- CombinedHeader header;
- result = (device.read(reinterpret_cast<char *>(&header), HeaderLength) == HeaderLength);
- if (result) {
- if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
- || memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
- && memcmp(&header.riff.type, "WAVE", 4) == 0
- && memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
- && header.wave.audioFormat == 1 // PCM
- ) {
- if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
- m_format.setByteOrder(QAudioFormat::LittleEndian);
- else
- m_format.setByteOrder(QAudioFormat::BigEndian);
-
- m_format.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels));
- m_format.setCodec("audio/pcm");
- m_format.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate));
- m_format.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
-
- switch(header.wave.bitsPerSample) {
- case 8:
- m_format.setSampleType(QAudioFormat::UnSignedInt);
- break;
- case 16:
- m_format.setSampleType(QAudioFormat::SignedInt);
- break;
- default:
- result = false;
- }
-
- m_dataLength = device.size() - HeaderLength;
- } else {
- result = false;
- }
- }
- }
-
- return result;
-}
-
-bool WavHeader::write(QIODevice &device)
-{
- CombinedHeader header;
-
- memset(&header, 0, HeaderLength);
-
- // RIFF header
- if (m_format.byteOrder() == QAudioFormat::LittleEndian)
- memcpy(header.riff.descriptor.id,"RIFF",4);
- else
- memcpy(header.riff.descriptor.id,"RIFX",4);
- qToLittleEndian<quint32>(quint32(m_dataLength + HeaderLength - 8),
- reinterpret_cast<unsigned char*>(&header.riff.descriptor.size));
- memcpy(header.riff.type, "WAVE",4);
-
- // WAVE header
- memcpy(header.wave.descriptor.id,"fmt ",4);
- qToLittleEndian<quint32>(quint32(16),
- reinterpret_cast<unsigned char*>(&header.wave.descriptor.size));
- qToLittleEndian<quint16>(quint16(1),
- reinterpret_cast<unsigned char*>(&header.wave.audioFormat));
- qToLittleEndian<quint16>(quint16(m_format.channelCount()),
- reinterpret_cast<unsigned char*>(&header.wave.numChannels));
- qToLittleEndian<quint32>(quint32(m_format.sampleRate()),
- reinterpret_cast<unsigned char*>(&header.wave.sampleRate));
- qToLittleEndian<quint32>(quint32(m_format.sampleRate() * m_format.channelCount() * m_format.sampleSize() / 8),
- reinterpret_cast<unsigned char*>(&header.wave.byteRate));
- qToLittleEndian<quint16>(quint16(m_format.channelCount() * m_format.sampleSize() / 8),
- reinterpret_cast<unsigned char*>(&header.wave.blockAlign));
- qToLittleEndian<quint16>(quint16(m_format.sampleSize()),
- reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample));
-
- // DATA header
- memcpy(header.data.descriptor.id,"data",4);
- qToLittleEndian<quint32>(quint32(m_dataLength),
- reinterpret_cast<unsigned char*>(&header.data.descriptor.size));
-
- return (device.write(reinterpret_cast<const char *>(&header), HeaderLength) == HeaderLength);
-}
-
-const QAudioFormat& WavHeader::format() const
-{
- return m_format;
-}
-
-qint64 WavHeader::dataLength() const
-{
- return m_dataLength;
-}
-
-qint64 WavHeader::headerLength()
-{
- return HeaderLength;
-}
-
-bool WavHeader::writeDataLength(QIODevice &device, qint64 dataLength)
-{
- bool result = false;
- if (!device.isSequential()) {
- device.seek(40);
- unsigned char dataLengthLE[4];
- qToLittleEndian<quint32>(quint32(dataLength), dataLengthLE);
- result = (device.write(reinterpret_cast<const char *>(dataLengthLE), 4) == 4);
- }
- return result;
-}
diff --git a/tests/auto/integration/qaudioinput/wavheader.h b/tests/auto/integration/qaudioinput/wavheader.h
deleted file mode 100644
index b9595cffc..000000000
--- a/tests/auto/integration/qaudioinput/wavheader.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#ifndef WAVHEADER_H
-#define WAVHEADER_H
-
-#include <QtCore/qobject.h>
-#include <QtCore/qfile.h>
-#include <qaudioformat.h>
-
-/**
- * Helper class for parsing WAV file headers.
- *
- * See https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
- */
-class WavHeader
-{
-public:
- WavHeader(const QAudioFormat &format = QAudioFormat(),
- qint64 dataLength = 0);
-
- // Reads WAV header and seeks to start of data
- bool read(QIODevice &device);
-
- // Writes WAV header
- bool write(QIODevice &device);
-
- const QAudioFormat& format() const;
- qint64 dataLength() const;
-
- static qint64 headerLength();
-
- static bool writeDataLength(QIODevice &device, qint64 dataLength);
-
-private:
- QAudioFormat m_format;
- qint64 m_dataLength;
-};
-
-#endif
-
diff --git a/tests/auto/integration/qaudiooutput/BLACKLIST b/tests/auto/integration/qaudiooutput/BLACKLIST
deleted file mode 100644
index 966b48af6..000000000
--- a/tests/auto/integration/qaudiooutput/BLACKLIST
+++ /dev/null
@@ -1 +0,0 @@
-linux ci
diff --git a/tests/auto/integration/qaudiooutput/qaudiooutput.pro b/tests/auto/integration/qaudiooutput/qaudiooutput.pro
deleted file mode 100644
index dfaebe36a..000000000
--- a/tests/auto/integration/qaudiooutput/qaudiooutput.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-TARGET = tst_qaudiooutput
-
-QT += core multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-HEADERS += wavheader.h
-SOURCES += wavheader.cpp tst_qaudiooutput.cpp
diff --git a/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp b/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp
deleted file mode 100644
index a81706ec1..000000000
--- a/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp
+++ /dev/null
@@ -1,988 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-//TESTED_COMPONENT=src/multimedia
-
-#include <QtTest/QtTest>
-#include <QtCore/qlocale.h>
-#include <QtCore/QTemporaryDir>
-#include <QtCore/QSharedPointer>
-#include <QtCore/QScopedPointer>
-
-#include <qaudiooutput.h>
-#include <qaudiodeviceinfo.h>
-#include <qaudioformat.h>
-#include <qaudio.h>
-
-#include "wavheader.h"
-
-#define AUDIO_BUFFER 192000
-
-#ifndef QTRY_VERIFY2
-#define QTRY_VERIFY2(__expr,__msg) \
- do { \
- const int __step = 50; \
- const int __timeout = 5000; \
- if (!(__expr)) { \
- QTest::qWait(0); \
- } \
- for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \
- QTest::qWait(__step); \
- } \
- QVERIFY2(__expr,__msg); \
- } while (0)
-#endif
-
-class tst_QAudioOutput : public QObject
-{
- Q_OBJECT
-public:
- tst_QAudioOutput(QObject* parent=0) : QObject(parent) {}
-
-private slots:
- void initTestCase();
-
- void format();
- void invalidFormat_data();
- void invalidFormat();
-
- void bufferSize_data();
- void bufferSize();
-
- void notifyInterval_data();
- void notifyInterval();
-
- void disableNotifyInterval();
-
- void stopWhileStopped();
- void suspendWhileStopped();
- void resumeWhileStopped();
-
- void pull_data(){generate_audiofile_testrows();}
- void pull();
-
- void pullSuspendResume_data(){generate_audiofile_testrows();}
- void pullSuspendResume();
-
- void push_data(){generate_audiofile_testrows();}
- void push();
-
- void pushSuspendResume_data(){generate_audiofile_testrows();}
- void pushSuspendResume();
-
- void pushUnderrun_data(){generate_audiofile_testrows();}
- void pushUnderrun();
-
- void volume_data();
- void volume();
-
-private:
- typedef QSharedPointer<QFile> FilePtr;
-
- QString formatToFileName(const QAudioFormat &format);
- void createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate = 440);
-
- void generate_audiofile_testrows();
-
- QAudioDeviceInfo audioDevice;
- QList<QAudioFormat> testFormats;
- QList<FilePtr> audioFiles;
- QScopedPointer<QTemporaryDir> m_temporaryDir;
-
- QScopedPointer<QByteArray> m_byteArray;
- QScopedPointer<QBuffer> m_buffer;
-};
-
-QString tst_QAudioOutput::formatToFileName(const QAudioFormat &format)
-{
- const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
- ? QString("LE") : QString("BE");
-
- const QString formatSigned = (format.sampleType() == QAudioFormat::SignedInt)
- ? QString("signed") : QString("unsigned");
-
- return QString("%1_%2_%3_%4_%5")
- .arg(format.sampleRate())
- .arg(format.sampleSize())
- .arg(formatSigned)
- .arg(formatEndian)
- .arg(format.channelCount());
-}
-
-void tst_QAudioOutput::createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate)
-{
- const int channelBytes = format.sampleSize() / 8;
- const int sampleBytes = format.channelCount() * channelBytes;
-
- Q_ASSERT(length % sampleBytes == 0);
- Q_UNUSED(sampleBytes); // suppress warning in release builds
-
- m_byteArray.reset(new QByteArray(length, 0));
- unsigned char *ptr = reinterpret_cast<unsigned char *>(m_byteArray->data());
- int sampleIndex = 0;
-
- while (length) {
- const qreal x = qSin(2 * M_PI * sampleRate * qreal(sampleIndex % format.sampleRate()) / format.sampleRate());
- for (int i=0; i<format.channelCount(); ++i) {
- if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt) {
- const quint8 value = static_cast<quint8>((1.0 + x) / 2 * 255);
- *reinterpret_cast<quint8*>(ptr) = value;
- } else if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::SignedInt) {
- const qint8 value = static_cast<qint8>(x * 127);
- *reinterpret_cast<quint8*>(ptr) = value;
- } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::UnSignedInt) {
- quint16 value = static_cast<quint16>((1.0 + x) / 2 * 65535);
- if (format.byteOrder() == QAudioFormat::LittleEndian)
- qToLittleEndian<quint16>(value, ptr);
- else
- qToBigEndian<quint16>(value, ptr);
- } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt) {
- qint16 value = static_cast<qint16>(x * 32767);
- if (format.byteOrder() == QAudioFormat::LittleEndian)
- qToLittleEndian<qint16>(value, ptr);
- else
- qToBigEndian<qint16>(value, ptr);
- }
-
- ptr += channelBytes;
- length -= channelBytes;
- }
- ++sampleIndex;
- }
-
- m_buffer.reset(new QBuffer(m_byteArray.data(), this));
- Q_ASSERT(m_buffer->open(QIODevice::ReadOnly));
-}
-
-void tst_QAudioOutput::generate_audiofile_testrows()
-{
- QTest::addColumn<FilePtr>("audioFile");
- QTest::addColumn<QAudioFormat>("audioFormat");
-
- for (int i=0; i<audioFiles.count(); i++) {
- QTest::newRow(QString("Audio File %1").arg(i).toLocal8Bit().constData())
- << audioFiles.at(i) << testFormats.at(i);
-
- }
-}
-
-void tst_QAudioOutput::initTestCase()
-{
- qRegisterMetaType<QAudioFormat>();
-
- // Only perform tests if audio output device exists
- const QList<QAudioDeviceInfo> devices =
- QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
-
- if (devices.size() <= 0)
- QSKIP("No audio backend");
-
- audioDevice = QAudioDeviceInfo::defaultOutputDevice();
-
-
- QAudioFormat format;
-
- format.setCodec("audio/pcm");
-
- if (audioDevice.isFormatSupported(audioDevice.preferredFormat()))
- testFormats.append(audioDevice.preferredFormat());
-
- // PCM 8000 mono S8
- format.setSampleRate(8000);
- format.setSampleSize(8);
- format.setSampleType(QAudioFormat::SignedInt);
- format.setByteOrder(QAudioFormat::LittleEndian);
- format.setChannelCount(1);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 11025 mono S16LE
- format.setSampleRate(11025);
- format.setSampleSize(16);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 22050 mono S16LE
- format.setSampleRate(22050);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 22050 stereo S16LE
- format.setChannelCount(2);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 44100 stereo S16LE
- format.setSampleRate(44100);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- // PCM 48000 stereo S16LE
- format.setSampleRate(48000);
- if (audioDevice.isFormatSupported(format))
- testFormats.append(format);
-
- QVERIFY(testFormats.size());
-
- const QChar slash = QLatin1Char('/');
- QString temporaryPattern = QDir::tempPath();
- if (!temporaryPattern.endsWith(slash))
- temporaryPattern += slash;
- temporaryPattern += "tst_qaudiooutputXXXXXX";
- m_temporaryDir.reset(new QTemporaryDir(temporaryPattern));
- m_temporaryDir->setAutoRemove(true);
- QVERIFY(m_temporaryDir->isValid());
-
- const QString temporaryAudioPath = m_temporaryDir->path() + slash;
- for (const QAudioFormat &format : qAsConst(testFormats)) {
- qint64 len = (format.sampleRate()*format.channelCount()*(format.sampleSize()/8)*2); // 2 seconds
- createSineWaveData(format, len);
- // Write generate sine wave data to file
- const QString fileName = temporaryAudioPath + QStringLiteral("generated")
- + formatToFileName(format) + QStringLiteral(".wav");
- FilePtr file(new QFile(fileName));
- QVERIFY2(file->open(QIODevice::WriteOnly), qPrintable(file->errorString()));
- WavHeader wavHeader(format, len);
- wavHeader.write(*file.data());
- file->write(m_byteArray->data(), len);
- file->close();
- audioFiles.append(file);
- }
-}
-
-void tst_QAudioOutput::format()
-{
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QAudioFormat requested = audioDevice.preferredFormat();
- QAudioFormat actual = audioOutput.format();
-
- QVERIFY2((requested.channelCount() == actual.channelCount()),
- QString("channels: requested=%1, actual=%2").arg(requested.channelCount()).arg(actual.channelCount()).toLocal8Bit().constData());
- QVERIFY2((requested.sampleRate() == actual.sampleRate()),
- QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toLocal8Bit().constData());
- QVERIFY2((requested.sampleSize() == actual.sampleSize()),
- QString("sampleSize: requested=%1, actual=%2").arg(requested.sampleSize()).arg(actual.sampleSize()).toLocal8Bit().constData());
- QVERIFY2((requested.codec() == actual.codec()),
- QString("codec: requested=%1, actual=%2").arg(requested.codec()).arg(actual.codec()).toLocal8Bit().constData());
- QVERIFY2((requested.byteOrder() == actual.byteOrder()),
- QString("byteOrder: requested=%1, actual=%2").arg(requested.byteOrder()).arg(actual.byteOrder()).toLocal8Bit().constData());
- QVERIFY2((requested.sampleType() == actual.sampleType()),
- QString("sampleType: requested=%1, actual=%2").arg(requested.sampleType()).arg(actual.sampleType()).toLocal8Bit().constData());
-}
-
-void tst_QAudioOutput::invalidFormat_data()
-{
- QTest::addColumn<QAudioFormat>("invalidFormat");
-
- QAudioFormat format;
-
- QTest::newRow("Null Format")
- << format;
-
- format = audioDevice.preferredFormat();
- format.setChannelCount(0);
- QTest::newRow("Channel count 0")
- << format;
-
- format = audioDevice.preferredFormat();
- format.setSampleRate(0);
- QTest::newRow("Sample rate 0")
- << format;
-
- format = audioDevice.preferredFormat();
- format.setSampleSize(0);
- QTest::newRow("Sample size 0")
- << format;
-}
-
-void tst_QAudioOutput::invalidFormat()
-{
- QFETCH(QAudioFormat, invalidFormat);
-
- QVERIFY2(!audioDevice.isFormatSupported(invalidFormat),
- "isFormatSupported() is returning true on an invalid format");
-
- QAudioOutput audioOutput(invalidFormat, this);
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- audioOutput.start();
- // Check that error is raised
- QTRY_VERIFY2((audioOutput.error() == QAudio::OpenError),"error() was not set to QAudio::OpenError after start()");
-}
-
-void tst_QAudioOutput::bufferSize_data()
-{
- QTest::addColumn<int>("bufferSize");
- QTest::newRow("Buffer size 512") << 512;
- QTest::newRow("Buffer size 4096") << 4096;
- QTest::newRow("Buffer size 8192") << 8192;
-}
-
-void tst_QAudioOutput::bufferSize()
-{
- QFETCH(int, bufferSize);
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() was not set to QAudio::NoError on creation(%1)").arg(bufferSize).toLocal8Bit().constData());
-
- audioOutput.setBufferSize(bufferSize);
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize");
- QVERIFY2((audioOutput.bufferSize() == bufferSize),
- QString("bufferSize: requested=%1, actual=%2").arg(bufferSize).arg(audioOutput.bufferSize()).toLocal8Bit().constData());
-}
-
-void tst_QAudioOutput::notifyInterval_data()
-{
- QTest::addColumn<int>("interval");
- QTest::newRow("Notify interval 50") << 50;
- QTest::newRow("Notify interval 100") << 100;
- QTest::newRow("Notify interval 250") << 250;
- QTest::newRow("Notify interval 1000") << 1000;
-}
-
-void tst_QAudioOutput::notifyInterval()
-{
- QFETCH(int, interval);
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
-
- audioOutput.setNotifyInterval(interval);
- QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() is not QAudio::NoError after setNotifyInterval(%1)").arg(interval).toLocal8Bit().constData());
- QVERIFY2((audioOutput.notifyInterval() == interval),
- QString("notifyInterval: requested=%1, actual=%2").arg(interval).arg(audioOutput.notifyInterval()).toLocal8Bit().constData());
-}
-
-void tst_QAudioOutput::disableNotifyInterval()
-{
- // Sets an invalid notification interval (QAudioOutput::setNotifyInterval(0))
- // Checks that
- // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
- // - if <= 0, set to zero and disable notify signal
-
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
-
- audioOutput.setNotifyInterval(0);
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(0)");
- QVERIFY2((audioOutput.notifyInterval() == 0),
- "notifyInterval() is not zero after setNotifyInterval(0)");
-
- audioOutput.setNotifyInterval(-1);
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(-1)");
- QVERIFY2((audioOutput.notifyInterval() == 0),
- "notifyInterval() is not zero after setNotifyInterval(-1)");
-
- //start and run to check if notify() is emitted
- if (audioFiles.size() > 0) {
- QAudioOutput audioOutputCheck(testFormats.at(0), this);
- audioOutputCheck.setNotifyInterval(0);
- audioOutputCheck.setVolume(0.1f);
-
- QSignalSpy notifySignal(&audioOutputCheck, SIGNAL(notify()));
- QFile *audioFile = audioFiles.at(0).data();
- audioFile->open(QIODevice::ReadOnly);
- audioOutputCheck.start(audioFile);
- QTest::qWait(3000); // 3 seconds should be plenty
- audioOutputCheck.stop();
- QVERIFY2((notifySignal.count() == 0),
- QString("didn't disable notify interval: shouldn't have got any but got %1").arg(notifySignal.count()).toLocal8Bit().constData());
- audioFile->close();
- }
-}
-
-void tst_QAudioOutput::stopWhileStopped()
-{
- // Calls QAudioOutput::stop() when object is already in StoppedState
- // Checks that
- // - No state change occurs
- // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
-
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
- audioOutput.stop();
-
- // Check that no state transition occurred
- QVERIFY2((stateSignal.count() == 0), "stop() while stopped is emitting a signal and it shouldn't");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
-}
-
-void tst_QAudioOutput::suspendWhileStopped()
-{
- // Calls QAudioOutput::suspend() when object is already in StoppedState
- // Checks that
- // - No state change occurs
- // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
-
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
- audioOutput.suspend();
-
- // Check that no state transition occurred
- QVERIFY2((stateSignal.count() == 0), "stop() while suspended is emitting a signal and it shouldn't");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
-}
-
-void tst_QAudioOutput::resumeWhileStopped()
-{
- // Calls QAudioOutput::resume() when object is already in StoppedState
- // Checks that
- // - No state change occurs
- // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
-
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
-
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
- audioOutput.resume();
-
- // Check that no state transition occurred
- QVERIFY2((stateSignal.count() == 0), "resume() while stopped is emitting a signal and it shouldn't");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after resume()");
-}
-
-void tst_QAudioOutput::pull()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioOutput audioOutput(audioFormat, this);
-
- audioOutput.setNotifyInterval(100);
- audioOutput.setVolume(0.1f);
-
- QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::ReadOnly);
- audioFile->seek(WavHeader::headerLength());
-
- audioOutput.start(audioFile.data());
-
- // Check that QAudioOutput immediately transitions to ActiveState
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioOutput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
-
- // Wait until playback finishes
- QTRY_VERIFY2(audioFile->atEnd(), "didn't play to EOF");
- QTRY_VERIFY(stateSignal.count() > 0);
- QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
- stateSignal.clear();
-
- qint64 processedUs = audioOutput.processedUSecs();
-
- audioOutput.stop();
- QTest::qWait(40);
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2((processedUs == 2000000),
- QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
- QVERIFY2(notifySignal.count() > 0, "not emitting notify() signal");
-
- audioFile->close();
-}
-
-void tst_QAudioOutput::pullSuspendResume()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
- QAudioOutput audioOutput(audioFormat, this);
-
- audioOutput.setNotifyInterval(100);
- audioOutput.setVolume(0.1f);
-
- QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::ReadOnly);
- audioFile->seek(WavHeader::headerLength());
-
- audioOutput.start(audioFile.data());
- // Check that QAudioOutput immediately transitions to ActiveState
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioOutput.periodSize() > 0);
- stateSignal.clear();
-
- // Wait for half of clip to play
- QTest::qWait(1000);
-
- audioOutput.suspend();
-
- // Give backends running in separate threads a chance to suspend.
- QTest::qWait(100);
-
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()");
- stateSignal.clear();
-
- // Check that only 'elapsed', and not 'processed' increases while suspended
- qint64 elapsedUs = audioOutput.elapsedUSecs();
- qint64 processedUs = audioOutput.processedUSecs();
- QTest::qWait(1000);
- QVERIFY(audioOutput.elapsedUSecs() > elapsedUs);
- QVERIFY(audioOutput.processedUSecs() == processedUs);
-
- audioOutput.resume();
-
- // Check that QAudioOutput immediately transitions to ActiveState
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
- stateSignal.clear();
-
- // Wait until playback finishes
- QTest::qWait(3000); // 3 seconds should be plenty
-
- QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
- QVERIFY(stateSignal.count() > 0);
- QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
- stateSignal.clear();
-
- processedUs = audioOutput.processedUSecs();
-
- audioOutput.stop();
- QTest::qWait(40);
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2((processedUs == 2000000),
- QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
-
- audioFile->close();
-}
-
-void tst_QAudioOutput::push()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioOutput audioOutput(audioFormat, this);
-
- audioOutput.setNotifyInterval(100);
- audioOutput.setVolume(0.1f);
-
- QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::ReadOnly);
- audioFile->seek(WavHeader::headerLength());
-
- QIODevice* feed = audioOutput.start();
-
- // Check that QAudioOutput immediately transitions to IdleState
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioOutput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
- QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
-
- qint64 written = 0;
- bool firstBuffer = true;
- QByteArray buffer(AUDIO_BUFFER, 0);
-
- while (written < audioFile->size()-WavHeader::headerLength()) {
-
- if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
- qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize());
- written += feed->write(buffer.constData(), len);
-
- if (firstBuffer) {
- // Check for transition to ActiveState when data is provided
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after receiving data, got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
- firstBuffer = false;
- stateSignal.clear();
- }
- } else
- QTest::qWait(20);
- }
-
- // Wait until playback finishes
- QTest::qWait(3000); // 3 seconds should be plenty
-
- QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
- QVERIFY(stateSignal.count() > 0);
- QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
- stateSignal.clear();
-
- qint64 processedUs = audioOutput.processedUSecs();
-
- audioOutput.stop();
- QTest::qWait(40);
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2((processedUs == 2000000),
- QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
- QVERIFY2(notifySignal.count() > 0, "not emitting notify signal");
-
- audioFile->close();
-}
-
-void tst_QAudioOutput::pushSuspendResume()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioOutput audioOutput(audioFormat, this);
-
- audioOutput.setNotifyInterval(100);
- audioOutput.setVolume(0.1f);
-
- QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::ReadOnly);
- audioFile->seek(WavHeader::headerLength());
-
- QIODevice* feed = audioOutput.start();
-
- // Check that QAudioOutput immediately transitions to IdleState
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioOutput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
- QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
-
- qint64 written = 0;
- bool firstBuffer = true;
- QByteArray buffer(AUDIO_BUFFER, 0);
-
- // Play half of the clip
- while (written < (audioFile->size()-WavHeader::headerLength())/2) {
-
- if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
- qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize());
- written += feed->write(buffer.constData(), len);
-
- if (firstBuffer) {
- // Check for transition to ActiveState when data is provided
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after receiving data, got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
- firstBuffer = false;
- }
- } else
- QTest::qWait(20);
- }
- stateSignal.clear();
-
- audioOutput.suspend();
-
- // Give backends running in separate threads a chance to suspend.
- QTest::qWait(100);
-
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()");
- stateSignal.clear();
-
- // Check that only 'elapsed', and not 'processed' increases while suspended
- qint64 elapsedUs = audioOutput.elapsedUSecs();
- qint64 processedUs = audioOutput.processedUSecs();
- QTest::qWait(1000);
- QVERIFY(audioOutput.elapsedUSecs() > elapsedUs);
- QVERIFY(audioOutput.processedUSecs() == processedUs);
-
- audioOutput.resume();
-
- // Give backends running in separate threads a chance to resume
- // but not too much or the rest of the file may be processed
- QTest::qWait(20);
-
- // Check that QAudioOutput immediately transitions to IdleState
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after resume()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
- stateSignal.clear();
-
- // Play rest of the clip
- while (!audioFile->atEnd()) {
- if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
- qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize());
- written += feed->write(buffer.constData(), len);
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after writing audio data");
- } else
- QTest::qWait(20);
- }
- stateSignal.clear();
-
- // Wait until playback finishes
- QTest::qWait(1000); // 1 seconds should be plenty
-
- QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
- QVERIFY(stateSignal.count() > 0);
- QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
- stateSignal.clear();
-
- processedUs = audioOutput.processedUSecs();
-
- audioOutput.stop();
- QTest::qWait(40);
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2((processedUs == 2000000),
- QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
-
- audioFile->close();
-}
-
-void tst_QAudioOutput::pushUnderrun()
-{
- QFETCH(FilePtr, audioFile);
- QFETCH(QAudioFormat, audioFormat);
-
- QAudioOutput audioOutput(audioFormat, this);
-
- audioOutput.setNotifyInterval(100);
- audioOutput.setVolume(0.1f);
-
- QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
- QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
-
- // Check that we are in the default state before calling start
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
- QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
-
- audioFile->close();
- audioFile->open(QIODevice::ReadOnly);
- audioFile->seek(WavHeader::headerLength());
-
- QIODevice* feed = audioOutput.start();
-
- // Check that QAudioOutput immediately transitions to IdleState
- QTRY_VERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
- QVERIFY(audioOutput.periodSize() > 0);
- stateSignal.clear();
-
- // Check that 'elapsed' increases
- QTest::qWait(40);
- QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
- QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
-
- qint64 written = 0;
- bool firstBuffer = true;
- QByteArray buffer(AUDIO_BUFFER, 0);
-
- // Play half of the clip
- while (written < (audioFile->size()-WavHeader::headerLength())/2) {
-
- if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
- qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize());
- written += feed->write(buffer.constData(), len);
-
- if (firstBuffer) {
- // Check for transition to ActiveState when data is provided
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after receiving data, got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
- firstBuffer = false;
- }
- } else
- QTest::qWait(20);
- }
- stateSignal.clear();
-
- // Wait for data to be played
- QTest::qWait(1000);
-
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit IdleState signal after suspend(), got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState, no data");
- QVERIFY2((audioOutput.error() == QAudio::UnderrunError), "error state is not equal to QAudio::UnderrunError, no data");
- stateSignal.clear();
-
- firstBuffer = true;
- // Play rest of the clip
- while (!audioFile->atEnd()) {
- if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
- qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize());
- written += feed->write(buffer.constData(), len);
- if (firstBuffer) {
- // Check for transition to ActiveState when data is provided
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit signal after receiving data, got %1 signals instead")
- .arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
- firstBuffer = false;
- }
- } else
- QTest::qWait(20);
- }
- stateSignal.clear();
-
- // Wait until playback finishes
- QTest::qWait(1000); // 1 seconds should be plenty
-
- QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
- stateSignal.clear();
-
- qint64 processedUs = audioOutput.processedUSecs();
-
- audioOutput.stop();
- QTest::qWait(40);
- QVERIFY2((stateSignal.count() == 1),
- QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
- QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
-
- QVERIFY2((processedUs == 2000000),
- QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
- QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
- QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
-
- audioFile->close();
-}
-
-void tst_QAudioOutput::volume_data()
-{
- QTest::addColumn<float>("actualFloat");
- QTest::addColumn<int>("expectedInt");
- QTest::newRow("Volume 0.3") << 0.3f << 3;
- QTest::newRow("Volume 0.6") << 0.6f << 6;
- QTest::newRow("Volume 0.9") << 0.9f << 9;
-}
-
-void tst_QAudioOutput::volume()
-{
- QFETCH(float, actualFloat);
- QFETCH(int, expectedInt);
- QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
-
- audioOutput.setVolume(actualFloat);
- QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt);
- // Wait a while to see if this changes
- QTest::qWait(500);
- QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt);
-}
-
-QTEST_MAIN(tst_QAudioOutput)
-
-#include "tst_qaudiooutput.moc"
diff --git a/tests/auto/integration/qaudiooutput/wavheader.cpp b/tests/auto/integration/qaudiooutput/wavheader.cpp
deleted file mode 100644
index 869d74d5b..000000000
--- a/tests/auto/integration/qaudiooutput/wavheader.cpp
+++ /dev/null
@@ -1,192 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtCore/qendian.h>
-#include "wavheader.h"
-
-
-struct chunk
-{
- char id[4];
- quint32 size;
-};
-
-struct RIFFHeader
-{
- chunk descriptor; // "RIFF"
- char type[4]; // "WAVE"
-};
-
-struct WAVEHeader
-{
- chunk descriptor;
- quint16 audioFormat;
- quint16 numChannels;
- quint32 sampleRate;
- quint32 byteRate;
- quint16 blockAlign;
- quint16 bitsPerSample;
-};
-
-struct DATAHeader
-{
- chunk descriptor;
-};
-
-struct CombinedHeader
-{
- RIFFHeader riff;
- WAVEHeader wave;
- DATAHeader data;
-};
-
-static const int HeaderLength = sizeof(CombinedHeader);
-
-
-WavHeader::WavHeader(const QAudioFormat &format, qint64 dataLength)
- : m_format(format)
- , m_dataLength(dataLength)
-{
-
-}
-
-bool WavHeader::read(QIODevice &device)
-{
- bool result = true;
-
- if (!device.isSequential())
- result = device.seek(0);
- // else, assume that current position is the start of the header
-
- if (result) {
- CombinedHeader header;
- result = (device.read(reinterpret_cast<char *>(&header), HeaderLength) == HeaderLength);
- if (result) {
- if ((memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0
- || memcmp(&header.riff.descriptor.id, "RIFX", 4) == 0)
- && memcmp(&header.riff.type, "WAVE", 4) == 0
- && memcmp(&header.wave.descriptor.id, "fmt ", 4) == 0
- && header.wave.audioFormat == 1 // PCM
- ) {
- if (memcmp(&header.riff.descriptor.id, "RIFF", 4) == 0)
- m_format.setByteOrder(QAudioFormat::LittleEndian);
- else
- m_format.setByteOrder(QAudioFormat::BigEndian);
-
- m_format.setChannelCount(qFromLittleEndian<quint16>(header.wave.numChannels));
- m_format.setCodec("audio/pcm");
- m_format.setSampleRate(qFromLittleEndian<quint32>(header.wave.sampleRate));
- m_format.setSampleSize(qFromLittleEndian<quint16>(header.wave.bitsPerSample));
-
- switch(header.wave.bitsPerSample) {
- case 8:
- m_format.setSampleType(QAudioFormat::UnSignedInt);
- break;
- case 16:
- m_format.setSampleType(QAudioFormat::SignedInt);
- break;
- default:
- result = false;
- }
-
- m_dataLength = device.size() - HeaderLength;
- } else {
- result = false;
- }
- }
- }
-
- return result;
-}
-
-bool WavHeader::write(QIODevice &device)
-{
- CombinedHeader header;
-
- memset(&header, 0, HeaderLength);
-
- // RIFF header
- if (m_format.byteOrder() == QAudioFormat::LittleEndian)
- memcpy(header.riff.descriptor.id,"RIFF",4);
- else
- memcpy(header.riff.descriptor.id,"RIFX",4);
- qToLittleEndian<quint32>(quint32(m_dataLength + HeaderLength - 8),
- reinterpret_cast<unsigned char*>(&header.riff.descriptor.size));
- memcpy(header.riff.type, "WAVE",4);
-
- // WAVE header
- memcpy(header.wave.descriptor.id,"fmt ",4);
- qToLittleEndian<quint32>(quint32(16),
- reinterpret_cast<unsigned char*>(&header.wave.descriptor.size));
- qToLittleEndian<quint16>(quint16(1),
- reinterpret_cast<unsigned char*>(&header.wave.audioFormat));
- qToLittleEndian<quint16>(quint16(m_format.channelCount()),
- reinterpret_cast<unsigned char*>(&header.wave.numChannels));
- qToLittleEndian<quint32>(quint32(m_format.sampleRate()),
- reinterpret_cast<unsigned char*>(&header.wave.sampleRate));
- qToLittleEndian<quint32>(quint32(m_format.sampleRate() * m_format.channelCount() * m_format.sampleSize() / 8),
- reinterpret_cast<unsigned char*>(&header.wave.byteRate));
- qToLittleEndian<quint16>(quint16(m_format.channelCount() * m_format.sampleSize() / 8),
- reinterpret_cast<unsigned char*>(&header.wave.blockAlign));
- qToLittleEndian<quint16>(quint16(m_format.sampleSize()),
- reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample));
-
- // DATA header
- memcpy(header.data.descriptor.id,"data",4);
- qToLittleEndian<quint32>(quint32(m_dataLength),
- reinterpret_cast<unsigned char*>(&header.data.descriptor.size));
-
- return (device.write(reinterpret_cast<const char *>(&header), HeaderLength) == HeaderLength);
-}
-
-const QAudioFormat& WavHeader::format() const
-{
- return m_format;
-}
-
-qint64 WavHeader::dataLength() const
-{
- return m_dataLength;
-}
-
-qint64 WavHeader::headerLength()
-{
- return HeaderLength;
-}
-
-bool WavHeader::writeDataLength(QIODevice &device, qint64 dataLength)
-{
- bool result = false;
- if (!device.isSequential()) {
- device.seek(40);
- unsigned char dataLengthLE[4];
- qToLittleEndian<quint32>(quint32(dataLength), dataLengthLE);
- result = (device.write(reinterpret_cast<const char *>(dataLengthLE), 4) == 4);
- }
- return result;
-}
diff --git a/tests/auto/integration/qaudiooutput/wavheader.h b/tests/auto/integration/qaudiooutput/wavheader.h
deleted file mode 100644
index b9595cffc..000000000
--- a/tests/auto/integration/qaudiooutput/wavheader.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#ifndef WAVHEADER_H
-#define WAVHEADER_H
-
-#include <QtCore/qobject.h>
-#include <QtCore/qfile.h>
-#include <qaudioformat.h>
-
-/**
- * Helper class for parsing WAV file headers.
- *
- * See https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
- */
-class WavHeader
-{
-public:
- WavHeader(const QAudioFormat &format = QAudioFormat(),
- qint64 dataLength = 0);
-
- // Reads WAV header and seeks to start of data
- bool read(QIODevice &device);
-
- // Writes WAV header
- bool write(QIODevice &device);
-
- const QAudioFormat& format() const;
- qint64 dataLength() const;
-
- static qint64 headerLength();
-
- static bool writeDataLength(QIODevice &device, qint64 dataLength);
-
-private:
- QAudioFormat m_format;
- qint64 m_dataLength;
-};
-
-#endif
-
diff --git a/tests/auto/integration/qaudiosink/BLACKLIST b/tests/auto/integration/qaudiosink/BLACKLIST
new file mode 100644
index 000000000..0b8789267
--- /dev/null
+++ b/tests/auto/integration/qaudiosink/BLACKLIST
@@ -0,0 +1,11 @@
+#QTBUG-113194
+[pullSuspendResume]
+macos ci
+
+#QTBUG-113194
+[pushSuspendResume]
+macos ci
+
+#QTBUG-122309
+[pullResumeFromUnderrun]
+rhel-9.2
diff --git a/tests/auto/integration/qaudiosink/CMakeLists.txt b/tests/auto/integration/qaudiosink/CMakeLists.txt
new file mode 100644
index 000000000..902326dcc
--- /dev/null
+++ b/tests/auto/integration/qaudiosink/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qaudiosink Test:
+#####################################################################
+
+qt_internal_add_test(tst_qaudiosink
+ SOURCES
+ tst_qaudiosink.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+)
diff --git a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
new file mode 100644
index 000000000..6fdfe8221
--- /dev/null
+++ b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp
@@ -0,0 +1,1053 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <QtCore/qlocale.h>
+#include <QtCore/QTemporaryDir>
+#include <QtCore/QSharedPointer>
+#include <QtCore/QScopedPointer>
+
+#include <qaudiosink.h>
+#include <qaudiodevice.h>
+#include <qaudioformat.h>
+#include <qaudio.h>
+#include <qmediadevices.h>
+#include <qwavedecoder.h>
+
+#define AUDIO_BUFFER 192000
+
+class tst_QAudioSink : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QAudioSink(QObject* parent=nullptr) : QObject(parent) {}
+
+private slots:
+ void initTestCase();
+ void format();
+ void invalidFormat_data();
+ void invalidFormat();
+
+ void bufferSize_data();
+ void bufferSize();
+
+ void stopWhileStopped();
+ void suspendWhileStopped();
+ void resumeWhileStopped();
+
+ void pull_data(){generate_audiofile_testrows();}
+ void pull();
+
+ void pullSuspendResume_data(){generate_audiofile_testrows();}
+ void pullSuspendResume();
+ void pullResumeFromUnderrun();
+
+ void push_data(){generate_audiofile_testrows();}
+ void push();
+
+ void pushSuspendResume_data(){generate_audiofile_testrows();}
+ void pushSuspendResume();
+
+ void pushResetResume();
+
+ void pushUnderrun_data(){generate_audiofile_testrows();}
+ void pushUnderrun();
+
+ void volume_data();
+ void volume();
+
+private:
+ using FilePtr = QSharedPointer<QFile>;
+
+ static QString formatToFileName(const QAudioFormat &format);
+ void createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate = 440);
+ static QString dumpStateSignalSpy(const QSignalSpy &stateSignalSpy);
+
+ static qint64 wavDataSize(QIODevice &input);
+
+ template<typename Checker>
+ static void pushDataToAudioSink(QAudioSink &sink, QIODevice &input, QIODevice &feed,
+ qint64 &allWritten, qint64 writtenLimit, Checker &&checker,
+ bool checkOnlyFirst = false);
+
+ void generate_audiofile_testrows();
+
+ QAudioDevice audioDevice;
+ QList<QAudioFormat> testFormats;
+ QList<FilePtr> audioFiles;
+ QScopedPointer<QTemporaryDir> m_temporaryDir;
+
+ QScopedPointer<QByteArray> m_byteArray;
+ QScopedPointer<QBuffer> m_buffer;
+};
+
+QString tst_QAudioSink::formatToFileName(const QAudioFormat &format)
+{
+ return QStringLiteral("%1_%2_%3")
+ .arg(format.sampleRate())
+ .arg(format.bytesPerSample())
+ .arg(format.channelCount());
+}
+
+void tst_QAudioSink::createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate)
+{
+ const int channelBytes = format.bytesPerSample();
+ const int sampleBytes = format.bytesPerFrame();
+
+ Q_ASSERT(length % sampleBytes == 0);
+ Q_UNUSED(sampleBytes); // suppress warning in release builds
+
+ m_byteArray.reset(new QByteArray(length, 0));
+ unsigned char *ptr = reinterpret_cast<unsigned char *>(m_byteArray->data());
+ int sampleIndex = 0;
+
+ while (length) {
+ const qreal x = qSin(2 * M_PI * sampleRate * qreal(sampleIndex % format.sampleRate()) / format.sampleRate());
+ for (int i = 0; i < format.channelCount(); ++i) {
+ switch (format.sampleFormat()) {
+ case QAudioFormat::UInt8: {
+ const quint8 value = static_cast<quint8>((1.0 + x) / 2 * 255);
+ *reinterpret_cast<quint8 *>(ptr) = value;
+ break;
+ }
+ case QAudioFormat::Int16: {
+ qint16 value = static_cast<qint16>(x * 32767);
+ *reinterpret_cast<qint16 *>(ptr) = value;
+ break;
+ }
+ case QAudioFormat::Int32: {
+ quint32 value = static_cast<quint32>(x) * std::numeric_limits<qint32>::max();
+ *reinterpret_cast<qint32 *>(ptr) = value;
+ break;
+ }
+ case QAudioFormat::Float:
+ *reinterpret_cast<float *>(ptr) = x;
+ break;
+ case QAudioFormat::Unknown:
+ case QAudioFormat::NSampleFormats:
+ break;
+ }
+
+ ptr += channelBytes;
+ length -= channelBytes;
+ }
+ ++sampleIndex;
+ }
+
+ m_buffer.reset(new QBuffer(m_byteArray.data(), this));
+ Q_ASSERT(m_buffer->open(QIODevice::ReadOnly));
+}
+
+QString tst_QAudioSink::dumpStateSignalSpy(const QSignalSpy& stateSignalSpy) {
+ QString result = "[";
+ bool first = true;
+ for (auto& params : stateSignalSpy)
+ {
+ if (!std::exchange(first, false))
+ result += ',';
+ result += QString::number(params.front().value<QAudio::State>());
+ }
+ result.append(']');
+ return result;
+}
+
+qint64 tst_QAudioSink::wavDataSize(QIODevice &input)
+{
+ return input.size() - QWaveDecoder::headerLength();
+}
+
+template<typename Checker>
+void tst_QAudioSink::pushDataToAudioSink(QAudioSink &sink, QIODevice &input, QIODevice &feed,
+ qint64 &allWritten, qint64 writtenLimit, Checker &&checker,
+ bool checkOnlyFirst)
+{
+ bool firstBuffer = true;
+ qint64 offset = 0;
+ QByteArray buffer;
+
+ while ((allWritten < writtenLimit || writtenLimit < 0) && !input.atEnd()
+ && !QTest::currentTestFailed()) {
+ if (sink.bytesFree() > 0) {
+ if (buffer.isNull())
+ buffer = input.read(sink.bytesFree());
+
+ const auto written = feed.write(buffer);
+ allWritten += written;
+ offset += written;
+
+ if (offset >= buffer.size()) {
+ offset = 0;
+ buffer.clear();
+ }
+
+ if (!checkOnlyFirst || firstBuffer)
+ checker();
+
+ firstBuffer = false;
+ } else {
+ // wait a bit to ensure some the sink has consumed some data
+ // The delay getting might need some improvements
+ const auto delay = qMin(10, sink.format().durationForBytes(sink.bufferSize()) / 1000 / 2);
+ QTest::qWait(delay);
+ }
+ }
+}
+
+void tst_QAudioSink::generate_audiofile_testrows()
+{
+ QTest::addColumn<FilePtr>("audioFile");
+ QTest::addColumn<QAudioFormat>("audioFormat");
+
+ for (int i=0; i<audioFiles.size(); i++) {
+ QTest::newRow(QStringLiteral("Audio File %1").arg(i).toUtf8().constData())
+ << audioFiles.at(i) << testFormats.at(i);
+ }
+}
+
+void tst_QAudioSink::initTestCase()
+{
+ // Only perform tests if audio output device exists
+ const QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
+
+ if (devices.size() <= 0)
+ QSKIP("No audio backend");
+
+ audioDevice = QMediaDevices::defaultAudioOutput();
+
+
+ QAudioFormat format;
+
+ if (audioDevice.isFormatSupported(audioDevice.preferredFormat())) {
+ if (format.sampleFormat() == QAudioFormat::Int16)
+ testFormats.append(audioDevice.preferredFormat());
+ }
+
+ // PCM 11025 mono S16LE
+ format.setChannelCount(1);
+ format.setSampleRate(11025);
+ format.setSampleFormat(QAudioFormat::Int16);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 22050 mono S16LE
+ format.setSampleRate(22050);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 22050 stereo S16LE
+ format.setChannelCount(2);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 44100 stereo S16LE
+ format.setSampleRate(44100);
+ if (audioDevice.isFormatSupported(format))
+#ifdef Q_OS_ANDROID
+ // Testset crash on emulator x86 with API 23 (Android 6) for 44,1 MHz.
+ // It is not happen on x86 with API 24. What is more, there is no crash when
+ // tested sample rate is 44,999 or any other value. Seems like problem on
+ // emulator side. Let's turn off this frequency for API 23
+ if (QNativeInterface::QAndroidApplication::sdkVersion() > __ANDROID_API_M__)
+#endif
+ testFormats.append(format);
+
+ // PCM 48000 stereo S16LE
+ format.setSampleRate(48000);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ QVERIFY(testFormats.size());
+
+ const QChar slash = QLatin1Char('/');
+ QString temporaryPattern = QDir::tempPath();
+ if (!temporaryPattern.endsWith(slash))
+ temporaryPattern += slash;
+ temporaryPattern += "tst_qaudiooutputXXXXXX";
+ m_temporaryDir.reset(new QTemporaryDir(temporaryPattern));
+ m_temporaryDir->setAutoRemove(true);
+ QVERIFY(m_temporaryDir->isValid());
+
+ const QString temporaryAudioPath = m_temporaryDir->path() + slash;
+ for (const QAudioFormat &format : std::as_const(testFormats)) {
+ qint64 len = format.sampleRate()*format.bytesPerFrame(); // 1 second
+ createSineWaveData(format, len);
+ // Write generate sine wave data to file
+ const QString fileName = temporaryAudioPath + QStringLiteral("generated")
+ + formatToFileName(format) + QStringLiteral(".wav");
+ FilePtr file(new QFile(fileName));
+ QVERIFY2(file->open(QIODevice::WriteOnly), qPrintable(file->errorString()));
+ QWaveDecoder waveDecoder(file.data(), format);
+ if (waveDecoder.open(QIODevice::WriteOnly)) {
+ waveDecoder.write(m_byteArray->data(), len);
+ waveDecoder.close();
+ }
+ file->close();
+ audioFiles.append(file);
+ }
+}
+
+void tst_QAudioSink::format()
+{
+ QAudioSink audioOutput(audioDevice.preferredFormat(), this);
+
+ QAudioFormat requested = audioDevice.preferredFormat();
+ QAudioFormat actual = audioOutput.format();
+
+ QVERIFY2((requested.channelCount() == actual.channelCount()),
+ QStringLiteral("channels: requested=%1, actual=%2")
+ .arg(requested.channelCount())
+ .arg(actual.channelCount())
+ .toUtf8()
+ .constData());
+ QVERIFY2((requested.sampleRate() == actual.sampleRate()),
+ QStringLiteral("sampleRate: requested=%1, actual=%2")
+ .arg(requested.sampleRate())
+ .arg(actual.sampleRate())
+ .toUtf8()
+ .constData());
+ QVERIFY2((requested.sampleFormat() == actual.sampleFormat()),
+ QStringLiteral("sampleFormat: requested=%1, actual=%2")
+ .arg((ushort)requested.sampleFormat())
+ .arg((ushort)actual.sampleFormat())
+ .toUtf8()
+ .constData());
+ QVERIFY(requested == actual);
+}
+
+void tst_QAudioSink::invalidFormat_data()
+{
+ QTest::addColumn<QAudioFormat>("invalidFormat");
+
+ QAudioFormat format;
+
+ QTest::newRow("Null Format")
+ << format;
+
+ format = audioDevice.preferredFormat();
+ format.setChannelCount(0);
+ QTest::newRow("Channel count 0")
+ << format;
+
+ format = audioDevice.preferredFormat();
+ format.setSampleRate(0);
+ QTest::newRow("Sample rate 0")
+ << format;
+
+ format = audioDevice.preferredFormat();
+ format.setSampleFormat(QAudioFormat::Unknown);
+ QTest::newRow("Sample size 0")
+ << format;
+}
+
+void tst_QAudioSink::invalidFormat()
+{
+ QFETCH(QAudioFormat, invalidFormat);
+
+ QVERIFY2(!audioDevice.isFormatSupported(invalidFormat),
+ "isFormatSupported() is returning true on an invalid format");
+
+ QAudioSink audioOutput(invalidFormat, this);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ audioOutput.start();
+ // Check that error is raised
+ QTRY_VERIFY2((audioOutput.error() == QAudio::OpenError),"error() was not set to QAudio::OpenError after start()");
+}
+
+void tst_QAudioSink::bufferSize_data()
+{
+ QTest::addColumn<int>("bufferSize");
+ QTest::newRow("Buffer size 512") << 512;
+ QTest::newRow("Buffer size 4096") << 4096;
+ QTest::newRow("Buffer size 8192") << 8192;
+}
+
+void tst_QAudioSink::bufferSize()
+{
+ QFETCH(int, bufferSize);
+ QAudioSink audioOutput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioOutput.error() == QAudio::NoError),
+ QStringLiteral("error() was not set to QAudio::NoError on creation(%1)")
+ .arg(bufferSize)
+ .toUtf8()
+ .constData());
+
+ audioOutput.setBufferSize(bufferSize);
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize");
+ QVERIFY2((audioOutput.bufferSize() == bufferSize),
+ QStringLiteral("bufferSize: requested=%1, actual=%2")
+ .arg(bufferSize)
+ .arg(audioOutput.bufferSize())
+ .toUtf8()
+ .constData());
+}
+
+void tst_QAudioSink::stopWhileStopped()
+{
+ // Calls QAudioSink::stop() when object is already in StoppedState
+ // Checks that
+ // - No state change occurs
+ // - No error is raised (QAudioSink::error() returns QAudio::NoError)
+
+ QAudioSink audioOutput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+ audioOutput.stop();
+
+ // Check that no state transition occurred
+ QVERIFY2((stateSignal.size() == 0), "stop() while stopped is emitting a signal and it shouldn't");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
+}
+
+void tst_QAudioSink::suspendWhileStopped()
+{
+ // Calls QAudioSink::suspend() when object is already in StoppedState
+ // Checks that
+ // - No state change occurs
+ // - No error is raised (QAudioSink::error() returns QAudio::NoError)
+
+ QAudioSink audioOutput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+ audioOutput.suspend();
+
+ // Check that no state transition occurred
+ QVERIFY2((stateSignal.size() == 0), "stop() while suspended is emitting a signal and it shouldn't");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
+}
+
+void tst_QAudioSink::resumeWhileStopped()
+{
+ // Calls QAudioSink::resume() when object is already in StoppedState
+ // Checks that
+ // - No state change occurs
+ // - No error is raised (QAudioSink::error() returns QAudio::NoError)
+
+ QAudioSink audioOutput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+ audioOutput.resume();
+
+ // Check that no state transition occurred
+ QVERIFY2((stateSignal.size() == 0), "resume() while stopped is emitting a signal and it shouldn't");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after resume()");
+}
+
+void tst_QAudioSink::pull()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSink audioOutput(audioFormat, this);
+
+ audioOutput.setVolume(0.1f);
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::ReadOnly);
+ audioFile->seek(QWaveDecoder::headerLength());
+
+ audioOutput.start(audioFile.data());
+
+ // Check that QAudioSink immediately transitions to ActiveState
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal on start(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTest::qWait(40);
+ QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+
+ // Wait until playback finishes
+ QTRY_VERIFY2(audioFile->atEnd(), "didn't play to EOF");
+ QTRY_VERIFY(stateSignal.size() > 0);
+ QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
+ stateSignal.clear();
+
+ QTRY_COMPARE(audioOutput.processedUSecs(), 1000000);
+
+ audioOutput.stop();
+ QTest::qWait(40);
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ audioFile->close();
+}
+
+void tst_QAudioSink::pullSuspendResume()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+ QAudioSink audioOutput(audioFormat, this);
+
+ audioOutput.setVolume(0.1f);
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::ReadOnly);
+ audioFile->seek(QWaveDecoder::headerLength());
+
+ audioOutput.start(audioFile.data());
+ // Check that QAudioSink immediately transitions to ActiveState
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal on start(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError),
+ "error state is not equal to QAudio::NoError after start()");
+
+ stateSignal.clear();
+ // Wait for half of clip to play
+ QTest::qWait(500);
+
+ audioOutput.suspend();
+ QTest::qWait(100);
+
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral(
+ "didn't emit SuspendedState signal after suspend(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()");
+ stateSignal.clear();
+
+ // Check that only 'elapsed', and not 'processed' increases while suspended
+ qint64 elapsedUs = audioOutput.elapsedUSecs();
+ qint64 processedUs = audioOutput.processedUSecs();
+ QTest::qWait(100);
+ QVERIFY(audioOutput.elapsedUSecs() > elapsedUs);
+ QVERIFY(audioOutput.processedUSecs() == processedUs);
+
+ audioOutput.resume();
+
+ // Check that QAudioSink immediately transitions to ActiveState
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after resume(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
+ stateSignal.clear();
+
+ // Wait until playback finishes
+ QTRY_VERIFY2(audioFile->atEnd(), "didn't play to EOF");
+ QTRY_VERIFY(stateSignal.size() > 0);
+ QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
+ stateSignal.clear();
+
+ QTRY_COMPARE(audioOutput.processedUSecs(), 1000000);
+
+ audioOutput.stop();
+ QTest::qWait(40);
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ audioFile->close();
+}
+
+void tst_QAudioSink::pullResumeFromUnderrun()
+{
+ class AudioPullSource : public QIODevice
+ {
+ public:
+ qint64 readData(char *data, qint64 len) override {
+ qint64 read = qMin(len, available);
+ available -= read;
+ memset(data, 0, read);
+ return read;
+ }
+ qint64 writeData(const char *, qint64) override { return 0; }
+ bool isSequential() const override { return true; }
+
+ qint64 bytesAvailable() const override { return available; }
+ bool atEnd() const override { return signalEnd && available == 0; }
+
+ qint64 available = 0;
+ bool signalEnd = false;
+ };
+
+ constexpr int chunkSize = 128;
+
+ QAudioFormat format;
+ format.setChannelCount(1);
+ format.setSampleFormat(QAudioFormat::UInt8);
+ format.setSampleRate(8000);
+
+ AudioPullSource audioSource;
+ QAudioSink audioOutput(format, this);
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+
+ audioSource.open(QIODeviceBase::ReadOnly);
+ audioSource.available = chunkSize;
+ QCOMPARE(audioOutput.state(), QAudio::StoppedState);
+ audioOutput.start(&audioSource);
+
+ QTRY_COMPARE(stateSignal.size(), 1);
+ QCOMPARE(audioOutput.state(), QAudio::ActiveState);
+ QCOMPARE(audioOutput.error(), QAudio::NoError);
+ stateSignal.clear();
+
+ QTRY_COMPARE(stateSignal.size(), 1);
+ QCOMPARE(audioOutput.state(), QAudio::IdleState);
+ QCOMPARE(audioOutput.error(), QAudio::UnderrunError);
+ stateSignal.clear();
+
+ QTest::qWait(300);
+ audioSource.available = chunkSize;
+ audioSource.signalEnd = true;
+
+ // Resume pull
+ emit audioSource.readyRead();
+
+ QTRY_COMPARE(stateSignal.size(), 2);
+ QCOMPARE(stateSignal.at(0).front().value<QAudio::State>(), QAudio::ActiveState);
+ QCOMPARE(stateSignal.at(1).front().value<QAudio::State>(), QAudio::IdleState);
+
+ QCOMPARE(audioOutput.error(), QAudio::NoError);
+ QCOMPARE(audioOutput.state(), QAudio::IdleState);
+
+ // we played two chunks, sample rate is per second
+ const int expectedUSecs = (double(chunkSize) / double(format.sampleRate()))
+ * 2 * 1000 * 1000;
+ QTRY_COMPARE(audioOutput.processedUSecs(), expectedUSecs);
+}
+
+void tst_QAudioSink::push()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSink audioOutput(audioFormat, this);
+
+ audioOutput.setVolume(0.1f);
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::ReadOnly);
+ audioFile->seek(QWaveDecoder::headerLength());
+
+ QIODevice* feed = audioOutput.start();
+
+ // Check that QAudioSink immediately transitions to IdleState
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal on start(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTest::qWait(40);
+ QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+ QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
+
+ qint64 written = 0;
+
+ auto checker = [&]() {
+ // Check for transition to ActiveState when data is provided
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after receiving data, got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after receiving data");
+ QVERIFY2((audioOutput.error() == QAudio::NoError),
+ "error state is not equal to QAudio::NoError after receiving data");
+ stateSignal.clear();
+ };
+
+ pushDataToAudioSink(audioOutput, *audioFile, *feed, written, wavDataSize(*audioFile), checker,
+ true);
+
+ // Wait until playback finishes
+ QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
+ QTRY_VERIFY(audioOutput.state() == QAudio::IdleState);
+ QTRY_VERIFY(stateSignal.size() > 0);
+ QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
+ stateSignal.clear();
+
+ QTRY_COMPARE(audioOutput.processedUSecs(), 1000000);
+
+ audioOutput.stop();
+ QTest::qWait(40);
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ audioFile->close();
+}
+
+void tst_QAudioSink::pushSuspendResume()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSink audioOutput(audioFormat, this);
+
+ audioOutput.setVolume(0.1f);
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::ReadOnly);
+ audioFile->seek(QWaveDecoder::headerLength());
+
+ QIODevice* feed = audioOutput.start();
+
+ // Check that QAudioSink immediately transitions to IdleState
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal on start(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTest::qWait(40);
+ QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+ QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
+
+ auto firstHalfChecker = [&]() {
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after receiving data, got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after receiving data");
+ QVERIFY2((audioOutput.error() == QAudio::NoError),
+ "error state is not equal to QAudio::NoError after receiving data");
+ };
+
+ qint64 written = 0;
+ // Play half of the clip
+ pushDataToAudioSink(audioOutput, *audioFile, *feed, written, wavDataSize(*audioFile) / 2,
+ firstHalfChecker, true);
+
+ stateSignal.clear();
+
+ const auto suspendedInState = audioOutput.state();
+ audioOutput.suspend();
+
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral(
+ "didn't emit SuspendedState signal after suspend(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()");
+ stateSignal.clear();
+
+ // Check that only 'elapsed', and not 'processed' increases while suspended
+ qint64 elapsedUs = audioOutput.elapsedUSecs();
+ qint64 processedUs = audioOutput.processedUSecs();
+ QTest::qWait(100);
+ QVERIFY(audioOutput.elapsedUSecs() > elapsedUs);
+ QVERIFY(audioOutput.processedUSecs() == processedUs);
+
+ audioOutput.resume();
+
+ // Give backends running in separate threads a chance to resume
+ // but not too much or the rest of the file may be processed
+ QTest::qWait(20);
+
+ // Check that QAudioSink immediately transitions to IdleState
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after resume(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == suspendedInState), "resume() didn't transition to state before suspend()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
+ stateSignal.clear();
+
+ // Play rest of the clip
+
+ auto restChecker = [&]() {
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after writing audio data");
+ };
+
+ pushDataToAudioSink(audioOutput, *audioFile, *feed, written, -1, restChecker);
+
+ QVERIFY(audioOutput.state() != QAudio::IdleState);
+ stateSignal.clear();
+
+ QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
+ QTRY_VERIFY(stateSignal.size() > 0);
+ QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
+ stateSignal.clear();
+
+ QTRY_COMPARE(audioOutput.processedUSecs(), 1000000);
+
+ audioOutput.stop();
+ QTest::qWait(40);
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ audioFile->close();
+}
+
+void tst_QAudioSink::pushResetResume()
+{
+ auto audioFile = audioFiles.at(0);
+ auto audioFormat = testFormats.at(0);
+
+ QAudioSink audioOutput(audioFormat, this);
+
+ audioOutput.setBufferSize(8192);
+ audioOutput.setVolume(0.1f);
+
+ audioFile->close();
+ audioFile->open(QIODevice::ReadOnly);
+ audioFile->seek(QWaveDecoder::headerLength());
+
+ QPointer<QIODevice> feed = audioOutput.start();
+
+ QTest::qWait(20);
+
+ auto buffer = audioFile->read(audioOutput.bytesFree());
+ feed->write(buffer);
+
+ QTest::qWait(20);
+ QTRY_COMPARE(audioOutput.state(), QAudio::ActiveState);
+
+ audioOutput.reset();
+ QCOMPARE(audioOutput.state(), QAudio::StoppedState);
+ QCOMPARE(audioOutput.error(), QAudio::NoError);
+
+ const auto processedUSecs = audioOutput.processedUSecs();
+
+ audioOutput.resume();
+ QTest::qWait(40);
+
+ // Nothing changed if resume after reset
+ QCOMPARE(audioOutput.state(), QAudio::StoppedState);
+ QCOMPARE(audioOutput.error(), QAudio::NoError);
+
+ QCOMPARE(audioOutput.processedUSecs(), processedUSecs);
+}
+
+void tst_QAudioSink::pushUnderrun()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSink audioOutput(audioFormat, this);
+
+ audioOutput.setVolume(0.1f);
+
+ QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::ReadOnly);
+ audioFile->seek(QWaveDecoder::headerLength());
+
+ QIODevice* feed = audioOutput.start();
+
+ // Check that QAudioSink immediately transitions to IdleState
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal on start(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTest::qWait(40);
+ QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+ QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
+
+ qint64 written = 0;
+
+ // Play half of the clip
+
+ auto firstHalfChecker = [&]() {
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after receiving data, got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after receiving data");
+ QVERIFY2((audioOutput.error() == QAudio::NoError),
+ "error state is not equal to QAudio::NoError after receiving data");
+ };
+
+ pushDataToAudioSink(audioOutput, *audioFile, *feed, written, wavDataSize(*audioFile) / 2,
+ firstHalfChecker, true);
+
+ stateSignal.clear();
+
+ // Wait for data to be played
+ QTest::qWait(700);
+
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit IdleState signal after suspend(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState, no data");
+ QVERIFY2((audioOutput.error() == QAudio::UnderrunError), "error state is not equal to QAudio::UnderrunError, no data");
+ stateSignal.clear();
+
+ // Play rest of the clip
+ auto restChecker = [&]() {
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after receiving data, got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after receiving data");
+ QVERIFY2((audioOutput.error() == QAudio::NoError),
+ "error state is not equal to QAudio::NoError after receiving data");
+ };
+ pushDataToAudioSink(audioOutput, *audioFile, *feed, written, -1, restChecker, true);
+
+ stateSignal.clear();
+
+ // Wait until playback finishes
+ QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit IdleState signal when at EOF, got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
+ stateSignal.clear();
+
+ QTRY_COMPARE(audioOutput.processedUSecs(), 1000000);
+
+ audioOutput.stop();
+ QTest::qWait(40);
+ QVERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(dumpStateSignalSpy(stateSignal))
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ audioFile->close();
+}
+
+void tst_QAudioSink::volume_data()
+{
+ QTest::addColumn<float>("actualFloat");
+ QTest::addColumn<int>("expectedInt");
+ QTest::newRow("Volume 0.3") << 0.3f << 3;
+ QTest::newRow("Volume 0.6") << 0.6f << 6;
+ QTest::newRow("Volume 0.9") << 0.9f << 9;
+}
+
+void tst_QAudioSink::volume()
+{
+ QFETCH(float, actualFloat);
+ QFETCH(int, expectedInt);
+ QAudioSink audioOutput(audioDevice.preferredFormat(), this);
+
+ audioOutput.setVolume(actualFloat);
+ QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt);
+ // Wait a while to see if this changes
+ QTest::qWait(500);
+ QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt);
+}
+
+QTEST_MAIN(tst_QAudioSink)
+
+#include "tst_qaudiosink.moc"
diff --git a/tests/auto/integration/qaudiosource/CMakeLists.txt b/tests/auto/integration/qaudiosource/CMakeLists.txt
new file mode 100644
index 000000000..0e572e804
--- /dev/null
+++ b/tests/auto/integration/qaudiosource/CMakeLists.txt
@@ -0,0 +1,14 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qaudiosource Test:
+#####################################################################
+
+qt_internal_add_test(tst_qaudiosource
+ SOURCES
+ tst_qaudiosource.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+)
diff --git a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
new file mode 100644
index 000000000..ae100a08b
--- /dev/null
+++ b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp
@@ -0,0 +1,817 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <QtCore/qlocale.h>
+#include <QtCore/QTemporaryDir>
+#include <QtCore/QSharedPointer>
+#include <QtCore/QScopedPointer>
+
+#include <qaudiosource.h>
+#include <qaudiodevice.h>
+#include <qaudioformat.h>
+#include <qaudio.h>
+#include <qmediadevices.h>
+
+#include <qwavedecoder.h>
+
+#define RANGE_ERR 0.5
+
+template<typename T> inline bool qTolerantCompare(T value, T expected)
+{
+ return qAbs(value - expected) < (RANGE_ERR * expected);
+}
+
+class tst_QAudioSource : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QAudioSource(QObject* parent=nullptr) : QObject(parent) {}
+
+private slots:
+ void initTestCase();
+
+ void format();
+ void invalidFormat_data();
+ void invalidFormat();
+
+ void bufferSize();
+
+ void stopWhileStopped();
+ void suspendWhileStopped();
+ void resumeWhileStopped();
+
+ void pull_data(){generate_audiofile_testrows();}
+ void pull();
+
+ void pullSuspendResume_data(){generate_audiofile_testrows();}
+ void pullSuspendResume();
+
+ void push_data(){generate_audiofile_testrows();}
+ void push();
+
+ void pushSuspendResume_data(){generate_audiofile_testrows();}
+ void pushSuspendResume();
+
+ void reset_data(){generate_audiofile_testrows();}
+ void reset();
+
+ void volume_data(){generate_audiofile_testrows();}
+ void volume();
+
+private:
+ using FilePtr = QSharedPointer<QFile>;
+
+ QString formatToFileName(const QAudioFormat &format);
+
+ void generate_audiofile_testrows();
+
+ QAudioDevice audioDevice;
+ QList<QAudioFormat> testFormats;
+ QList<FilePtr> audioFiles;
+ QScopedPointer<QTemporaryDir> m_temporaryDir;
+
+ QScopedPointer<QByteArray> m_byteArray;
+ QScopedPointer<QBuffer> m_buffer;
+
+ bool m_inCISystem = false;
+};
+
+void tst_QAudioSource::generate_audiofile_testrows()
+{
+ QTest::addColumn<FilePtr>("audioFile");
+ QTest::addColumn<QAudioFormat>("audioFormat");
+
+ for (int i=0; i<audioFiles.size(); i++) {
+ QTest::newRow(QStringLiteral("%1").arg(i).toUtf8().constData())
+ << audioFiles.at(i) << testFormats.at(i);
+
+ // Only run first format in CI system to reduce test times
+ if (m_inCISystem)
+ break;
+ }
+}
+
+QString tst_QAudioSource::formatToFileName(const QAudioFormat &format)
+{
+ return QStringLiteral("%1_%2_%3")
+ .arg(format.sampleRate())
+ .arg(format.bytesPerSample())
+ .arg(format.channelCount());
+}
+
+void tst_QAudioSource::initTestCase()
+{
+#ifdef Q_OS_ANDROID
+ // The test might fail because libOpenSLES cannot create AudioRecorder for that emulator. The
+ // Android documentation states that the emulator doesn't support this at all all
+ // https://developer.android.com/media/platform/mediarecorder. However, in practice this test
+ // fails only prior to Android 10.
+ if (QNativeInterface::QAndroidApplication::sdkVersion() < __ANDROID_API_Q__)
+ QSKIP("Emulated Android version doesn't support audio recording");
+#endif
+
+ m_inCISystem = qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci";
+
+ if (m_inCISystem)
+ QSKIP("SKIP initTestCase on CI. To be fixed");
+
+ // Only perform tests if audio input device exists
+ const QList<QAudioDevice> devices = QMediaDevices::audioInputs();
+
+ if (devices.size() <= 0)
+ QSKIP("No audio backend");
+
+ audioDevice = QMediaDevices::defaultAudioInput();
+
+
+ QAudioFormat format;
+ format.setChannelCount(1);
+
+ if (audioDevice.isFormatSupported(audioDevice.preferredFormat())) {
+ if (format.sampleFormat() == QAudioFormat::Int16)
+ testFormats.append(audioDevice.preferredFormat());
+ }
+
+ // PCM 11025 mono S16LE
+ format.setSampleRate(11025);
+ format.setSampleFormat(QAudioFormat::Int16);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 22050 mono S16LE
+ format.setSampleRate(22050);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 22050 stereo S16LE
+ format.setChannelCount(2);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 44100 stereo S16LE
+ format.setSampleRate(44100);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ // PCM 48000 stereo S16LE
+ format.setSampleRate(48000);
+ if (audioDevice.isFormatSupported(format))
+ testFormats.append(format);
+
+ QVERIFY(testFormats.size());
+
+ const QChar slash = QLatin1Char('/');
+ QString temporaryPattern = QDir::tempPath();
+ if (!temporaryPattern.endsWith(slash))
+ temporaryPattern += slash;
+ temporaryPattern += "tst_qaudioinputXXXXXX";
+ m_temporaryDir.reset(new QTemporaryDir(temporaryPattern));
+ m_temporaryDir->setAutoRemove(true);
+ QVERIFY(m_temporaryDir->isValid());
+
+ const QString temporaryAudioPath = m_temporaryDir->path() + slash;
+ for (const QAudioFormat &format : std::as_const(testFormats)) {
+ const QString fileName = temporaryAudioPath + formatToFileName(format) + QStringLiteral(".wav");
+ audioFiles.append(FilePtr::create(fileName));
+ }
+}
+
+void tst_QAudioSource::format()
+{
+ QAudioSource audioInput(audioDevice.preferredFormat(), this);
+
+ QAudioFormat requested = audioDevice.preferredFormat();
+ QAudioFormat actual = audioInput.format();
+
+ QVERIFY2((requested.channelCount() == actual.channelCount()),
+ QStringLiteral("channels: requested=%1, actual=%2")
+ .arg(requested.channelCount())
+ .arg(actual.channelCount())
+ .toUtf8()
+ .constData());
+ QVERIFY2((requested.sampleRate() == actual.sampleRate()),
+ QStringLiteral("sampleRate: requested=%1, actual=%2")
+ .arg(requested.sampleRate())
+ .arg(actual.sampleRate())
+ .toUtf8()
+ .constData());
+ QVERIFY2((requested.sampleFormat() == actual.sampleFormat()),
+ QStringLiteral("sampleFormat: requested=%1, actual=%2")
+ .arg((ushort)requested.sampleFormat())
+ .arg((ushort)actual.sampleFormat())
+ .toUtf8()
+ .constData());
+ QCOMPARE(actual, requested);
+}
+
+void tst_QAudioSource::invalidFormat_data()
+{
+ QTest::addColumn<QAudioFormat>("invalidFormat");
+
+ QAudioFormat format;
+
+ QTest::newRow("Null Format")
+ << format;
+
+ format = audioDevice.preferredFormat();
+ format.setChannelCount(0);
+ QTest::newRow("Channel count 0")
+ << format;
+
+ format = audioDevice.preferredFormat();
+ format.setSampleRate(0);
+ QTest::newRow("Sample rate 0")
+ << format;
+
+ format = audioDevice.preferredFormat();
+ format.setSampleFormat(QAudioFormat::Unknown);
+ QTest::newRow("Sample size 0")
+ << format;
+}
+
+void tst_QAudioSource::invalidFormat()
+{
+ QFETCH(QAudioFormat, invalidFormat);
+
+ QVERIFY2(!audioDevice.isFormatSupported(invalidFormat),
+ "isFormatSupported() is returning true on an invalid format");
+
+ QAudioSource audioInput(invalidFormat, this);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ audioInput.start();
+
+ // Check that error is raised
+ QTRY_VERIFY2((audioInput.error() == QAudio::OpenError),"error() was not set to QAudio::OpenError after start()");
+}
+
+void tst_QAudioSource::bufferSize()
+{
+ QAudioSource audioInput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
+
+ audioInput.setBufferSize(512);
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize(512)");
+ QVERIFY2((audioInput.bufferSize() == 512),
+ QStringLiteral("bufferSize: requested=512, actual=%2")
+ .arg(audioInput.bufferSize())
+ .toUtf8()
+ .constData());
+
+ audioInput.setBufferSize(4096);
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize(4096)");
+ QVERIFY2((audioInput.bufferSize() == 4096),
+ QStringLiteral("bufferSize: requested=4096, actual=%2")
+ .arg(audioInput.bufferSize())
+ .toUtf8()
+ .constData());
+
+ audioInput.setBufferSize(8192);
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize(8192)");
+ QVERIFY2((audioInput.bufferSize() == 8192),
+ QStringLiteral("bufferSize: requested=8192, actual=%2")
+ .arg(audioInput.bufferSize())
+ .toUtf8()
+ .constData());
+}
+
+void tst_QAudioSource::stopWhileStopped()
+{
+ // Calls QAudioSource::stop() when object is already in StoppedState
+ // Checks that
+ // - No state change occurs
+ // - No error is raised (QAudioSource::error() returns QAudio::NoError)
+
+ QAudioSource audioInput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+ audioInput.stop();
+
+ // Check that no state transition occurred
+ QVERIFY2((stateSignal.size() == 0), "stop() while stopped is emitting a signal and it shouldn't");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
+}
+
+void tst_QAudioSource::suspendWhileStopped()
+{
+ // Calls QAudioSource::suspend() when object is already in StoppedState
+ // Checks that
+ // - No state change occurs
+ // - No error is raised (QAudioSource::error() returns QAudio::NoError)
+
+ QAudioSource audioInput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+ audioInput.suspend();
+
+ // Check that no state transition occurred
+ QVERIFY2((stateSignal.size() == 0), "stop() while suspended is emitting a signal and it shouldn't");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
+}
+
+void tst_QAudioSource::resumeWhileStopped()
+{
+ // Calls QAudioSource::resume() when object is already in StoppedState
+ // Checks that
+ // - No state change occurs
+ // - No error is raised (QAudioSource::error() returns QAudio::NoError)
+
+ QAudioSource audioInput(audioDevice.preferredFormat(), this);
+
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+ audioInput.resume();
+
+ // Check that no state transition occurred
+ QVERIFY2((stateSignal.size() == 0), "resume() while stopped is emitting a signal and it shouldn't");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after resume()");
+}
+
+void tst_QAudioSource::pull()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSource audioInput(audioFormat, this);
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::WriteOnly);
+ QWaveDecoder waveDecoder(audioFile.data(), audioFormat);
+ if (!waveDecoder.open(QIODevice::WriteOnly)) {
+ waveDecoder.close();
+ audioFile->close();
+ QSKIP("Audio format not supported for writing to WAV file.");
+ }
+ QCOMPARE(waveDecoder.size(), QWaveDecoder::headerLength());
+
+ audioInput.start(audioFile.data());
+
+ // Check that QAudioSource immediately transitions to ActiveState or IdleState
+ QTRY_VERIFY2((stateSignal.size() > 0),"didn't emit signals on start()");
+ QVERIFY2((audioInput.state() == QAudio::ActiveState || audioInput.state() == QAudio::IdleState),
+ "didn't transition to ActiveState or IdleState after start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTRY_VERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+ QTRY_VERIFY2((audioInput.processedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+
+ // Allow some recording to happen
+ QTest::qWait(3000); // 3 seconds should be plenty
+
+ stateSignal.clear();
+
+ qint64 processedUs = audioInput.processedUSecs();
+ QVERIFY2(qTolerantCompare(processedUs, 3000000LL),
+ QStringLiteral(
+ "processedUSecs() doesn't fall in acceptable range, should be 3000000 (%1)")
+ .arg(processedUs)
+ .toUtf8()
+ .constData());
+
+ audioInput.stop();
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ //QWaveHeader::writeDataLength(*audioFile, audioFile->pos() - WavHeader::headerLength());
+ //waveDecoder.writeDataLength();
+ waveDecoder.close();
+ audioFile->close();
+
+}
+
+void tst_QAudioSource::pullSuspendResume()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSource audioInput(audioFormat, this);
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::WriteOnly);
+ QWaveDecoder waveDecoder(audioFile.get(), audioFormat);
+ if (!waveDecoder.open(QIODevice::WriteOnly)) {
+ waveDecoder.close();
+ audioFile->close();
+ QSKIP("Audio format not supported for writing to WAV file.");
+ }
+ QCOMPARE(waveDecoder.size(), QWaveDecoder::headerLength());
+
+ audioInput.start(audioFile.data());
+
+ // Check that QAudioSource immediately transitions to ActiveState or IdleState
+ QTRY_VERIFY2((stateSignal.size() > 0),"didn't emit signals on start()");
+ QVERIFY2((audioInput.state() == QAudio::ActiveState || audioInput.state() == QAudio::IdleState),
+ "didn't transition to ActiveState or IdleState after start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTRY_VERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+ QTRY_VERIFY2((audioInput.processedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+
+ // Allow some recording to happen
+ QTest::qWait(3000); // 3 seconds should be plenty
+
+ QVERIFY2((audioInput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after some recording");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after some recording");
+
+ stateSignal.clear();
+
+ audioInput.suspend();
+
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral(
+ "didn't emit SuspendedState signal after suspend(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::SuspendedState), "didn't transitions to SuspendedState after stop()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ stateSignal.clear();
+
+ // Check that only 'elapsed', and not 'processed' increases while suspended
+ qint64 elapsedUs = audioInput.elapsedUSecs();
+ qint64 processedUs = audioInput.processedUSecs();
+ QVERIFY2(qTolerantCompare(processedUs, 3000000LL),
+ QStringLiteral(
+ "processedUSecs() doesn't fall in acceptable range, should be 3000000 (%1)")
+ .arg(processedUs)
+ .toUtf8()
+ .constData());
+ QTRY_VERIFY(audioInput.elapsedUSecs() > elapsedUs);
+ QVERIFY(audioInput.processedUSecs() == processedUs);
+
+ audioInput.resume();
+
+ // Check that QAudioSource immediately transitions to ActiveState
+ QTRY_VERIFY2((stateSignal.size() == 1),
+ QStringLiteral("didn't emit signal after resume(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
+ stateSignal.clear();
+
+ audioInput.stop();
+ QTest::qWait(40);
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ //WavHeader::writeDataLength(*audioFile,audioFile->pos()-WavHeader::headerLength());
+ //waveDecoder.writeDataLength();
+ waveDecoder.close();
+ audioFile->close();
+}
+
+void tst_QAudioSource::push()
+{
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+
+ QAudioSource audioInput(audioFormat, this);
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::WriteOnly);
+ QWaveDecoder waveDecoder(audioFile.get(), audioFormat);
+ if (!waveDecoder.open(QIODevice::WriteOnly)) {
+ waveDecoder.close();
+ audioFile->close();
+ QSKIP("Audio format not supported for writing to WAV file.");
+ }
+ QCOMPARE(waveDecoder.size(), QWaveDecoder::headerLength());
+
+ // Set a large buffer to avoid underruns during QTest::qWaits
+ audioInput.setBufferSize(audioFormat.bytesForDuration(100000));
+
+ QIODevice* feed = audioInput.start();
+
+ // Check that QAudioSource immediately transitions to IdleState
+ QTRY_VERIFY2((stateSignal.size() == 1),"didn't emit IdleState signal on start()");
+ QVERIFY2((audioInput.state() == QAudio::IdleState),
+ "didn't transition to IdleState after start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTest::qWait(40);
+ QVERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+
+ qint64 totalBytesRead = 0;
+ bool firstBuffer = true;
+ qint64 len = audioFormat.sampleRate()*audioFormat.bytesPerFrame()/2; // .5 seconds
+ while (totalBytesRead < len) {
+ QTRY_VERIFY_WITH_TIMEOUT(audioInput.bytesAvailable() > 0, 1000);
+ QByteArray buffer = feed->readAll();
+ audioFile->write(buffer);
+ totalBytesRead += buffer.size();
+ if (firstBuffer && buffer.size()) {
+ // Check for transition to ActiveState when data is provided
+ QTRY_VERIFY2((stateSignal.size() == 1),"didn't emit ActiveState signal on data");
+ QVERIFY2((audioInput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after data");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ firstBuffer = false;
+ }
+ }
+
+ stateSignal.clear();
+
+ qint64 processedUs = audioInput.processedUSecs();
+
+ audioInput.stop();
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2(qTolerantCompare(processedUs, 500000LL),
+ QStringLiteral(
+ "processedUSecs() doesn't fall in acceptable range, should be 500000 (%1)")
+ .arg(processedUs)
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ //WavHeader::writeDataLength(*audioFile,audioFile->pos()-WavHeader::headerLength());
+ //waveDecoder.writeDataLength();
+ waveDecoder.close();
+ audioFile->close();
+}
+
+void tst_QAudioSource::pushSuspendResume()
+{
+#ifdef Q_OS_LINUX
+ if (m_inCISystem)
+ QSKIP("QTBUG-26504 Fails 20% of time with pulseaudio backend");
+#endif
+ QFETCH(FilePtr, audioFile);
+ QFETCH(QAudioFormat, audioFormat);
+ QAudioSource audioInput(audioFormat, this);
+
+ audioInput.setBufferSize(audioFormat.bytesForDuration(100000));
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioFile->close();
+ audioFile->open(QIODevice::WriteOnly);
+ QWaveDecoder waveDecoder(audioFile.get(), audioFormat);
+ if (!waveDecoder.open(QIODevice::WriteOnly)) {
+ waveDecoder.close();
+ audioFile->close();
+ QSKIP("Audio format not supported for writing to WAV file.");
+ }
+ QCOMPARE(waveDecoder.size(), QWaveDecoder::headerLength());
+
+ QIODevice* feed = audioInput.start();
+
+ // Check that QAudioSource immediately transitions to IdleState
+ QTRY_VERIFY2((stateSignal.size() == 1),"didn't emit IdleState signal on start()");
+ QVERIFY2((audioInput.state() == QAudio::IdleState),
+ "didn't transition to IdleState after start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ // Check that 'elapsed' increases
+ QTRY_VERIFY2((audioInput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
+
+ qint64 totalBytesRead = 0;
+ bool firstBuffer = true;
+ qint64 len = audioFormat.sampleRate() * audioFormat.bytesPerFrame() / 2; // .5 seconds
+ while (totalBytesRead < len) {
+ QTRY_VERIFY_WITH_TIMEOUT(audioInput.bytesAvailable() > 0, 1000);
+ auto buffer = feed->readAll();
+ audioFile->write(buffer);
+ totalBytesRead += buffer.size();
+ if (firstBuffer && buffer.size()) {
+ // Check for transition to ActiveState when data is provided
+ QTRY_VERIFY2((stateSignal.size() == 1),"didn't emit ActiveState signal on data");
+ QVERIFY2((audioInput.state() == QAudio::ActiveState),
+ "didn't transition to ActiveState after data");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ firstBuffer = false;
+ }
+ }
+ stateSignal.clear();
+
+ audioInput.suspend();
+
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral(
+ "didn't emit SuspendedState signal after suspend(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::SuspendedState), "didn't transitions to SuspendedState after stop()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
+ stateSignal.clear();
+
+ // Check that only 'elapsed', and not 'processed' increases while suspended
+ qint64 elapsedUs = audioInput.elapsedUSecs();
+ qint64 processedUs = audioInput.processedUSecs();
+ QTRY_VERIFY(audioInput.elapsedUSecs() > elapsedUs);
+ QVERIFY(audioInput.processedUSecs() == processedUs);
+
+ // Drain any data, in case we run out of space when resuming
+ while (feed->readAll().size() > 0)
+ ;
+ QCOMPARE(audioInput.bytesAvailable(), 0);
+
+ audioInput.resume();
+
+ // Check that QAudioSource immediately transitions to Active or IdleState
+ QTRY_VERIFY2((stateSignal.size() > 0),"didn't emit signals on resume()");
+ QVERIFY2((audioInput.state() == QAudio::ActiveState || audioInput.state() == QAudio::IdleState),
+ "didn't transition to ActiveState or IdleState after resume()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
+
+ stateSignal.clear();
+
+ // Read another seconds worth
+ totalBytesRead = 0;
+ firstBuffer = true;
+ while (totalBytesRead < len && audioInput.state() != QAudio::StoppedState) {
+ QTRY_VERIFY(audioInput.bytesAvailable() > 0);
+ auto buffer = feed->readAll();
+ audioFile->write(buffer);
+ totalBytesRead += buffer.size();
+ }
+ stateSignal.clear();
+
+ processedUs = audioInput.processedUSecs();
+
+ audioInput.stop();
+ QTRY_VERIFY2(
+ (stateSignal.size() == 1),
+ QStringLiteral("didn't emit StoppedState signal after stop(), got %1 signals instead")
+ .arg(stateSignal.size())
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
+
+ QVERIFY2(qTolerantCompare(processedUs, 1000000LL),
+ QStringLiteral(
+ "processedUSecs() doesn't fall in acceptable range, should be 2040000 (%1)")
+ .arg(processedUs)
+ .toUtf8()
+ .constData());
+ QVERIFY2((audioInput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
+
+ //WavHeader::writeDataLength(*audioFile,audioFile->pos()-WavHeader::headerLength());
+ //waveDecoder.writeDataLength();
+ waveDecoder.close();
+ audioFile->close();
+}
+
+void tst_QAudioSource::reset()
+{
+ QFETCH(QAudioFormat, audioFormat);
+
+ // Try both push/pull.. the vagaries of Active vs Idle are tested elsewhere
+ {
+ QAudioSource audioInput(audioFormat, this);
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ QIODevice* device = audioInput.start();
+ // Check that QAudioSource immediately transitions to IdleState
+ QTRY_VERIFY2((stateSignal.size() == 1),"didn't emit IdleState signal on start()");
+ QVERIFY2((audioInput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ QTRY_VERIFY2_WITH_TIMEOUT((audioInput.bytesAvailable() > 0), "no bytes available after starting", 10000);
+
+ // Trigger a read
+ QByteArray data = device->readAll();
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ audioInput.reset();
+ QTRY_VERIFY2((stateSignal.size() == 1),"didn't emit StoppedState signal after reset()");
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after reset()");
+ QVERIFY2((audioInput.bytesAvailable() == 0), "buffer not cleared after reset()");
+ }
+
+ {
+ QAudioSource audioInput(audioFormat, this);
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+
+ QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged);
+
+ // Check that we are in the default state before calling start
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
+ QVERIFY2((audioInput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
+
+ audioInput.start(&buffer);
+
+ // Check that QAudioSource immediately transitions to ActiveState
+ QTRY_VERIFY2((stateSignal.size() >= 1),"didn't emit state changed signal on start()");
+ QTRY_VERIFY2((audioInput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
+ QVERIFY2((audioInput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
+ stateSignal.clear();
+
+ audioInput.reset();
+ QTRY_VERIFY2((stateSignal.size() >= 1),"didn't emit StoppedState signal after reset()");
+ QVERIFY2((audioInput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after reset()");
+ QVERIFY2((audioInput.bytesAvailable() == 0), "buffer not cleared after reset()");
+ }
+}
+
+void tst_QAudioSource::volume()
+{
+ QFETCH(QAudioFormat, audioFormat);
+
+ const qreal half(0.5f);
+ const qreal one(1.0f);
+
+ QAudioSource audioInput(audioFormat, this);
+
+ qreal volume = audioInput.volume();
+ audioInput.setVolume(half);
+ QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 5);
+
+ audioInput.setVolume(one);
+ QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 10);
+
+ audioInput.setVolume(half);
+ audioInput.start();
+ QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 5);
+ audioInput.setVolume(one);
+ QTRY_VERIFY(qRound(audioInput.volume()*10.0f) == 10);
+
+ audioInput.setVolume(volume);
+}
+
+QTEST_MAIN(tst_QAudioSource)
+
+#include "tst_qaudiosource.moc"
diff --git a/tests/auto/integration/qcamerabackend/BLACKLIST b/tests/auto/integration/qcamerabackend/BLACKLIST
new file mode 100644
index 000000000..e958c2103
--- /dev/null
+++ b/tests/auto/integration/qcamerabackend/BLACKLIST
@@ -0,0 +1,12 @@
+ci
+
+[testCameraCaptureMetadata]
+osx
+ios
+
+[testCameraStartParallel]
+ios
+
+[testCameraFormat]
+osx
+ios
diff --git a/tests/auto/integration/qcamerabackend/CMakeLists.txt b/tests/auto/integration/qcamerabackend/CMakeLists.txt
new file mode 100644
index 000000000..07a6a4cae
--- /dev/null
+++ b/tests/auto/integration/qcamerabackend/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qcamerabackend.pro.
+
+#####################################################################
+## tst_qcamerabackend Test:
+#####################################################################
+
+qt_internal_add_test(tst_qcamerabackend
+ SOURCES
+ tst_qcamerabackend.cpp
+ ../shared/mediabackendutils.h
+ INCLUDE_DIRECTORIES
+ ../shared/
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ Qt::Widgets
+)
+
+# special case begin
+if(APPLE)
+ set_property(TARGET tst_qcamerabackend PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist")
+ set_property(TARGET tst_qcamerabackend PROPERTY PROPERTY MACOSX_BUNDLE TRUE)
+endif()
+# special case end
diff --git a/tests/auto/integration/qcamerabackend/Info.plist b/tests/auto/integration/qcamerabackend/Info.plist
new file mode 100644
index 000000000..590080a1f
--- /dev/null
+++ b/tests/auto/integration/qcamerabackend/Info.plist
@@ -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 Test</string>
+ <key>NSMicrophoneUsageDescription</key>
+ <string>Qt Multimedia Test</string>
+
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+</dict>
+</plist>
diff --git a/tests/auto/integration/qcamerabackend/qcamerabackend.pro b/tests/auto/integration/qcamerabackend/qcamerabackend.pro
deleted file mode 100644
index b5f079008..000000000
--- a/tests/auto/integration/qcamerabackend/qcamerabackend.pro
+++ /dev/null
@@ -1,10 +0,0 @@
-TARGET = tst_qcamerabackend
-
-# DirectShow plugin requires widgets.
-QT += multimedia-private widgets testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-SOURCES += tst_qcamerabackend.cpp
-
diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
index c49236f43..fd6d819eb 100644
--- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
+++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp
@@ -1,54 +1,32 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-//TESTED_COMPONENT=src/multimedia
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QtGui/QImageReader>
+#include <QtCore/qurl.h>
+#include <QtCore/qlocale.h>
#include <QDebug>
+#include <QVideoSink>
+#include <QMediaPlayer>
-#include <qabstractvideosurface.h>
-#include <qcameracontrol.h>
-#include <qcameralockscontrol.h>
-#include <qcameraexposurecontrol.h>
-#include <qcameraflashcontrol.h>
-#include <qcamerafocuscontrol.h>
-#include <qcameraimagecapturecontrol.h>
-#include <qimageencodercontrol.h>
-#include <qcameraimageprocessingcontrol.h>
-#include <qcameracapturebufferformatcontrol.h>
-#include <qcameracapturedestinationcontrol.h>
-#include <qmediaservice.h>
+#include <private/qplatformcamera_p.h>
+#include <private/qplatformimagecapture_p.h>
+#include <private/qplatformmediaintegration_p.h>
#include <qcamera.h>
-#include <qcamerainfo.h>
-#include <qcameraimagecapture.h>
-#include <qvideorenderercontrol.h>
-#include <private/qmediaserviceprovider_p.h>
+#include <qcameradevice.h>
+#include <qimagecapture.h>
+#include <qmediacapturesession.h>
+#include <qobject.h>
+#include <qmediadevices.h>
+#include <qmediarecorder.h>
+#include <qmediaplayer.h>
+#include <qaudiooutput.h>
+
+#ifdef Q_OS_DARWIN
+#include <QtCore/private/qcore_mac_p.h>
+#endif
+
+#include <mediabackendutils.h>
QT_USE_NAMESPACE
@@ -68,14 +46,13 @@ public slots:
void cleanupTestCase();
private slots:
- void testCameraInfo();
- void testCtorWithDevice();
- void testCtorWithCameraInfo();
+ void testCameraDevice();
+ void testCtorWithCameraDevice();
void testCtorWithPosition();
- void testCameraStates();
- void testCameraStartError();
- void testCaptureMode();
+ void testCameraActive();
+ void testCameraStartParallel();
+ void testCameraFormat();
void testCameraCapture();
void testCaptureToBuffer();
void testCameraCaptureMetadata();
@@ -84,285 +61,318 @@ private slots:
void testVideoRecording_data();
void testVideoRecording();
+
+ void testNativeMetadata();
+
+ void multipleCameraSet();
+
private:
+ bool noCamera = false;
+};
+
+class TestVideoFormat : public QVideoSink
+{
+ Q_OBJECT
+public:
+ explicit TestVideoFormat(const QCameraFormat &format)
+ : formatMismatch(0),
+ cameraFormat(format)
+ {
+ connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoFormat::checkVideoFrameFormat);
+ }
+
+ void setCameraFormatToTest(const QCameraFormat &format)
+ {
+ formatMismatch = -1;
+ cameraFormat = format;
+ }
+
+ int formatMismatch = -1;
+
+private:
+ QCameraFormat cameraFormat;
+
+public Q_SLOTS:
+ void checkVideoFrameFormat(const QVideoFrame &frame)
+ {
+ QVideoFrameFormat surfaceFormat = frame.surfaceFormat();
+ if (surfaceFormat.pixelFormat() == cameraFormat.pixelFormat()
+ && surfaceFormat.frameSize() == cameraFormat.resolution()) {
+ formatMismatch = 0;
+#ifdef Q_OS_ANDROID
+ } else if ((surfaceFormat.pixelFormat() == QVideoFrameFormat::Format_YUV420P
+ || surfaceFormat.pixelFormat() == QVideoFrameFormat::Format_NV12)
+ && (cameraFormat.pixelFormat() == QVideoFrameFormat::Format_YUV420P
+ || cameraFormat.pixelFormat() == QVideoFrameFormat::Format_NV12)
+ && surfaceFormat.frameSize() == cameraFormat.resolution()) {
+ formatMismatch = 0;
+#endif
+ } else {
+ formatMismatch = 1;
+ }
+ }
};
void tst_QCameraBackend::initTestCase()
{
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-118571");
+#endif
QCamera camera;
- if (!camera.isAvailable())
- QSKIP("Camera is not available");
+ noCamera = !camera.isAvailable();
}
void tst_QCameraBackend::cleanupTestCase()
{
}
-void tst_QCameraBackend::testCameraInfo()
+void tst_QCameraBackend::testCameraDevice()
{
- int deviceCount = QMediaServiceProvider::defaultServiceProvider()->devices(QByteArray(Q_MEDIASERVICE_CAMERA)).count();
- const QList<QCameraInfo> cameras = QCameraInfo::availableCameras();
- QCOMPARE(cameras.count(), deviceCount);
+ const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
if (cameras.isEmpty()) {
- QVERIFY(QCameraInfo::defaultCamera().isNull());
+ QVERIFY(noCamera);
+ QVERIFY(QMediaDevices::defaultVideoInput().isNull());
QSKIP("Camera selection is not supported");
}
+ QVERIFY(!noCamera);
- for (const QCameraInfo &info : cameras) {
- QVERIFY(!info.deviceName().isEmpty());
+ for (const QCameraDevice &info : cameras) {
+ QVERIFY(!info.id().isEmpty());
QVERIFY(!info.description().isEmpty());
- QVERIFY(info.orientation() % 90 == 0);
}
}
-void tst_QCameraBackend::testCtorWithDevice()
+void tst_QCameraBackend::testCtorWithCameraDevice()
{
- const auto availableCameras = QCameraInfo::availableCameras();
- if (availableCameras.isEmpty())
- QSKIP("Camera selection not supported");
-
- QCamera *camera = new QCamera(availableCameras.first().deviceName().toLatin1());
- QCOMPARE(camera->error(), QCamera::NoError);
- delete camera;
+ if (noCamera) {
+ // only verify that we get an error trying to create a camera
+ QCamera camera;
+ QCOMPARE(camera.error(), QCamera::CameraError);
+ QVERIFY(camera.cameraDevice().isNull());
- //loading non existing camera should fail
- camera = new QCamera(QUuid::createUuid().toByteArray());
- QCOMPARE(camera->error(), QCamera::ServiceMissingError);
-
- delete camera;
-}
+ QSKIP("No camera available");
+ }
-void tst_QCameraBackend::testCtorWithCameraInfo()
-{
- if (QCameraInfo::availableCameras().isEmpty())
- QSKIP("Camera selection not supported");
+ QCameraDevice defaultCamera = QMediaDevices::defaultVideoInput();
{
- QCameraInfo info = QCameraInfo::defaultCamera();
- QCamera camera(info);
+ // should use default camera
+ QCamera camera;
QCOMPARE(camera.error(), QCamera::NoError);
- QCOMPARE(QCameraInfo(camera), info);
+ QVERIFY(!camera.cameraDevice().isNull());
+ QCOMPARE(camera.cameraDevice(), defaultCamera);
}
+
{
- QCameraInfo info = QCameraInfo::availableCameras().first();
- QCamera camera(info);
+ // should use default camera
+ QCamera camera(QCameraDevice{});
QCOMPARE(camera.error(), QCamera::NoError);
- QCOMPARE(QCameraInfo(camera), info);
+ QVERIFY(!camera.cameraDevice().isNull());
+ QCOMPARE(camera.cameraDevice(), defaultCamera);
}
+
{
- // loading an invalid CameraInfo should fail
- QCamera *camera = new QCamera(QCameraInfo());
- QCOMPARE(camera->error(), QCamera::ServiceMissingError);
- QVERIFY(QCameraInfo(*camera).isNull());
- delete camera;
+ QCamera camera(defaultCamera);
+ QCOMPARE(camera.error(), QCamera::NoError);
+ QCOMPARE(camera.cameraDevice(), defaultCamera);
}
{
- // loading non existing camera should fail
- QCamera camera(QCameraInfo(QUuid::createUuid().toByteArray()));
- QCOMPARE(camera.error(), QCamera::ServiceMissingError);
- QVERIFY(QCameraInfo(camera).isNull());
+ QCameraDevice info = QMediaDevices::videoInputs().first();
+ QCamera camera(info);
+ QCOMPARE(camera.error(), QCamera::NoError);
+ QCOMPARE(camera.cameraDevice(), info);
}
}
void tst_QCameraBackend::testCtorWithPosition()
{
+ if (noCamera)
+ QSKIP("No camera available");
+
{
- QCamera camera(QCamera::UnspecifiedPosition);
+ QCamera camera(QCameraDevice::UnspecifiedPosition);
QCOMPARE(camera.error(), QCamera::NoError);
}
{
- QCamera camera(QCamera::FrontFace);
+ QCamera camera(QCameraDevice::FrontFace);
// even if no camera is available at this position, it should not fail
// and load the default camera
QCOMPARE(camera.error(), QCamera::NoError);
}
{
- QCamera camera(QCamera::BackFace);
+ QCamera camera(QCameraDevice::BackFace);
// even if no camera is available at this position, it should not fail
// and load the default camera
QCOMPARE(camera.error(), QCamera::NoError);
}
}
-void tst_QCameraBackend::testCameraStates()
+void tst_QCameraBackend::testCameraActive()
{
+ QMediaCaptureSession session;
QCamera camera;
- QCameraImageCapture imageCapture(&camera);
-
- QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error)));
- QSignalSpy stateChangedSignal(&camera, SIGNAL(stateChanged(QCamera::State)));
- QSignalSpy statusChangedSignal(&camera, SIGNAL(statusChanged(QCamera::Status)));
+ camera.setCameraDevice(QCameraDevice());
+ QImageCapture imageCapture;
+ session.setCamera(&camera);
+ session.setImageCapture(&imageCapture);
- QCOMPARE(camera.state(), QCamera::UnloadedState);
- QCOMPARE(camera.status(), QCamera::UnloadedStatus);
+ QSignalSpy errorSignal(&camera, &QCamera::errorOccurred);
+ QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged);
- camera.load();
- QCOMPARE(camera.state(), QCamera::LoadedState);
- QCOMPARE(stateChangedSignal.count(), 1);
- QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::LoadedState);
- QVERIFY(stateChangedSignal.count() > 0);
+ QCOMPARE(camera.isActive(), false);
- QTRY_COMPARE(camera.status(), QCamera::LoadedStatus);
- QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::LoadedStatus);
-
- camera.unload();
- QCOMPARE(camera.state(), QCamera::UnloadedState);
- QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::UnloadedState);
- QTRY_COMPARE(camera.status(), QCamera::UnloadedStatus);
- QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::UnloadedStatus);
+ if (noCamera)
+ QSKIP("No camera available");
+ camera.setCameraDevice(QMediaDevices::defaultVideoInput());
+ QCOMPARE(camera.error(), QCamera::NoError);
camera.start();
- QCOMPARE(camera.state(), QCamera::ActiveState);
- QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::ActiveState);
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
- QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::ActiveStatus);
+ QTRY_COMPARE(camera.isActive(), true);
+ QTRY_COMPARE(activeChangedSignal.size(), 1);
+ QCOMPARE(activeChangedSignal.last().first().value<bool>(), true);
camera.stop();
- QCOMPARE(camera.state(), QCamera::LoadedState);
- QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::LoadedState);
- QTRY_COMPARE(camera.status(), QCamera::LoadedStatus);
- QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::LoadedStatus);
-
- camera.unload();
- QCOMPARE(camera.state(), QCamera::UnloadedState);
- QCOMPARE(stateChangedSignal.last().first().value<QCamera::State>(), QCamera::UnloadedState);
- QTRY_COMPARE(camera.status(), QCamera::UnloadedStatus);
- QCOMPARE(statusChangedSignal.last().first().value<QCamera::Status>(), QCamera::UnloadedStatus);
+ QCOMPARE(camera.isActive(), false);
+ QCOMPARE(activeChangedSignal.last().first().value<bool>(), false);
QCOMPARE(camera.errorString(), QString());
- QCOMPARE(errorSignal.count(), 0);
}
-void tst_QCameraBackend::testCameraStartError()
+void tst_QCameraBackend::testCameraStartParallel()
{
- QCamera camera1(QCameraInfo::defaultCamera());
- QCamera camera2(QCameraInfo::defaultCamera());
+#ifdef Q_OS_ANDROID
+ QSKIP("Multi-camera feature is currently not supported on Android. "
+ "Cannot open same device twice.");
+#endif
+#ifdef Q_OS_LINUX
+ QSKIP("Multi-camera feature is currently not supported on Linux. "
+ "Cannot open same device twice.");
+#endif
+ if (noCamera)
+ QSKIP("No camera available");
+
+ QMediaCaptureSession session1;
+ QMediaCaptureSession session2;
+ QCamera camera1(QMediaDevices::defaultVideoInput());
+ QCamera camera2(QMediaDevices::defaultVideoInput());
+ session1.setCamera(&camera1);
+ session2.setCamera(&camera2);
QSignalSpy errorSpy1(&camera1, &QCamera::errorOccurred);
QSignalSpy errorSpy2(&camera2, &QCamera::errorOccurred);
camera1.start();
camera2.start();
- QCOMPARE(camera1.state(), QCamera::ActiveState);
- QTRY_COMPARE(camera1.status(), QCamera::ActiveStatus);
+ QCOMPARE(camera1.isActive(), true);
QCOMPARE(camera1.error(), QCamera::NoError);
- QCOMPARE(camera2.state(), QCamera::UnloadedState);
- QCOMPARE(camera2.status(), QCamera::UnloadedStatus);
- QCOMPARE(camera2.error(), QCamera::CameraError);
+ QCOMPARE(camera2.isActive(), true);
+ QCOMPARE(camera2.error(), QCamera::NoError);
- QCOMPARE(errorSpy1.count(), 0);
- QCOMPARE(errorSpy2.count(), 1);
+ QCOMPARE(errorSpy1.size(), 0);
+ QCOMPARE(errorSpy2.size(), 0);
}
-void tst_QCameraBackend::testCaptureMode()
+void tst_QCameraBackend::testCameraFormat()
{
QCamera camera;
+ QCameraDevice device = camera.cameraDevice();
+ auto videoFormats = device.videoFormats();
+ if (videoFormats.isEmpty())
+ QSKIP("No Camera available, skipping test.");
+ QCameraFormat cameraFormat = videoFormats.first();
+ QSignalSpy spy(&camera, &QCamera::cameraFormatChanged);
+ QVERIFY(spy.size() == 0);
+
+ QMediaCaptureSession session;
+ session.setCamera(&camera);
+ QVERIFY(videoFormats.size());
+ camera.setCameraFormat(cameraFormat);
+ QCOMPARE(camera.cameraFormat(), cameraFormat);
+ QVERIFY(spy.size() == 1);
+
+ TestVideoFormat videoFormatTester(cameraFormat);
+ session.setVideoOutput(&videoFormatTester);
+ camera.start();
+ QTRY_VERIFY(videoFormatTester.formatMismatch == 0);
- QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error)));
- QSignalSpy stateChangedSignal(&camera, SIGNAL(stateChanged(QCamera::State)));
- QSignalSpy captureModeSignal(&camera, SIGNAL(captureModeChanged(QCamera::CaptureModes)));
-
- QCOMPARE(camera.captureMode(), QCamera::CaptureStillImage);
-
- if (!camera.isCaptureModeSupported(QCamera::CaptureVideo)) {
- camera.setCaptureMode(QCamera::CaptureVideo);
- QCOMPARE(camera.captureMode(), QCamera::CaptureStillImage);
- QSKIP("Video capture not supported");
+ spy.clear();
+ camera.stop();
+ // Change camera format
+ if (videoFormats.size() > 1) {
+ QCameraFormat secondFormat = videoFormats.at(1);
+ camera.setCameraFormat(secondFormat);
+ QCOMPARE(camera.cameraFormat(), secondFormat);
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(camera.cameraFormat(), secondFormat);
+ videoFormatTester.setCameraFormatToTest(secondFormat);
+ camera.start();
+ QTRY_VERIFY(videoFormatTester.formatMismatch == 0);
+
+ // check that frame format is not same as previous camera format
+ videoFormatTester.setCameraFormatToTest(cameraFormat);
+ QTRY_VERIFY(videoFormatTester.formatMismatch == 1);
}
- camera.setCaptureMode(QCamera::CaptureVideo);
- QCOMPARE(camera.captureMode(), QCamera::CaptureVideo);
- QTRY_COMPARE(captureModeSignal.size(), 1);
- QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureVideo);
- captureModeSignal.clear();
-
- camera.load();
- QTRY_COMPARE(camera.status(), QCamera::LoadedStatus);
- //capture mode should still be video
- QCOMPARE(camera.captureMode(), QCamera::CaptureVideo);
-
- //it should be possible to switch capture mode in Loaded state
- camera.setCaptureMode(QCamera::CaptureStillImage);
- QTRY_COMPARE(captureModeSignal.size(), 1);
- QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureStillImage);
- captureModeSignal.clear();
-
- camera.setCaptureMode(QCamera::CaptureVideo);
- QTRY_COMPARE(captureModeSignal.size(), 1);
- QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureVideo);
- captureModeSignal.clear();
-
+ // Set null format
+ spy.clear();
+ camera.stop();
+ camera.setCameraFormat({});
+ QCOMPARE(spy.size(), 1);
+ videoFormatTester.setCameraFormatToTest({});
camera.start();
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
- //capture mode should still be video
- QCOMPARE(camera.captureMode(), QCamera::CaptureVideo);
-
- stateChangedSignal.clear();
- //it should be possible to switch capture mode in Active state
- camera.setCaptureMode(QCamera::CaptureStillImage);
- //camera may leave Active status, but should return to Active
- QTest::qWait(10); //camera may leave Active status async
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
- QCOMPARE(camera.captureMode(), QCamera::CaptureStillImage);
- QVERIFY2(stateChangedSignal.isEmpty(), "camera should not change the state during capture mode changes");
-
- QCOMPARE(captureModeSignal.size(), 1);
- QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureStillImage);
- captureModeSignal.clear();
-
- camera.setCaptureMode(QCamera::CaptureVideo);
- //camera may leave Active status, but should return to Active
- QTest::qWait(10); //camera may leave Active status async
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
- QCOMPARE(camera.captureMode(), QCamera::CaptureVideo);
-
- QVERIFY2(stateChangedSignal.isEmpty(), "camera should not change the state during capture mode changes");
-
- QCOMPARE(captureModeSignal.size(), 1);
- QCOMPARE(captureModeSignal.last().first().value<QCamera::CaptureModes>(), QCamera::CaptureVideo);
- captureModeSignal.clear();
-
+ // In case of a null format, the backend should have picked
+ // a decent format to render frames
+ QTRY_VERIFY(videoFormatTester.formatMismatch == 1);
camera.stop();
- QCOMPARE(camera.captureMode(), QCamera::CaptureVideo);
- camera.unload();
- QCOMPARE(camera.captureMode(), QCamera::CaptureVideo);
- QVERIFY2(errorSignal.isEmpty(), QString("Camera error: %1").arg(camera.errorString()).toLocal8Bit());
+ spy.clear();
+ // Shouldn't change anything as it's the same device
+ camera.setCameraDevice(device);
+ QCOMPARE(spy.size(), 0);
}
void tst_QCameraBackend::testCameraCapture()
{
+ QMediaCaptureSession session;
QCamera camera;
- QCameraImageCapture imageCapture(&camera);
+ QImageCapture imageCapture;
+ session.setCamera(&camera);
+ session.setImageCapture(&imageCapture);
+
//prevents camera to flash during the test
- camera.exposure()->setFlashMode(QCameraExposure::FlashOff);
+ camera.setFlashMode(QCamera::FlashOff);
QVERIFY(!imageCapture.isReadyForCapture());
- QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage)));
- QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString)));
- QSignalSpy errorSignal(&imageCapture, SIGNAL(error(int,QCameraImageCapture::Error,QString)));
+ QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured);
+ QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved);
+ QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred);
- imageCapture.capture();
+ imageCapture.captureToFile();
QTRY_COMPARE(errorSignal.size(), 1);
- QCOMPARE(imageCapture.error(), QCameraImageCapture::NotReadyError);
+ QCOMPARE(imageCapture.error(), QImageCapture::NotReadyError);
QCOMPARE(capturedSignal.size(), 0);
errorSignal.clear();
+ if (noCamera)
+ QSKIP("No camera available");
+
+ QVideoSink sink;
+ session.setVideoOutput(&sink);
camera.start();
QTRY_VERIFY(imageCapture.isReadyForCapture());
- QCOMPARE(camera.status(), QCamera::ActiveStatus);
+ QVERIFY(camera.isActive());
QCOMPARE(errorSignal.size(), 0);
- int id = imageCapture.capture();
+ int id = imageCapture.captureToFile();
- QTRY_VERIFY(!savedSignal.isEmpty());
+ QTRY_VERIFY_WITH_TIMEOUT(!savedSignal.isEmpty(), 8s);
QTRY_COMPARE(capturedSignal.size(), 1);
QCOMPARE(capturedSignal.last().first().toInt(), id);
QCOMPARE(errorSignal.size(), 0);
- QCOMPARE(imageCapture.error(), QCameraImageCapture::NoError);
+ QCOMPARE(imageCapture.error(), QImageCapture::NoError);
QCOMPARE(savedSignal.last().first().toInt(), id);
QString location = savedSignal.last().last().toString();
@@ -378,35 +388,25 @@ void tst_QCameraBackend::testCameraCapture()
void tst_QCameraBackend::testCaptureToBuffer()
{
- QCamera camera;
- QCameraImageCapture imageCapture(&camera);
- camera.exposure()->setFlashMode(QCameraExposure::FlashOff);
-
- camera.load();
+ if (noCamera)
+ QSKIP("No camera available");
- if (!imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer))
- QSKIP("Buffer capture not supported");
-
- QTRY_COMPARE(camera.status(), QCamera::LoadedStatus);
+ QMediaCaptureSession session;
+ QCamera camera;
+ QImageCapture imageCapture;
+ session.setCamera(&camera);
+ session.setImageCapture(&imageCapture);
- QVERIFY(imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToFile));
- QVERIFY(imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer));
- QVERIFY(imageCapture.isCaptureDestinationSupported(
- QCameraImageCapture::CaptureToBuffer | QCameraImageCapture::CaptureToFile));
+ camera.setFlashMode(QCamera::FlashOff);
- QSignalSpy destinationChangedSignal(&imageCapture, SIGNAL(captureDestinationChanged(QCameraImageCapture::CaptureDestinations)));
+ camera.setActive(true);
- QCOMPARE(imageCapture.captureDestination(), QCameraImageCapture::CaptureToFile);
- imageCapture.setCaptureDestination(QCameraImageCapture::CaptureToBuffer);
- QCOMPARE(imageCapture.captureDestination(), QCameraImageCapture::CaptureToBuffer);
- QCOMPARE(destinationChangedSignal.size(), 1);
- QCOMPARE(destinationChangedSignal.first().first().value<QCameraImageCapture::CaptureDestinations>(),
- QCameraImageCapture::CaptureToBuffer);
+ QTRY_VERIFY(camera.isActive());
- QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage)));
- QSignalSpy imageAvailableSignal(&imageCapture, SIGNAL(imageAvailable(int,QVideoFrame)));
- QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString)));
- QSignalSpy errorSignal(&imageCapture, SIGNAL(error(int,QCameraImageCapture::Error,QString)));
+ QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured);
+ QSignalSpy imageAvailableSignal(&imageCapture, &QImageCapture::imageAvailable);
+ QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved);
+ QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred);
camera.start();
QTRY_VERIFY(imageCapture.isReadyForCapture());
@@ -425,241 +425,340 @@ void tst_QCameraBackend::testCaptureToBuffer()
QCOMPARE(imageAvailableSignal.first().first().toInt(), id);
QVideoFrame frame = imageAvailableSignal.first().last().value<QVideoFrame>();
- QVERIFY(!frame.image().isNull());
+ QVERIFY(!frame.toImage().isNull());
frame = QVideoFrame();
capturedSignal.clear();
imageAvailableSignal.clear();
savedSignal.clear();
- if (imageCapture.supportedBufferFormats().contains(QVideoFrame::Format_UYVY)) {
- imageCapture.setBufferFormat(QVideoFrame::Format_UYVY);
- QCOMPARE(imageCapture.bufferFormat(), QVideoFrame::Format_UYVY);
-
- id = imageCapture.capture();
- QTRY_VERIFY(!imageAvailableSignal.isEmpty());
-
- QVERIFY(errorSignal.isEmpty());
- QVERIFY(!capturedSignal.isEmpty());
- QVERIFY(!imageAvailableSignal.isEmpty());
- QVERIFY(savedSignal.isEmpty());
-
- QTest::qWait(2000);
- QVERIFY(savedSignal.isEmpty());
-
- frame = imageAvailableSignal.first().last().value<QVideoFrame>();
- QVERIFY(frame.isValid());
-
- qDebug() << frame.pixelFormat();
- QCOMPARE(frame.pixelFormat(), QVideoFrame::Format_UYVY);
- QVERIFY(!frame.size().isEmpty());
- frame = QVideoFrame();
-
- capturedSignal.clear();
- imageAvailableSignal.clear();
- savedSignal.clear();
-
- imageCapture.setBufferFormat(QVideoFrame::Format_Jpeg);
- QCOMPARE(imageCapture.bufferFormat(), QVideoFrame::Format_Jpeg);
- }
-
QTRY_VERIFY(imageCapture.isReadyForCapture());
-
- //Try to capture to both buffer and file
- if (imageCapture.isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer | QCameraImageCapture::CaptureToFile)) {
- imageCapture.setCaptureDestination(QCameraImageCapture::CaptureToBuffer | QCameraImageCapture::CaptureToFile);
-
- int oldId = id;
- id = imageCapture.capture();
- QVERIFY(id != oldId);
- QTRY_VERIFY(!savedSignal.isEmpty());
-
- QVERIFY(errorSignal.isEmpty());
- QVERIFY(!capturedSignal.isEmpty());
- QVERIFY(!imageAvailableSignal.isEmpty());
- QVERIFY(!savedSignal.isEmpty());
-
- QCOMPARE(capturedSignal.first().first().toInt(), id);
- QCOMPARE(imageAvailableSignal.first().first().toInt(), id);
-
- frame = imageAvailableSignal.first().last().value<QVideoFrame>();
- QVERIFY(!frame.image().isNull());
-
- QString fileName = savedSignal.first().last().toString();
- QVERIFY(QFileInfo(fileName).exists());
- }
}
void tst_QCameraBackend::testCameraCaptureMetadata()
{
- QSKIP("Capture metadata is supported only on harmattan");
+ if (noCamera)
+ QSKIP("No camera available");
+ QMediaCaptureSession session;
QCamera camera;
- QCameraImageCapture imageCapture(&camera);
- camera.exposure()->setFlashMode(QCameraExposure::FlashOff);
+ QImageCapture imageCapture;
+ session.setCamera(&camera);
+ session.setImageCapture(&imageCapture);
+
+ camera.setFlashMode(QCamera::FlashOff);
+
+ QMediaMetaData referenceMetaData;
+ referenceMetaData.insert(QMediaMetaData::Title, QStringLiteral("Title"));
+ referenceMetaData.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::German));
+ referenceMetaData.insert(QMediaMetaData::Description, QStringLiteral("Description"));
+ imageCapture.setMetaData(referenceMetaData);
- QSignalSpy metadataSignal(&imageCapture, SIGNAL(imageMetadataAvailable(int,QString,QVariant)));
- QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString)));
+ QSignalSpy metadataSignal(&imageCapture, &QImageCapture::imageMetadataAvailable);
+ QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved);
camera.start();
QTRY_VERIFY(imageCapture.isReadyForCapture());
- int id = imageCapture.capture(QString::fromLatin1("/dev/null"));
+ QTemporaryDir dir;
+ auto tmpFile = dir.filePath("testImage");
+ int id = imageCapture.captureToFile(tmpFile);
QTRY_VERIFY(!savedSignal.isEmpty());
QVERIFY(!metadataSignal.isEmpty());
+
QCOMPARE(metadataSignal.first().first().toInt(), id);
+ QMediaMetaData receivedMetaData = metadataSignal.first()[1].value<QMediaMetaData>();
+
+ if (isGStreamerPlatform()) {
+ for (auto key : {
+ QMediaMetaData::Title,
+ QMediaMetaData::Language,
+ QMediaMetaData::Description,
+ })
+ QCOMPARE(receivedMetaData[key], referenceMetaData[key]);
+ QVERIFY(receivedMetaData[QMediaMetaData::Resolution].isValid());
+ }
}
void tst_QCameraBackend::testExposureCompensation()
{
- QSKIP("Capture exposure parameters are supported only on mobile platforms");
+ if (noCamera)
+ QSKIP("No camera available");
+ QMediaCaptureSession session;
QCamera camera;
- QCameraExposure *exposure = camera.exposure();
+ session.setCamera(&camera);
+
+ QSignalSpy exposureCompensationSignal(&camera, &QCamera::exposureCompensationChanged);
- QSignalSpy exposureCompensationSignal(exposure, SIGNAL(exposureCompensationChanged(qreal)));
+ // it should be possible to set exposure parameters in Unloaded state
+ QCOMPARE(camera.exposureCompensation(), 0.);
+ if (!(camera.supportedFeatures() & QCamera::Feature::ExposureCompensation))
+ return;
- //it should be possible to set exposure parameters in Unloaded state
- QCOMPARE(exposure->exposureCompensation()+1.0, 1.0);
- exposure->setExposureCompensation(1.0);
- QCOMPARE(exposure->exposureCompensation(), 1.0);
- QTRY_COMPARE(exposureCompensationSignal.count(), 1);
+ camera.setExposureCompensation(1.0);
+ QCOMPARE(camera.exposureCompensation(), 1.0);
+ QTRY_COMPARE(exposureCompensationSignal.size(), 1);
QCOMPARE(exposureCompensationSignal.last().first().toReal(), 1.0);
//exposureCompensationChanged should not be emitted when value is not changed
- exposure->setExposureCompensation(1.0);
+ camera.setExposureCompensation(1.0);
QTest::qWait(50);
- QCOMPARE(exposureCompensationSignal.count(), 1);
-
- //exposure compensation should be preserved during load/start
- camera.load();
- QTRY_COMPARE(camera.status(), QCamera::LoadedStatus);
-
- QCOMPARE(exposure->exposureCompensation(), 1.0);
-
- exposureCompensationSignal.clear();
- exposure->setExposureCompensation(-1.0);
- QCOMPARE(exposure->exposureCompensation(), -1.0);
- QTRY_COMPARE(exposureCompensationSignal.count(), 1);
- QCOMPARE(exposureCompensationSignal.last().first().toReal(), -1.0);
+ QCOMPARE(exposureCompensationSignal.size(), 1);
+ //exposure compensation should be preserved during start
camera.start();
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
+ QTRY_VERIFY(camera.isActive());
- QCOMPARE(exposure->exposureCompensation(), -1.0);
+ QCOMPARE(camera.exposureCompensation(), 1.0);
exposureCompensationSignal.clear();
- exposure->setExposureCompensation(1.0);
- QCOMPARE(exposure->exposureCompensation(), 1.0);
- QTRY_COMPARE(exposureCompensationSignal.count(), 1);
- QCOMPARE(exposureCompensationSignal.last().first().toReal(), 1.0);
+ camera.setExposureCompensation(-1.0);
+ QCOMPARE(camera.exposureCompensation(), -1.0);
+ QTRY_COMPARE(exposureCompensationSignal.size(), 1);
+ QCOMPARE(exposureCompensationSignal.last().first().toReal(), -1.0);
}
void tst_QCameraBackend::testExposureMode()
{
- QSKIP("Capture exposure parameters are supported only on mobile platforms");
+ if (noCamera)
+ QSKIP("No camera available");
QCamera camera;
- QCameraExposure *exposure = camera.exposure();
- QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureAuto);
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto);
// Night
- exposure->setExposureMode(QCameraExposure::ExposureNight);
- QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureNight);
- camera.start();
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
- QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureNight);
+ if (camera.isExposureModeSupported(QCamera::ExposureNight)) {
+ camera.setExposureMode(QCamera::ExposureNight);
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureNight);
+ camera.start();
+ QVERIFY(camera.isActive());
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureNight);
+ }
- camera.unload();
- QTRY_COMPARE(camera.status(), QCamera::UnloadedStatus);
+ camera.stop();
+ QTRY_VERIFY(!camera.isActive());
// Auto
- exposure->setExposureMode(QCameraExposure::ExposureAuto);
- QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureAuto);
+ camera.setExposureMode(QCamera::ExposureAuto);
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto);
camera.start();
- QTRY_COMPARE(camera.status(), QCamera::ActiveStatus);
- QCOMPARE(exposure->exposureMode(), QCameraExposure::ExposureAuto);
+ QTRY_VERIFY(camera.isActive());
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto);
+
+ // Manual
+ if (camera.isExposureModeSupported(QCamera::ExposureManual)) {
+ camera.setExposureMode(QCamera::ExposureManual);
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureManual);
+ camera.start();
+ QVERIFY(camera.isActive());
+ QCOMPARE(camera.exposureMode(), QCamera::ExposureManual);
+
+ camera.setManualExposureTime(.02f); // ~20ms should be supported by most cameras
+ QVERIFY(camera.manualExposureTime() > .01 && camera.manualExposureTime() < .04);
+ }
+
+ camera.setExposureMode(QCamera::ExposureAuto);
}
void tst_QCameraBackend::testVideoRecording_data()
{
- QTest::addColumn<QByteArray>("device");
+ QTest::addColumn<QCameraDevice>("device");
- const auto devices = QCameraInfo::availableCameras();
+ const auto devices = QMediaDevices::videoInputs();
- for (const auto &device : devices) {
- QTest::newRow(device.description().toUtf8())
- << device.deviceName().toLatin1();
- }
+ for (const auto &device : devices)
+ QTest::newRow(device.description().toUtf8()) << device;
if (devices.isEmpty())
- QTest::newRow("Default device") << QByteArray();
+ QTest::newRow("Null device") << QCameraDevice();
}
void tst_QCameraBackend::testVideoRecording()
{
- QFETCH(QByteArray, device);
+ if (noCamera)
+ QSKIP("No camera available");
+ QFETCH(QCameraDevice, device);
- QScopedPointer<QCamera> camera(device.isEmpty() ? new QCamera : new QCamera(device));
+ QMediaCaptureSession session;
+ QScopedPointer<QCamera> camera(new QCamera(device));
+ session.setCamera(camera.data());
- QMediaRecorder recorder(camera.data());
+ QMediaRecorder recorder;
+ session.setRecorder(&recorder);
- QSignalSpy errorSignal(camera.data(), SIGNAL(errorOccurred(QCamera::Error)));
- QSignalSpy recorderErrorSignal(&recorder, SIGNAL(error(QMediaRecorder::Error)));
- QSignalSpy recorderStatusSignal(&recorder, SIGNAL(statusChanged(QMediaRecorder::Status)));
+ QSignalSpy errorSignal(camera.data(), &QCamera::errorOccurred);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
- if (!camera->isCaptureModeSupported(QCamera::CaptureVideo)) {
- QSKIP("Video capture not supported");
- }
+ recorder.setVideoResolution(320, 240);
- camera->setCaptureMode(QCamera::CaptureVideo);
+ // Insert metadata
+ QMediaMetaData metaData;
+ metaData.insert(QMediaMetaData::Author, QStringLiteral("Author"));
+ metaData.insert(QMediaMetaData::Date, QDateTime::currentDateTime());
+ recorder.setMetaData(metaData);
- QVideoEncoderSettings videoSettings;
- videoSettings.setResolution(320, 240);
- recorder.setVideoSettings(videoSettings);
+ camera->start();
+ if (noCamera || device.isNull()) {
+ QVERIFY(!camera->isActive());
+ return;
+ }
+ QTRY_VERIFY(camera->isActive());
- QCOMPARE(recorder.status(), QMediaRecorder::UnloadedStatus);
+ QTRY_VERIFY(camera->isActive());
- camera->start();
- QVERIFY(recorder.status() == QMediaRecorder::LoadingStatus ||
- recorder.status() == QMediaRecorder::LoadedStatus);
- QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status());
- QTRY_COMPARE(camera->status(), QCamera::ActiveStatus);
- QTRY_COMPARE(recorder.status(), QMediaRecorder::LoadedStatus);
- QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status());
-
- //record 5 seconds clip
recorder.record();
- QTRY_COMPARE(recorder.status(), QMediaRecorder::RecordingStatus);
- QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status());
- QTest::qWait(5000);
- recorderStatusSignal.clear();
- recorder.stop();
- bool foundFinalizingStatus = false;
- for (auto &list : recorderStatusSignal) {
- if (list.contains(QVariant(QMediaRecorder::FinalizingStatus))) {
- foundFinalizingStatus = true;
- break;
- }
+ if (!recorderErrorSignal.empty() || recorderErrorSignal.wait(550)) {
+ QEXPECT_FAIL_GSTREAMER("", "QTBUG-124148: GStreamer might return ResourceError", Continue);
+
+ QCOMPARE(recorderErrorSignal.last().first().toInt(), QMediaRecorder::FormatError);
+ return;
}
- QVERIFY(foundFinalizingStatus);
- QTRY_COMPARE(recorder.status(), QMediaRecorder::LoadedStatus);
- QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status());
+
+ QTRY_VERIFY(durationChanged.size());
+
+ QCOMPARE(recorder.metaData(), metaData);
+
+ recorderStateChanged.clear();
+ recorder.stop();
+ QTRY_VERIFY(recorderStateChanged.size() > 0);
+ QVERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
QVERIFY(errorSignal.isEmpty());
QVERIFY(recorderErrorSignal.isEmpty());
QString fileName = recorder.actualLocation().toLocalFile();
QVERIFY(!fileName.isEmpty());
+ QVERIFY(QFileInfo(fileName).size() > 0);
+
+ QMediaPlayer player;
+ player.setSource(fileName);
+
+ QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::LoadedMedia, 8s);
+ QCOMPARE_EQ(player.metaData().value(QMediaMetaData::Resolution).toSize(), QSize(320, 240));
+ QCOMPARE_GT(player.duration(), 350);
+ QCOMPARE_LT(player.duration(), 650);
+
+ // TODO: integrate with a virtual camera and check mediaplayer output
+
+ QFile(fileName).remove();
+}
+
+void tst_QCameraBackend::testNativeMetadata()
+{
+ if (noCamera)
+ QSKIP("No camera available");
+
+ QMediaCaptureSession session;
+ QCameraDevice device = QMediaDevices::defaultVideoInput();
+ QCamera camera(device);
+ session.setCamera(&camera);
+ QMediaRecorder recorder;
+ session.setRecorder(&recorder);
+
+ QSignalSpy errorSignal(&camera, &QCamera::errorOccurred);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+
+ camera.start();
+ if (device.isNull()) {
+ QVERIFY(!camera.isActive());
+ return;
+ }
+
+ QTRY_VERIFY(camera.isActive());
+
+ // Insert common metadata supported on all platforms
+ // Don't use Date, as some backends set that on their own
+ QMediaMetaData metaData;
+ metaData.insert(QMediaMetaData::Title, QStringLiteral("Title"));
+ metaData.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::German));
+ metaData.insert(QMediaMetaData::Description, QStringLiteral("Description"));
+
+ recorder.setMetaData(metaData);
+
+ recorder.record();
+ QTRY_VERIFY(durationChanged.size());
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecorderState::RecordingState);
+
+ QCOMPARE(recorder.metaData(), metaData);
+
+ recorderStateChanged.clear();
+ recorder.stop();
+
+ QTRY_VERIFY(recorderStateChanged.size() > 0);
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecorderState::StoppedState);
+
+ QVERIFY(errorSignal.isEmpty());
+ if (!isGStreamerPlatform()) {
+ // https://bugreports.qt.io/browse/QTBUG-124183
+ QVERIFY(recorderErrorSignal.isEmpty());
+ }
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
QVERIFY(QFileInfo(fileName).size() > 0);
+
+ // QMediaRecorder::metaData() can only test that QMediaMetaData is set properly on the recorder.
+ // Use QMediaPlayer to test that the native metadata is properly set on the track
+ QAudioOutput output;
+ QMediaPlayer player;
+ player.setAudioOutput(&output);
+
+ QSignalSpy metadataChangedSpy(&player, &QMediaPlayer::metaDataChanged);
+
+ player.setSource(QUrl::fromLocalFile(fileName));
+ player.play();
+
+ int metadataChangedRequiredCount = isGStreamerPlatform() ? 2 : 1;
+
+ QTRY_VERIFY(metadataChangedSpy.size() >= metadataChangedRequiredCount);
+
+ QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(),
+ metaData.value(QMediaMetaData::Title).toString());
+ auto lang = player.metaData().value(QMediaMetaData::Language).value<QLocale::Language>();
+ if (lang != QLocale::AnyLanguage)
+ QCOMPARE(lang, metaData.value(QMediaMetaData::Language).value<QLocale::Language>());
+ QCOMPARE(player.metaData().value(QMediaMetaData::Description).toString(), metaData.value(QMediaMetaData::Description).toString());
+ QVERIFY(player.metaData().value(QMediaMetaData::Resolution).isValid());
+
+ if (isGStreamerPlatform())
+ QVERIFY(player.metaData().value(QMediaMetaData::Date).isValid());
+
+ player.stop();
+ player.setSource({});
QFile(fileName).remove();
+}
+
+void tst_QCameraBackend::multipleCameraSet()
+{
+ if (noCamera)
+ QSKIP("No camera available");
+
+ QMediaCaptureSession session;
+ QCameraDevice device = QMediaDevices::defaultVideoInput();
+
+ QMediaRecorder recorder;
+ session.setRecorder(&recorder);
- camera->setCaptureMode(QCamera::CaptureStillImage);
- QTRY_COMPARE(recorder.status(), QMediaRecorder::UnloadedStatus);
- QCOMPARE(recorderStatusSignal.last().first().value<QMediaRecorder::Status>(), recorder.status());
+ for (int i = 0; i < 5; ++i) {
+#ifdef Q_OS_DARWIN
+ QMacAutoReleasePool releasePool;
+#endif
+
+ QCamera camera(device);
+ session.setCamera(&camera);
+
+ camera.start();
+
+ QTest::qWait(100);
+ }
}
QTEST_MAIN(tst_QCameraBackend)
diff --git a/tests/auto/integration/qdeclarativevideooutput/qdeclarativevideooutput.pro b/tests/auto/integration/qdeclarativevideooutput/qdeclarativevideooutput.pro
deleted file mode 100644
index c4221232a..000000000
--- a/tests/auto/integration/qdeclarativevideooutput/qdeclarativevideooutput.pro
+++ /dev/null
@@ -1,11 +0,0 @@
-TARGET = tst_qdeclarativevideooutput
-
-QT += multimedia-private qml testlib quick qtmultimediaquicktools-private
-CONFIG += testcase
-
-RESOURCES += qml.qrc
-
-SOURCES += \
- tst_qdeclarativevideooutput.cpp
-
-INCLUDEPATH += ../../../../src/imports/multimedia
diff --git a/tests/auto/integration/qdeclarativevideooutput/qml.qrc b/tests/auto/integration/qdeclarativevideooutput/qml.qrc
deleted file mode 100644
index 5f6483ac3..000000000
--- a/tests/auto/integration/qdeclarativevideooutput/qml.qrc
+++ /dev/null
@@ -1,5 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>main.qml</file>
- </qresource>
-</RCC>
diff --git a/tests/auto/integration/qdeclarativevideooutput/tst_qdeclarativevideooutput.cpp b/tests/auto/integration/qdeclarativevideooutput/tst_qdeclarativevideooutput.cpp
deleted file mode 100644
index b31947738..000000000
--- a/tests/auto/integration/qdeclarativevideooutput/tst_qdeclarativevideooutput.cpp
+++ /dev/null
@@ -1,740 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-//TESTED_COMPONENT=plugins/declarative/multimedia
-
-#include <QtTest/QtTest>
-
-#include <QtQml/qqmlengine.h>
-#include <QtQml/qqmlcomponent.h>
-#include <QQuickView>
-
-#include "private/qdeclarativevideooutput_p.h"
-
-#include <qabstractvideosurface.h>
-#include <qvideorenderercontrol.h>
-#include <qvideosurfaceformat.h>
-
-#include <qmediaobject.h>
-
-class SurfaceHolder : public QObject
-{
- Q_OBJECT
- Q_PROPERTY(QAbstractVideoSurface *videoSurface READ videoSurface WRITE setVideoSurface)
-public:
- SurfaceHolder(QObject *parent)
- : QObject(parent)
- , m_surface(0)
- {
- }
-
- QAbstractVideoSurface *videoSurface() const
- {
- return m_surface;
- }
- void setVideoSurface(QAbstractVideoSurface *surface)
- {
- if (m_surface != surface && m_surface && m_surface->isActive()) {
- m_surface->stop();
- }
- m_surface = surface;
- }
-
- void presentDummyFrame(const QSize &size);
-
-private:
- QAbstractVideoSurface *m_surface;
-
-};
-
-// Starts the surface and puts a frame
-void SurfaceHolder::presentDummyFrame(const QSize &size)
-{
- if (m_surface && m_surface->supportedPixelFormats().count() > 0) {
- QVideoFrame::PixelFormat pixelFormat = m_surface->supportedPixelFormats().value(0);
- QVideoSurfaceFormat format(size, pixelFormat);
- QVideoFrame frame(size.width() * size.height() * 4, size, size.width() * 4, pixelFormat);
-
- if (!m_surface->isActive())
- m_surface->start(format);
- m_surface->present(frame);
-
- // Have to spin an event loop or two for the surfaceFormatChanged() signal
- qApp->processEvents();
- }
-}
-
-class tst_QDeclarativeVideoOutput : public QObject
-{
- Q_OBJECT
-public:
- tst_QDeclarativeVideoOutput();
-
- ~tst_QDeclarativeVideoOutput()
- {
- delete m_mappingOutput;
- delete m_mappingSurface;
- delete m_mappingComponent;
- }
-
-public slots:
- void initTestCase();
-
-private slots:
- void fillMode();
- void flushMode();
- void orientation();
- void surfaceSource();
- void paintSurface();
- void sourceRect();
-
- void contentRect();
- void contentRect_data();
-
- void mappingPoint();
- void mappingPoint_data();
- void mappingRect();
- void mappingRect_data();
-
- // XXX May be worth adding tests that the surface activeChanged signals are sent appropriately
- // to holder?
-
-private:
- QQmlEngine m_engine;
-
- // Variables used for the mapping test
- QQmlComponent *m_mappingComponent;
- QObject *m_mappingOutput;
- SurfaceHolder *m_mappingSurface;
-
- void updateOutputGeometry(QObject *output);
-
- QRectF invokeR2R(QObject *object, const char *signature, const QRectF &rect);
- QPointF invokeP2P(QObject *object, const char *signature, const QPointF &point);
-};
-
-void tst_QDeclarativeVideoOutput::initTestCase()
-{
- // We initialize the mapping vars here
- m_mappingComponent = new QQmlComponent(&m_engine);
- m_mappingComponent->loadUrl(QUrl("qrc:/main.qml"));
- m_mappingSurface = new SurfaceHolder(this);
-
- m_mappingOutput = m_mappingComponent->create();
- QVERIFY(m_mappingOutput != 0);
-
- m_mappingOutput->setProperty("source", QVariant::fromValue(static_cast<QObject*>(m_mappingSurface)));
-
- m_mappingSurface->presentDummyFrame(QSize(200,100)); // this should start m_surface
- updateOutputGeometry(m_mappingOutput);
-}
-
-Q_DECLARE_METATYPE(QDeclarativeVideoOutput::FillMode)
-Q_DECLARE_METATYPE(QDeclarativeVideoOutput::FlushMode)
-
-tst_QDeclarativeVideoOutput::tst_QDeclarativeVideoOutput()
- : m_mappingComponent(0)
- , m_mappingOutput(0)
- , m_mappingSurface(0)
-{
- qRegisterMetaType<QDeclarativeVideoOutput::FillMode>();
-}
-
-void tst_QDeclarativeVideoOutput::fillMode()
-{
- QQmlComponent component(&m_engine);
- component.loadUrl(QUrl("qrc:/main.qml"));
-
- QObject *videoOutput = component.create();
- QVERIFY(videoOutput != 0);
-
- QSignalSpy propSpy(videoOutput, SIGNAL(fillModeChanged(QDeclarativeVideoOutput::FillMode)));
-
- // Default is preserveaspectfit
- QCOMPARE(videoOutput->property("fillMode").value<QDeclarativeVideoOutput::FillMode>(), QDeclarativeVideoOutput::PreserveAspectFit);
- QCOMPARE(propSpy.count(), 0);
-
- videoOutput->setProperty("fillMode", QVariant(int(QDeclarativeVideoOutput::PreserveAspectCrop)));
- QCOMPARE(videoOutput->property("fillMode").value<QDeclarativeVideoOutput::FillMode>(), QDeclarativeVideoOutput::PreserveAspectCrop);
- QCOMPARE(propSpy.count(), 1);
-
- videoOutput->setProperty("fillMode", QVariant(int(QDeclarativeVideoOutput::Stretch)));
- QCOMPARE(videoOutput->property("fillMode").value<QDeclarativeVideoOutput::FillMode>(), QDeclarativeVideoOutput::Stretch);
- QCOMPARE(propSpy.count(), 2);
-
- videoOutput->setProperty("fillMode", QVariant(int(QDeclarativeVideoOutput::Stretch)));
- QCOMPARE(videoOutput->property("fillMode").value<QDeclarativeVideoOutput::FillMode>(), QDeclarativeVideoOutput::Stretch);
- QCOMPARE(propSpy.count(), 2);
-
- delete videoOutput;
-}
-
-void tst_QDeclarativeVideoOutput::flushMode()
-{
- QQmlComponent component(&m_engine);
- component.loadUrl(QUrl("qrc:/main.qml"));
-
- QObject *videoOutput = component.create();
- QVERIFY(videoOutput != 0);
-
- QSignalSpy propSpy(videoOutput, SIGNAL(flushModeChanged()));
-
- QCOMPARE(videoOutput->property("flushMode").value<QDeclarativeVideoOutput::FlushMode>(), QDeclarativeVideoOutput::EmptyFrame);
- QCOMPARE(propSpy.count(), 0);
-
- videoOutput->setProperty("flushMode", QVariant(int(QDeclarativeVideoOutput::FirstFrame)));
- QCOMPARE(videoOutput->property("fillMode").value<QDeclarativeVideoOutput::FlushMode>(), QDeclarativeVideoOutput::FirstFrame);
- QCOMPARE(propSpy.count(), 1);
-}
-
-void tst_QDeclarativeVideoOutput::orientation()
-{
- QQmlComponent component(&m_engine);
- component.loadUrl(QUrl("qrc:/main.qml"));
-
- QObject *videoOutput = component.create();
- QVERIFY(videoOutput != 0);
-
- QSignalSpy propSpy(videoOutput, SIGNAL(orientationChanged()));
-
- // Default orientation is 0
- QCOMPARE(videoOutput->property("orientation").toInt(), 0);
- QCOMPARE(propSpy.count(), 0);
-
- videoOutput->setProperty("orientation", QVariant(90));
- QCOMPARE(videoOutput->property("orientation").toInt(), 90);
- QCOMPARE(propSpy.count(), 1);
-
- videoOutput->setProperty("orientation", QVariant(180));
- QCOMPARE(videoOutput->property("orientation").toInt(), 180);
- QCOMPARE(propSpy.count(), 2);
-
- videoOutput->setProperty("orientation", QVariant(270));
- QCOMPARE(videoOutput->property("orientation").toInt(), 270);
- QCOMPARE(propSpy.count(), 3);
-
- videoOutput->setProperty("orientation", QVariant(360));
- QCOMPARE(videoOutput->property("orientation").toInt(), 360);
- QCOMPARE(propSpy.count(), 4);
-
- // More than 360 should be fine
- videoOutput->setProperty("orientation", QVariant(540));
- QCOMPARE(videoOutput->property("orientation").toInt(), 540);
- QCOMPARE(propSpy.count(), 5);
-
- // Negative should be fine
- videoOutput->setProperty("orientation", QVariant(-180));
- QCOMPARE(videoOutput->property("orientation").toInt(), -180);
- QCOMPARE(propSpy.count(), 6);
-
- // Same value should not reemit
- videoOutput->setProperty("orientation", QVariant(-180));
- QCOMPARE(videoOutput->property("orientation").toInt(), -180);
- QCOMPARE(propSpy.count(), 6);
-
- // Non multiples of 90 should not work
- videoOutput->setProperty("orientation", QVariant(-1));
- QCOMPARE(videoOutput->property("orientation").toInt(), -180);
- QCOMPARE(propSpy.count(), 6);
-
- delete videoOutput;
-}
-
-void tst_QDeclarativeVideoOutput::surfaceSource()
-{
- QQmlComponent component(&m_engine);
- component.loadUrl(QUrl("qrc:/main.qml"));
-
- QObject *videoOutput = component.create();
- QVERIFY(videoOutput != 0);
-
- SurfaceHolder holder(this);
-
- QCOMPARE(holder.videoSurface(), static_cast<QAbstractVideoSurface*>(0));
-
- videoOutput->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder)));
-
- QVERIFY(holder.videoSurface() != 0);
-
- // Now we could do things with the surface..
- const QList<QVideoFrame::PixelFormat> formats = holder.videoSurface()->supportedPixelFormats();
- QVERIFY(formats.count() > 0);
-
- // See if we can start and stop each pixel format (..)
- for (QVideoFrame::PixelFormat format : formats) {
- QVideoSurfaceFormat surfaceFormat(QSize(200,100), format);
- QVERIFY(holder.videoSurface()->isFormatSupported(surfaceFormat)); // This does kind of depend on node factories
-
- QVERIFY(holder.videoSurface()->start(surfaceFormat));
- QVERIFY(holder.videoSurface()->surfaceFormat() == surfaceFormat);
- QVERIFY(holder.videoSurface()->isActive());
-
- holder.videoSurface()->stop();
-
- QVERIFY(!holder.videoSurface()->isActive());
- }
-
- delete videoOutput;
-
- // This should clear the surface
- QCOMPARE(holder.videoSurface(), static_cast<QAbstractVideoSurface*>(0));
-
- // Also, creating two sources, setting them in order, and destroying the first
- // should not zero holder.videoSurface()
- videoOutput = component.create();
- videoOutput->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder)));
-
- QAbstractVideoSurface *surface = holder.videoSurface();
- QVERIFY(holder.videoSurface());
-
- QObject *videoOutput2 = component.create();
- QVERIFY(videoOutput2);
- videoOutput2->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder)));
- QVERIFY(holder.videoSurface());
- QVERIFY(holder.videoSurface() != surface); // Surface should have changed
- surface = holder.videoSurface();
-
- // Now delete first one
- delete videoOutput;
- QVERIFY(holder.videoSurface());
- QVERIFY(holder.videoSurface() == surface); // Should not have changed surface
-
- // Now create a second surface and assign it as the source
- // The old surface holder should be zeroed
- SurfaceHolder holder2(this);
- videoOutput2->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder2)));
-
- QCOMPARE(holder.videoSurface(), static_cast<QAbstractVideoSurface*>(0));
- QVERIFY(holder2.videoSurface() != 0);
-
- // Finally a combination - set the same source to two things, then assign a new source
- // to the first output - should not reset the first source
- videoOutput = component.create();
- videoOutput->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder2)));
-
- // Both vo and vo2 were pointed to holder2 - setting vo2 should not clear holder2
- QVERIFY(holder2.videoSurface() != 0);
- QVERIFY(holder.videoSurface() == 0);
- videoOutput2->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder)));
- QVERIFY(holder2.videoSurface() != 0);
- QVERIFY(holder.videoSurface() != 0);
-
- // They should also be independent
- QVERIFY(holder.videoSurface() != holder2.videoSurface());
-
- delete videoOutput;
- delete videoOutput2;
-}
-
-static const uchar rgb32ImageData[] =
-{// B G R A
- 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff,
- 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff
-};
-
-void tst_QDeclarativeVideoOutput::paintSurface()
-{
- QQuickView window;
- window.setSource(QUrl("qrc:/main.qml"));
- window.show();
- QVERIFY(QTest::qWaitForWindowExposed(&window));
-
- auto videoOutput = qobject_cast<QDeclarativeVideoOutput *>(window.rootObject());
- QVERIFY(videoOutput);
-
- auto surface = videoOutput->property("videoSurface").value<QAbstractVideoSurface *>();
- QVERIFY(surface);
- QVERIFY(!surface->isActive());
- videoOutput->setSize(QSize(2, 2));
- QVideoSurfaceFormat format(QSize(2, 2), QVideoFrame::Format_RGB32);
- QVERIFY(surface->isFormatSupported(format));
- QVERIFY(surface->start(format));
- QVERIFY(surface->isActive());
-
- QImage img(rgb32ImageData, 2, 2, 8, QImage::Format_RGB32);
- QVERIFY(surface->present(img));
-}
-
-void tst_QDeclarativeVideoOutput::sourceRect()
-{
- QQmlComponent component(&m_engine);
- component.loadUrl(QUrl("qrc:/main.qml"));
-
- QObject *videoOutput = component.create();
- QVERIFY(videoOutput != 0);
-
- SurfaceHolder holder(this);
-
- QSignalSpy propSpy(videoOutput, SIGNAL(sourceRectChanged()));
-
- videoOutput->setProperty("source", QVariant::fromValue(static_cast<QObject*>(&holder)));
-
- QRectF invalid(0,0,-1,-1);
-
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), invalid);
-
- holder.presentDummyFrame(QSize(200,100));
-
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
- QCOMPARE(propSpy.count(), 1);
-
- // Another frame shouldn't cause a source rect change
- holder.presentDummyFrame(QSize(200,100));
- QCOMPARE(propSpy.count(), 1);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- // Changing orientation and stretch modes should not affect this
- videoOutput->setProperty("orientation", QVariant(90));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- videoOutput->setProperty("orientation", QVariant(180));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- videoOutput->setProperty("orientation", QVariant(270));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- videoOutput->setProperty("orientation", QVariant(-90));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- videoOutput->setProperty("fillMode", QVariant(int(QDeclarativeVideoOutput::PreserveAspectCrop)));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- videoOutput->setProperty("fillMode", QVariant(int(QDeclarativeVideoOutput::Stretch)));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- videoOutput->setProperty("fillMode", QVariant(int(QDeclarativeVideoOutput::Stretch)));
- updateOutputGeometry(videoOutput);
- QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
-
- delete videoOutput;
-}
-
-void tst_QDeclarativeVideoOutput::mappingPoint()
-{
- QFETCH(QPointF, point);
- QFETCH(int, orientation);
- QFETCH(QDeclarativeVideoOutput::FillMode, fillMode);
- QFETCH(QPointF, expected);
-
- QVERIFY(m_mappingOutput);
- m_mappingOutput->setProperty("orientation", QVariant(orientation));
- m_mappingOutput->setProperty("fillMode", QVariant::fromValue(fillMode));
-
- updateOutputGeometry(m_mappingOutput);
-
- QPointF output = invokeP2P(m_mappingOutput, "mapPointToItem", point);
- QPointF reverse = invokeP2P(m_mappingOutput, "mapPointToSource", output);
-
- QCOMPARE(output, expected);
- QCOMPARE(reverse, point);
-
- // Now the normalized versions
- // Source rectangle is 200x100
- QPointF normal(point.x() / 200, point.y() / 100);
-
- output = invokeP2P(m_mappingOutput, "mapNormalizedPointToItem", normal);
- reverse = invokeP2P(m_mappingOutput, "mapPointToSourceNormalized", output);
-
- QCOMPARE(output, expected);
- QCOMPARE(reverse, normal);
-}
-
-void tst_QDeclarativeVideoOutput::mappingPoint_data()
-{
- QTest::addColumn<QPointF>("point");
- QTest::addColumn<int>("orientation");
- QTest::addColumn<QDeclarativeVideoOutput::FillMode>("fillMode");
- QTest::addColumn<QPointF>("expected");
-
- QDeclarativeVideoOutput::FillMode stretch = QDeclarativeVideoOutput::Stretch;
- QDeclarativeVideoOutput::FillMode fit = QDeclarativeVideoOutput::PreserveAspectFit;
- QDeclarativeVideoOutput::FillMode crop = QDeclarativeVideoOutput::PreserveAspectCrop;
-
- // First make sure the component has processed the frame
- QCOMPARE(m_mappingOutput->property("sourceRect").toRectF(), QRectF(0,0,200,100));
-
- // 200x100 -> 150,100 stretch, 150x75 fit @ 12.5f, 200x100 @-25,0 crop
-
- // Corners, then the center, then a point in the middle somewhere
- QTest::newRow("s0-0") << QPointF(0,0) << 0 << stretch << QPointF(0,0);
- QTest::newRow("s1-0") << QPointF(200,0) << 0 << stretch << QPointF(150,0);
- QTest::newRow("s2-0") << QPointF(0,100) << 0 << stretch << QPointF(0,100);
- QTest::newRow("s3-0") << QPointF(200,100) << 0 << stretch << QPointF(150,100);
- QTest::newRow("s4-0") << QPointF(100,50) << 0 << stretch << QPointF(75,50);
- QTest::newRow("s5-0") << QPointF(40,80) << 0 << stretch << QPointF(30,80);
-
- QTest::newRow("f0-0") << QPointF(0,0) << 0 << fit << QPointF(0,12.5f);
- QTest::newRow("f1-0") << QPointF(200,0) << 0 << fit << QPointF(150,12.5f);
- QTest::newRow("f2-0") << QPointF(0,100) << 0 << fit << QPointF(0,87.5f);
- QTest::newRow("f3-0") << QPointF(200,100) << 0 << fit << QPointF(150,87.5f);
- QTest::newRow("f4-0") << QPointF(100,50) << 0 << stretch << QPointF(75,50);
- QTest::newRow("f5-0") << QPointF(40,80) << 0 << stretch << QPointF(30,80);
-
- QTest::newRow("c0-0") << QPointF(0,0) << 0 << crop << QPointF(-25,0);
- QTest::newRow("c1-0") << QPointF(200,0) << 0 << crop << QPointF(175,0);
- QTest::newRow("c2-0") << QPointF(0,100) << 0 << crop << QPointF(-25,100);
- QTest::newRow("c3-0") << QPointF(200,100) << 0 << crop << QPointF(175,100);
- QTest::newRow("c4-0") << QPointF(100,50) << 0 << stretch << QPointF(75,50);
- QTest::newRow("c5-0") << QPointF(40,80) << 0 << stretch << QPointF(30,80);
-
- // 90 degrees (anti clockwise)
- QTest::newRow("s0-90") << QPointF(0,0) << 90 << stretch << QPointF(0,100);
- QTest::newRow("s1-90") << QPointF(200,0) << 90 << stretch << QPointF(0,0);
- QTest::newRow("s2-90") << QPointF(0,100) << 90 << stretch << QPointF(150,100);
- QTest::newRow("s3-90") << QPointF(200,100) << 90 << stretch << QPointF(150,0);
- QTest::newRow("s4-90") << QPointF(100,50) << 90 << stretch << QPointF(75,50);
- QTest::newRow("s5-90") << QPointF(40,80) << 90 << stretch << QPointF(120,80);
-
- QTest::newRow("f0-90") << QPointF(0,0) << 90 << fit << QPointF(50,100);
- QTest::newRow("f1-90") << QPointF(200,0) << 90 << fit << QPointF(50,0);
- QTest::newRow("f2-90") << QPointF(0,100) << 90 << fit << QPointF(100,100);
- QTest::newRow("f3-90") << QPointF(200,100) << 90 << fit << QPointF(100,0);
- QTest::newRow("f4-90") << QPointF(100,50) << 90 << fit << QPointF(75,50);
- QTest::newRow("f5-90") << QPointF(40,80) << 90 << fit << QPointF(90,80);
-
- QTest::newRow("c0-90") << QPointF(0,0) << 90 << crop << QPointF(0,200);
- QTest::newRow("c1-90") << QPointF(200,0) << 90 << crop << QPointF(0,-100);
- QTest::newRow("c2-90") << QPointF(0,100) << 90 << crop << QPointF(150,200);
- QTest::newRow("c3-90") << QPointF(200,100) << 90 << crop << QPointF(150,-100);
- QTest::newRow("c4-90") << QPointF(100,50) << 90 << crop << QPointF(75,50);
- QTest::newRow("c5-90") << QPointF(40,80) << 90 << crop << QPointF(120,140);
-
- // 180
- QTest::newRow("s0-180") << QPointF(0,0) << 180 << stretch << QPointF(150,100);
- QTest::newRow("s1-180") << QPointF(200,0) << 180 << stretch << QPointF(0,100);
- QTest::newRow("s2-180") << QPointF(0,100) << 180 << stretch << QPointF(150,0);
- QTest::newRow("s3-180") << QPointF(200,100) << 180 << stretch << QPointF(0,0);
- QTest::newRow("s4-180") << QPointF(100,50) << 180 << stretch << QPointF(75,50);
- QTest::newRow("s5-180") << QPointF(40,80) << 180 << stretch << QPointF(120,20);
-
- QTest::newRow("f0-180") << QPointF(0,0) << 180 << fit << QPointF(150,87.5f);
- QTest::newRow("f1-180") << QPointF(200,0) << 180 << fit << QPointF(0,87.5f);
- QTest::newRow("f2-180") << QPointF(0,100) << 180 << fit << QPointF(150,12.5f);
- QTest::newRow("f3-180") << QPointF(200,100) << 180 << fit << QPointF(0,12.5f);
- QTest::newRow("f4-180") << QPointF(100,50) << 180 << fit << QPointF(75,50);
- QTest::newRow("f5-180") << QPointF(40,80) << 180 << fit << QPointF(120,27.5f);
-
- QTest::newRow("c0-180") << QPointF(0,0) << 180 << crop << QPointF(175,100);
- QTest::newRow("c1-180") << QPointF(200,0) << 180 << crop << QPointF(-25,100);
- QTest::newRow("c2-180") << QPointF(0,100) << 180 << crop << QPointF(175,0);
- QTest::newRow("c3-180") << QPointF(200,100) << 180 << crop << QPointF(-25,0);
- QTest::newRow("c4-180") << QPointF(100,50) << 180 << crop << QPointF(75,50);
- QTest::newRow("c5-180") << QPointF(40,80) << 180 << crop << QPointF(135,20);
-
- // 270
- QTest::newRow("s0-270") << QPointF(0,0) << 270 << stretch << QPointF(150,0);
- QTest::newRow("s1-270") << QPointF(200,0) << 270 << stretch << QPointF(150,100);
- QTest::newRow("s2-270") << QPointF(0,100) << 270 << stretch << QPointF(0,0);
- QTest::newRow("s3-270") << QPointF(200,100) << 270 << stretch << QPointF(0,100);
- QTest::newRow("s4-270") << QPointF(100,50) << 270 << stretch << QPointF(75,50);
- QTest::newRow("s5-270") << QPointF(40,80) << 270 << stretch << QPointF(30,20);
-
- QTest::newRow("f0-270") << QPointF(0,0) << 270 << fit << QPointF(100,0);
- QTest::newRow("f1-270") << QPointF(200,0) << 270 << fit << QPointF(100,100);
- QTest::newRow("f2-270") << QPointF(0,100) << 270 << fit << QPointF(50,0);
- QTest::newRow("f3-270") << QPointF(200,100) << 270 << fit << QPointF(50,100);
- QTest::newRow("f4-270") << QPointF(100,50) << 270 << fit << QPointF(75,50);
- QTest::newRow("f5-270") << QPointF(40,80) << 270 << fit << QPointF(60,20);
-
- QTest::newRow("c0-270") << QPointF(0,0) << 270 << crop << QPointF(150,-100);
- QTest::newRow("c1-270") << QPointF(200,0) << 270 << crop << QPointF(150,200);
- QTest::newRow("c2-270") << QPointF(0,100) << 270 << crop << QPointF(0,-100);
- QTest::newRow("c3-270") << QPointF(200,100) << 270 << crop << QPointF(0,200);
- QTest::newRow("c4-270") << QPointF(100,50) << 270 << crop << QPointF(75,50);
- QTest::newRow("c5-270") << QPointF(40,80) << 270 << crop << QPointF(30,-40);
-}
-
-/* Test all rectangle mapping */
-void tst_QDeclarativeVideoOutput::mappingRect()
-{
- QFETCH(QRectF, rect);
- QFETCH(int, orientation);
- QFETCH(QDeclarativeVideoOutput::FillMode, fillMode);
- QFETCH(QRectF, expected);
-
- QVERIFY(m_mappingOutput);
- m_mappingOutput->setProperty("orientation", QVariant(orientation));
- m_mappingOutput->setProperty("fillMode", QVariant::fromValue(fillMode));
-
- updateOutputGeometry(m_mappingOutput);
-
- QRectF output = invokeR2R(m_mappingOutput, "mapRectToItem", rect);
- QRectF reverse = invokeR2R(m_mappingOutput, "mapRectToSource", output);
-
- QCOMPARE(output, expected);
- QCOMPARE(reverse, rect);
-
- // Now the normalized versions
- // Source rectangle is 200x100
- QRectF normal(rect.x() / 200, rect.y() / 100, rect.width() / 200, rect.height() / 100);
-
- output = invokeR2R(m_mappingOutput, "mapNormalizedRectToItem", normal);
- reverse = invokeR2R(m_mappingOutput, "mapRectToSourceNormalized", output);
-
- QCOMPARE(output, expected);
- QCOMPARE(reverse, normal);
-}
-
-void tst_QDeclarativeVideoOutput::mappingRect_data()
-{
- QTest::addColumn<QRectF>("rect");
- QTest::addColumn<int>("orientation");
- QTest::addColumn<QDeclarativeVideoOutput::FillMode>("fillMode");
- QTest::addColumn<QRectF>("expected");
-
- // First make sure the component has processed the frame
- QCOMPARE(m_mappingOutput->property("sourceRect").toRectF(), QRectF(0,0,200,100));
-
- QDeclarativeVideoOutput::FillMode stretch = QDeclarativeVideoOutput::Stretch;
- QDeclarativeVideoOutput::FillMode fit = QDeclarativeVideoOutput::PreserveAspectFit;
- QDeclarativeVideoOutput::FillMode crop = QDeclarativeVideoOutput::PreserveAspectCrop;
-
- // Full rectangle mapping
- // Stretch
- QTest::newRow("s0") << QRectF(0,0, 200, 100) << 0 << stretch << QRectF(0,0,150,100);
- QTest::newRow("s90") << QRectF(0,0, 200, 100) << 90 << stretch << QRectF(0,0,150,100);
- QTest::newRow("s180") << QRectF(0,0, 200, 100) << 180 << stretch << QRectF(0,0,150,100);
- QTest::newRow("s270") << QRectF(0,0, 200, 100) << 270 << stretch << QRectF(0,0,150,100);
-
- // Fit
- QTest::newRow("f0") << QRectF(0,0, 200, 100) << 0 << fit << QRectF(0,12.5f,150,75);
- QTest::newRow("f90") << QRectF(0,0, 200, 100) << 90 << fit << QRectF(50,0,50,100);
- QTest::newRow("f180") << QRectF(0,0, 200, 100) << 180 << fit << QRectF(0,12.5f,150,75);
- QTest::newRow("f270") << QRectF(0,0, 200, 100) << 270 << fit << QRectF(50,0,50,100);
-
- // Crop
- QTest::newRow("c0") << QRectF(0,0, 200, 100) << 0 << crop << QRectF(-25,0,200,100);
- QTest::newRow("c90") << QRectF(0,0, 200, 100) << 90 << crop << QRectF(0,-100,150,300);
- QTest::newRow("c180") << QRectF(0,0, 200, 100) << 180 << crop << QRectF(-25,0,200,100);
- QTest::newRow("c270") << QRectF(0,0, 200, 100) << 270 << crop << QRectF(0,-100,150,300);
-
- // Partial rectangle mapping
- // Stretch
- // 50-130 in x (0.25 - 0.65), 25-50 (0.25 - 0.5) in y (out of 200, 100) -> 150x100
- QTest::newRow("p-s0") << QRectF(50, 25, 80, 25) << 0 << stretch << QRectF(37.5f,25,60,25);
- QTest::newRow("p-s90") << QRectF(50, 25, 80, 25) << 90 << stretch << QRectF(37.5f,35,37.5f,40);
- QTest::newRow("p-s180") << QRectF(50, 25, 80, 25) << 180 << stretch << QRectF(52.5f,50,60,25);
- QTest::newRow("p-s270") << QRectF(50, 25, 80, 25) << 270 << stretch << QRectF(75,25,37.5f,40);
-
- // Fit
- QTest::newRow("p-f0") << QRectF(50, 25, 80, 25) << 0 << fit << QRectF(37.5f,31.25f,60,18.75f);
- QTest::newRow("p-f90") << QRectF(50, 25, 80, 25) << 90 << fit << QRectF(62.5f,35,12.5f,40);
- QTest::newRow("p-f180") << QRectF(50, 25, 80, 25) << 180 << fit << QRectF(52.5f,50,60,18.75f);
- QTest::newRow("p-f270") << QRectF(50, 25, 80, 25) << 270 << fit << QRectF(75,25,12.5f,40);
-
- // Crop
- QTest::newRow("p-c0") << QRectF(50, 25, 80, 25) << 0 << crop << QRectF(25,25,80,25);
- QTest::newRow("p-c90") << QRectF(50, 25, 80, 25) << 90 << crop << QRectF(37.5f,5,37.5f,120);
- QTest::newRow("p-c180") << QRectF(50, 25, 80, 25) << 180 << crop << QRectF(45,50,80,25);
- QTest::newRow("p-c270") << QRectF(50, 25, 80, 25) << 270 << crop << QRectF(75,-25,37.5f,120);
-}
-
-void tst_QDeclarativeVideoOutput::updateOutputGeometry(QObject *output)
-{
- // Since the object isn't visible, update() doesn't do anything
- // so we manually force this
- QMetaObject::invokeMethod(output, "_q_updateGeometry");
-}
-
-void tst_QDeclarativeVideoOutput::contentRect()
-{
- QFETCH(int, orientation);
- QFETCH(QDeclarativeVideoOutput::FillMode, fillMode);
- QFETCH(QRectF, expected);
-
- QVERIFY(m_mappingOutput);
- m_mappingOutput->setProperty("orientation", QVariant(orientation));
- m_mappingOutput->setProperty("fillMode", QVariant::fromValue(fillMode));
-
- updateOutputGeometry(m_mappingOutput);
-
- QRectF output = m_mappingOutput->property("contentRect").toRectF();
- QCOMPARE(output, expected);
-}
-
-void tst_QDeclarativeVideoOutput::contentRect_data()
-{
- QTest::addColumn<int>("orientation");
- QTest::addColumn<QDeclarativeVideoOutput::FillMode>("fillMode");
- QTest::addColumn<QRectF>("expected");
-
- // First make sure the component has processed the frame
- QCOMPARE(m_mappingOutput->property("sourceRect").toRectF(), QRectF(0,0,200,100));
-
- QDeclarativeVideoOutput::FillMode stretch = QDeclarativeVideoOutput::Stretch;
- QDeclarativeVideoOutput::FillMode fit = QDeclarativeVideoOutput::PreserveAspectFit;
- QDeclarativeVideoOutput::FillMode crop = QDeclarativeVideoOutput::PreserveAspectCrop;
-
- // Stretch just keeps the full render rect regardless of orientation
- QTest::newRow("s0") << 0 << stretch << QRectF(0,0,150,100);
- QTest::newRow("s90") << 90 << stretch << QRectF(0,0,150,100);
- QTest::newRow("s180") << 180 << stretch << QRectF(0,0,150,100);
- QTest::newRow("s270") << 270 << stretch << QRectF(0,0,150,100);
-
- // Fit depends on orientation
- // Source is 200x100, fitting in 150x100 -> 150x75
- // or 100x200 -> 50x100
- QTest::newRow("f0") << 0 << fit << QRectF(0,12.5f,150,75);
- QTest::newRow("f90") << 90 << fit << QRectF(50,0,50,100);
- QTest::newRow("f180") << 180 << fit << QRectF(0,12.5,150,75);
- QTest::newRow("f270") << 270 << fit << QRectF(50,0,50,100);
-
- // Crop also depends on orientation, may go outside render rect
- // 200x100 -> -25,0 200x100
- // 100x200 -> 0,-100 150x300
- QTest::newRow("c0") << 0 << crop << QRectF(-25,0,200,100);
- QTest::newRow("c90") << 90 << crop << QRectF(0,-100,150,300);
- QTest::newRow("c180") << 180 << crop << QRectF(-25,0,200,100);
- QTest::newRow("c270") << 270 << crop << QRectF(0,-100,150,300);
-}
-
-
-QRectF tst_QDeclarativeVideoOutput::invokeR2R(QObject *object, const char *signature, const QRectF &rect)
-{
- QRectF r;
- QMetaObject::invokeMethod(object, signature, Q_RETURN_ARG(QRectF, r), Q_ARG(QRectF, rect));
- return r;
-}
-
-QPointF tst_QDeclarativeVideoOutput::invokeP2P(QObject *object, const char *signature, const QPointF &point)
-{
- QPointF p;
- QMetaObject::invokeMethod(object, signature, Q_RETURN_ARG(QPointF, p), Q_ARG(QPointF, point));
- return p;
-}
-
-
-QTEST_MAIN(tst_QDeclarativeVideoOutput)
-
-#include "tst_qdeclarativevideooutput.moc"
diff --git a/tests/auto/integration/qdeclarativevideooutput_window/qdeclarativevideooutput_window.pro b/tests/auto/integration/qdeclarativevideooutput_window/qdeclarativevideooutput_window.pro
deleted file mode 100644
index eeb1c0135..000000000
--- a/tests/auto/integration/qdeclarativevideooutput_window/qdeclarativevideooutput_window.pro
+++ /dev/null
@@ -1,11 +0,0 @@
-TARGET = tst_qdeclarativevideooutput_window
-
-QT += multimedia-private qml testlib quick qtmultimediaquicktools-private
-CONFIG += testcase
-
-RESOURCES += qml.qrc
-
-SOURCES += \
- tst_qdeclarativevideooutput_window.cpp
-
-INCLUDEPATH += ../../../../src/imports/multimedia
diff --git a/tests/auto/integration/qdeclarativevideooutput_window/qml.qrc b/tests/auto/integration/qdeclarativevideooutput_window/qml.qrc
deleted file mode 100644
index 5f6483ac3..000000000
--- a/tests/auto/integration/qdeclarativevideooutput_window/qml.qrc
+++ /dev/null
@@ -1,5 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>main.qml</file>
- </qresource>
-</RCC>
diff --git a/tests/auto/integration/qdeclarativevideooutput_window/tst_qdeclarativevideooutput_window.cpp b/tests/auto/integration/qdeclarativevideooutput_window/tst_qdeclarativevideooutput_window.cpp
deleted file mode 100644
index 645b5d3c6..000000000
--- a/tests/auto/integration/qdeclarativevideooutput_window/tst_qdeclarativevideooutput_window.cpp
+++ /dev/null
@@ -1,271 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Copyright (C) 2016 Research In Motion
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-//TESTED_COMPONENT=plugins/declarative/multimedia
-
-#include "private/qdeclarativevideooutput_p.h"
-#include <QtCore/qobject.h>
-#include <QtTest/qtest.h>
-#include <QtQml/qqmlengine.h>
-#include <QtQml/qqmlcomponent.h>
-#include <QtQuick/qquickitem.h>
-#include <QtQuick/qquickview.h>
-#include <QtMultimedia/qmediaobject.h>
-#include <QtMultimedia/qmediaservice.h>
-#include <QtMultimedia/qvideowindowcontrol.h>
-
-Q_DECLARE_METATYPE(QDeclarativeVideoOutput::FillMode)
-
-class SourceObject : public QObject
-{
- Q_OBJECT
- Q_PROPERTY(QObject *mediaObject READ mediaObject CONSTANT)
-public:
- explicit SourceObject(QMediaObject *mediaObject, QObject *parent = 0)
- : QObject(parent), m_mediaObject(mediaObject)
- {}
-
- QObject *mediaObject() const
- { return m_mediaObject; }
-
-private:
- QMediaObject *m_mediaObject;
-};
-
-class QtTestWindowControl : public QVideoWindowControl
-{
-public:
- QtTestWindowControl()
- : m_winId(0)
- , m_repaintCount(0)
- , m_brightness(0)
- , m_contrast(0)
- , m_hue(0)
- , m_saturation(0)
- , m_aspectRatioMode(Qt::KeepAspectRatio)
- , m_fullScreen(0)
- {
- }
-
- WId winId() const { return m_winId; }
- void setWinId(WId id) { m_winId = id; }
-
- QRect displayRect() const { return m_displayRect; }
- void setDisplayRect(const QRect &rect) { m_displayRect = rect; }
-
- bool isFullScreen() const { return m_fullScreen; }
- void setFullScreen(bool fullScreen) { emit fullScreenChanged(m_fullScreen = fullScreen); }
-
- int repaintCount() const { return m_repaintCount; }
- void setRepaintCount(int count) { m_repaintCount = count; }
- void repaint() { ++m_repaintCount; }
-
- QSize nativeSize() const { return m_nativeSize; }
- void setNativeSize(const QSize &size) { m_nativeSize = size; emit nativeSizeChanged(); }
-
- Qt::AspectRatioMode aspectRatioMode() const { return m_aspectRatioMode; }
- void setAspectRatioMode(Qt::AspectRatioMode mode) { m_aspectRatioMode = mode; }
-
- int brightness() const { return m_brightness; }
- void setBrightness(int brightness) { emit brightnessChanged(m_brightness = brightness); }
-
- int contrast() const { return m_contrast; }
- void setContrast(int contrast) { emit contrastChanged(m_contrast = contrast); }
-
- int hue() const { return m_hue; }
- void setHue(int hue) { emit hueChanged(m_hue = hue); }
-
- int saturation() const { return m_saturation; }
- void setSaturation(int saturation) { emit saturationChanged(m_saturation = saturation); }
-
-private:
- WId m_winId;
- int m_repaintCount;
- int m_brightness;
- int m_contrast;
- int m_hue;
- int m_saturation;
- Qt::AspectRatioMode m_aspectRatioMode;
- QRect m_displayRect;
- QSize m_nativeSize;
- bool m_fullScreen;
-};
-
-class QtTestVideoService : public QMediaService
-{
- Q_OBJECT
-public:
- QtTestVideoService(QtTestWindowControl *window)
- : QMediaService(0)
- , windowControl(window)
- {}
-
- QMediaControl *requestControl(const char *name)
- {
- if (qstrcmp(name, QVideoWindowControl_iid) == 0)
- return windowControl;
- return 0;
- }
-
- void releaseControl(QMediaControl *control)
- {
- Q_ASSERT(control);
- }
-
- QtTestWindowControl *windowControl;
-};
-
-class QtTestVideoObject : public QMediaObject
-{
- Q_OBJECT
-public:
- explicit QtTestVideoObject(QtTestVideoService *service):
- QMediaObject(0, service)
- {
- }
-};
-
-class tst_QDeclarativeVideoOutputWindow : public QObject
-{
- Q_OBJECT
-public:
- tst_QDeclarativeVideoOutputWindow()
- : QObject(0)
- , m_service(new QtTestVideoService(&m_windowControl))
- , m_videoObject(m_service)
- , m_sourceObject(&m_videoObject)
- {
- }
-
- ~tst_QDeclarativeVideoOutputWindow()
- {
- }
-
-public slots:
- void initTestCase();
- void cleanupTestCase();
-
-private slots:
- void winId();
- void nativeSize();
- void aspectRatio();
- void geometryChange();
- void resetCanvas();
-
-private:
- QQmlEngine m_engine;
- QQuickItem *m_videoItem;
- QScopedPointer<QQuickItem> m_rootItem;
- QtTestWindowControl m_windowControl;
- QtTestVideoService *m_service;
- QtTestVideoObject m_videoObject;
- SourceObject m_sourceObject;
- QQuickView m_view;
-};
-
-void tst_QDeclarativeVideoOutputWindow::initTestCase()
-{
- qRegisterMetaType<QDeclarativeVideoOutput::FillMode>();
-
- QQmlComponent component(&m_engine);
- component.loadUrl(QUrl("qrc:/main.qml"));
-
- m_rootItem.reset(qobject_cast<QQuickItem *>(component.create()));
- m_videoItem = m_rootItem->findChild<QQuickItem *>("videoOutput");
- QVERIFY(m_videoItem);
- m_rootItem->setParentItem(m_view.contentItem());
- m_videoItem->setProperty("source", QVariant::fromValue<QObject *>(&m_sourceObject));
-
- m_windowControl.setNativeSize(QSize(400, 200));
- m_view.resize(200, 200);
- m_view.show();
-}
-
-void tst_QDeclarativeVideoOutputWindow::cleanupTestCase()
-{
- // Make sure that QDeclarativeVideoOutput doesn't segfault when it is being destroyed after
- // the service is already gone
- delete m_service;
- m_service = 0;
- m_view.setSource(QUrl());
- m_rootItem.reset();
-}
-
-void tst_QDeclarativeVideoOutputWindow::winId()
-{
- QCOMPARE(m_windowControl.winId(), m_view.winId());
-}
-
-void tst_QDeclarativeVideoOutputWindow::nativeSize()
-{
- QCOMPARE(m_videoItem->implicitWidth(), qreal(400.0f));
- QCOMPARE(m_videoItem->implicitHeight(), qreal(200.0f));
-}
-
-void tst_QDeclarativeVideoOutputWindow::aspectRatio()
-{
- const QRect expectedDisplayRect(25, 50, 150, 100);
- int oldRepaintCount = m_windowControl.repaintCount();
- m_videoItem->setProperty("fillMode", QDeclarativeVideoOutput::Stretch);
- QTRY_COMPARE(m_windowControl.aspectRatioMode(), Qt::IgnoreAspectRatio);
- QCOMPARE(m_windowControl.displayRect(), expectedDisplayRect);
- QVERIFY(m_windowControl.repaintCount() > oldRepaintCount);
-
- oldRepaintCount = m_windowControl.repaintCount();
- m_videoItem->setProperty("fillMode", QDeclarativeVideoOutput::PreserveAspectFit);
- QTRY_COMPARE(m_windowControl.aspectRatioMode(), Qt::KeepAspectRatio);
- QCOMPARE(m_windowControl.displayRect(), expectedDisplayRect);
- QVERIFY(m_windowControl.repaintCount() > oldRepaintCount);
-
- oldRepaintCount = m_windowControl.repaintCount();
- m_videoItem->setProperty("fillMode", QDeclarativeVideoOutput::PreserveAspectCrop);
- QTRY_COMPARE(m_windowControl.aspectRatioMode(), Qt::KeepAspectRatioByExpanding);
- QCOMPARE(m_windowControl.displayRect(), expectedDisplayRect);
- QVERIFY(m_windowControl.repaintCount() > oldRepaintCount);
-}
-
-void tst_QDeclarativeVideoOutputWindow::geometryChange()
-{
- m_videoItem->setWidth(50);
- QTRY_COMPARE(m_windowControl.displayRect(), QRect(25, 50, 50, 100));
-
- m_videoItem->setX(30);
- QTRY_COMPARE(m_windowControl.displayRect(), QRect(30, 50, 50, 100));
-}
-
-void tst_QDeclarativeVideoOutputWindow::resetCanvas()
-{
- m_rootItem->setParentItem(0);
- QCOMPARE((int)m_windowControl.winId(), 0);
-}
-
-
-QTEST_MAIN(tst_QDeclarativeVideoOutputWindow)
-
-#include "tst_qdeclarativevideooutput_window.moc"
diff --git a/tests/auto/integration/qmediacapturesession/BLACKLIST b/tests/auto/integration/qmediacapturesession/BLACKLIST
new file mode 100644
index 000000000..550ecdd6f
--- /dev/null
+++ b/tests/auto/integration/qmediacapturesession/BLACKLIST
@@ -0,0 +1 @@
+ci
diff --git a/tests/auto/integration/qmediacapturesession/CMakeLists.txt b/tests/auto/integration/qmediacapturesession/CMakeLists.txt
new file mode 100644
index 000000000..1aec26493
--- /dev/null
+++ b/tests/auto/integration/qmediacapturesession/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qcamerabackend.pro.
+
+#####################################################################
+## tst_qcamerabackend Test:
+#####################################################################
+
+qt_internal_add_test(tst_qmediacapturesession
+ SOURCES
+ tst_qmediacapturesession.cpp
+ ../shared/mediabackendutils.h
+ INCLUDE_DIRECTORIES
+ ../shared/
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ Qt::MultimediaWidgets
+)
+
+if(QT_FEATURE_gstreamer)
+ set_tests_properties(tst_qmediacapturesession
+ PROPERTIES ENVIRONMENT "G_DEBUG=fatal_criticals"
+ )
+endif()
diff --git a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp
new file mode 100644
index 000000000..4de5b7239
--- /dev/null
+++ b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp
@@ -0,0 +1,1278 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <QtGui/QImageReader>
+#include <QtCore/qurl.h>
+#include <QDebug>
+#include <QVideoSink>
+#include <QVideoWidget>
+#include <QSysInfo>
+
+#include <qcamera.h>
+#include <qcameradevice.h>
+#include <qimagecapture.h>
+#include <qmediacapturesession.h>
+#include <qmediaplayer.h>
+#include <qmediadevices.h>
+#include <qmediarecorder.h>
+#include <qaudiooutput.h>
+#include <qaudioinput.h>
+#include <qaudiodevice.h>
+#include <qaudiodecoder.h>
+#include <qaudiobuffer.h>
+#include <qscreencapture.h>
+#include <qwindowcapture.h>
+#include <qaudiobufferinput.h>
+#include <qvideoframeinput.h>
+
+#include <qcamera.h>
+#include <QMediaFormat>
+#include <QtMultimediaWidgets/QVideoWidget>
+
+#include <mediabackendutils.h>
+
+QT_USE_NAMESPACE
+
+/*
+ This is the backend conformance test.
+
+ Since it relies on platform media framework and sound hardware
+ it may be less stable.
+*/
+
+class tst_QMediaCaptureSession: public QObject
+{
+ Q_OBJECT
+
+private slots:
+
+ void initTestCase()
+ {
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") {
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-118571");
+#endif
+ }
+ }
+
+ void testAudioMute();
+ void stress_test_setup_and_teardown();
+ void stress_test_setup_and_teardown_keep_session();
+ void stress_test_setup_and_teardown_keep_recorder();
+ void stress_test_setup_and_teardown_keep_camera();
+ void stress_test_setup_and_teardown_keep_audioinput();
+ void stress_test_setup_and_teardown_keep_audiooutput();
+ void stress_test_setup_and_teardown_keep_video();
+
+ void record_video_without_preview();
+
+ void can_add_and_remove_AudioInput_with_and_without_AudioOutput_attached();
+ void can_change_AudioDevices_on_attached_AudioInput();
+ void can_change_AudioInput_during_recording();
+ void disconnects_deleted_AudioInput();
+ void can_move_AudioInput_between_sessions();
+
+ void disconnects_deleted_AudioOutput();
+ void can_move_AudioOutput_between_sessions_and_player();
+
+ void disconnects_deleted_AudioBufferInput();
+ void can_move_AudioBufferInput_between_sessions();
+
+ void disconnects_deleted_VideoFrameInput();
+ void can_move_VideoFrameInput_between_sessions();
+
+ void can_add_and_remove_Camera();
+ void can_move_Camera_between_sessions();
+ void can_disconnect_Camera_when_recording();
+ void can_add_and_remove_different_Cameras();
+ void can_change_CameraDevice_on_attached_Camera();
+
+ void can_change_VideoOutput_with_and_without_camera();
+ void can_change_VideoOutput_when_recording();
+
+ void can_add_and_remove_recorders();
+ void can_move_Recorder_between_sessions();
+ void cannot_record_without_Camera_and_AudioInput();
+ void can_record_AudioInput_with_null_AudioDevice();
+ void can_record_Camera_with_null_CameraDevice();
+ void recording_stops_when_recorder_removed();
+
+ void can_add_and_remove_ImageCapture();
+ void can_move_ImageCapture_between_sessions();
+ void capture_is_not_available_when_Camera_is_null();
+ void can_add_ImageCapture_and_capture_during_recording();
+
+ void can_reset_audio_input_output();
+
+private:
+ void recordOk(QMediaCaptureSession &session);
+ void recordFail(QMediaCaptureSession &session);
+};
+
+
+void tst_QMediaCaptureSession::recordOk(QMediaCaptureSession &session)
+{
+ QMediaRecorder recorder;
+ session.setRecorder(&recorder);
+
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+
+ recorder.record();
+ QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 2000);
+ QVERIFY(durationChanged.wait(2000));
+ recorder.stop();
+
+ QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::StoppedState, 2000);
+ QVERIFY(recorderErrorSignal.isEmpty());
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QTRY_VERIFY(QFileInfo(fileName).size() > 0);
+ QFile(fileName).remove();
+}
+
+void tst_QMediaCaptureSession::recordFail(QMediaCaptureSession &session)
+{
+ QMediaRecorder recorder;
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+
+ session.setRecorder(&recorder);
+ recorder.record();
+
+ QTRY_VERIFY_WITH_TIMEOUT(recorderErrorSignal.size() == 1, 2000);
+ QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::StoppedState, 2000);
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown()
+{
+ for (int i = 0; i < 50; i++) {
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ QCamera camera;
+ QAudioInput input;
+ QAudioOutput output;
+ QVideoWidget video;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_session()
+{
+ QMediaCaptureSession session;
+ for (int i = 0; i < 50; i++) {
+ QMediaRecorder recorder;
+ QCamera camera;
+ QAudioInput input;
+ QAudioOutput output;
+ QVideoWidget video;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_recorder()
+{
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ for (int i = 0; i < 50; i++) {
+ QCamera camera;
+ QAudioInput input;
+ QAudioOutput output;
+ QVideoWidget video;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_camera()
+{
+ QCamera camera;
+ for (int i = 0; i < 50; i++) {
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ QAudioInput input;
+ QAudioOutput output;
+ QVideoWidget video;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audioinput()
+{
+ QAudioInput input;
+ for (int i = 0; i < 50; i++) {
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ QCamera camera;
+ QAudioOutput output;
+ QVideoWidget video;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_audiooutput()
+{
+ QAudioOutput output;
+ for (int i = 0; i < 50; i++) {
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ QCamera camera;
+ QAudioInput input;
+ QVideoWidget video;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::stress_test_setup_and_teardown_keep_video()
+{
+ QVideoWidget video;
+ for (int i = 0; i < 50; i++) {
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ QCamera camera;
+ QAudioInput input;
+ QAudioOutput output;
+
+ session.setAudioInput(&input);
+ session.setAudioOutput(&output);
+ session.setRecorder(&recorder);
+ session.setCamera(&camera);
+ session.setVideoOutput(&video);
+
+ QRandomGenerator rng;
+ QTest::qWait(rng.bounded(200));
+ }
+}
+
+void tst_QMediaCaptureSession::record_video_without_preview()
+{
+ QCamera camera;
+
+ if (!camera.isAvailable())
+ QSKIP("No video input is available");
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+
+ session.setCamera(&camera);
+ camera.setActive(true);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+
+ session.setCamera(nullptr);
+ QTRY_COMPARE(cameraChanged.size(), 2);
+
+ // can't record without audio and video
+ recordFail(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_add_and_remove_AudioInput_with_and_without_AudioOutput_attached()
+{
+ QAudioInput input;
+ if (input.device().isNull())
+ QSKIP("No audio input available");
+
+ QMediaCaptureSession session;
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+ session.setAudioInput(nullptr);
+ QTRY_COMPARE(audioInputChanged.size(), 2);
+
+ QAudioOutput output;
+ if (output.device().isNull())
+ return;
+
+ session.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged.size(), 1);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 3);
+
+ session.setAudioOutput(nullptr);
+ QTRY_COMPARE(audioOutputChanged.size(), 2);
+
+ session.setAudioInput(nullptr);
+ QTRY_COMPARE(audioInputChanged.size(), 4);
+}
+
+void tst_QMediaCaptureSession::can_change_AudioDevices_on_attached_AudioInput()
+{
+ auto audioInputs = QMediaDevices::audioInputs();
+ if (audioInputs.size() < 2)
+ QSKIP("Two audio inputs are not available");
+
+ QAudioInput input(audioInputs[0]);
+ QSignalSpy deviceChanged(&input, &QAudioInput::deviceChanged);
+
+ QMediaCaptureSession session;
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+
+ input.setDevice(audioInputs[1]);
+ QTRY_COMPARE(deviceChanged.size(), 1);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+
+ input.setDevice(audioInputs[0]);
+ QTRY_COMPARE(deviceChanged.size(), 2);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_change_AudioInput_during_recording()
+{
+ QAudioInput input;
+ if (input.device().isNull())
+ QSKIP("No audio input available");
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+
+ recorder.record();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
+ QVERIFY(durationChanged.wait(2000));
+ session.setAudioInput(nullptr);
+ QTRY_COMPARE(audioInputChanged.size(), 2);
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 3);
+ recorder.stop();
+
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
+ QVERIFY(recorderErrorSignal.isEmpty());
+
+ session.setAudioInput(nullptr);
+ QTRY_COMPARE(audioInputChanged.size(), 4);
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QTRY_VERIFY(QFileInfo(fileName).size() > 0);
+ QFile(fileName).remove();
+}
+
+void tst_QMediaCaptureSession::disconnects_deleted_AudioInput()
+{
+ if (QMediaDevices::audioInputs().isEmpty())
+ QSKIP("No audio input available");
+
+ QMediaCaptureSession session;
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ {
+ QAudioInput input;
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+ }
+ QVERIFY(session.audioInput() == nullptr);
+ QTRY_COMPARE(audioInputChanged.size(), 2);
+}
+
+void tst_QMediaCaptureSession::can_move_AudioInput_between_sessions()
+{
+ if (QMediaDevices::audioInputs().isEmpty())
+ QSKIP("No audio input available");
+
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QSignalSpy audioInputChanged0(&session0, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy audioInputChanged1(&session1, &QMediaCaptureSession::audioInputChanged);
+
+ QAudioInput input;
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy audioInputChanged2(&session2, &QMediaCaptureSession::audioInputChanged);
+ session2.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged2.size(), 1);
+ }
+ session0.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged0.size(), 1);
+ QVERIFY(session0.audioInput() != nullptr);
+
+ session1.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged0.size(), 2);
+ QVERIFY(session0.audioInput() == nullptr);
+ QTRY_COMPARE(audioInputChanged1.size(), 1);
+ QVERIFY(session1.audioInput() != nullptr);
+}
+
+void tst_QMediaCaptureSession::disconnects_deleted_AudioOutput()
+{
+ if (QMediaDevices::audioOutputs().isEmpty())
+ QSKIP("No audio output available");
+
+ QMediaCaptureSession session;
+ QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged);
+ {
+ QAudioOutput output;
+ session.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged.size(), 1);
+ }
+ QVERIFY(session.audioOutput() == nullptr);
+ QTRY_COMPARE(audioOutputChanged.size(), 2);
+}
+
+void tst_QMediaCaptureSession::can_move_AudioOutput_between_sessions_and_player()
+{
+ if (QMediaDevices::audioOutputs().isEmpty())
+ QSKIP("No audio output available");
+
+ QAudioOutput output;
+
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QMediaPlayer player;
+ QSignalSpy audioOutputChanged0(&session0, &QMediaCaptureSession::audioOutputChanged);
+ QSignalSpy audioOutputChanged1(&session1, &QMediaCaptureSession::audioOutputChanged);
+ QSignalSpy audioOutputChangedPlayer(&player, &QMediaPlayer::audioOutputChanged);
+
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy audioOutputChanged2(&session2, &QMediaCaptureSession::audioOutputChanged);
+ session2.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged2.size(), 1);
+ }
+
+ session0.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged0.size(), 1);
+ QVERIFY(session0.audioOutput() != nullptr);
+
+ session1.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged0.size(), 2);
+ QVERIFY(session0.audioOutput() == nullptr);
+ QTRY_COMPARE(audioOutputChanged1.size(), 1);
+ QVERIFY(session1.audioOutput() != nullptr);
+
+ player.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged0.size(), 2);
+ QVERIFY(session0.audioOutput() == nullptr);
+ QTRY_COMPARE(audioOutputChanged1.size(), 2);
+ QVERIFY(session1.audioOutput() == nullptr);
+ QTRY_COMPARE(audioOutputChangedPlayer.size(), 1);
+ QVERIFY(player.audioOutput() != nullptr);
+
+ session0.setAudioOutput(&output);
+ QTRY_COMPARE(audioOutputChanged0.size(), 3);
+ QVERIFY(session0.audioOutput() != nullptr);
+ QTRY_COMPARE(audioOutputChangedPlayer.size(), 2);
+ QVERIFY(player.audioOutput() == nullptr);
+}
+
+void tst_QMediaCaptureSession::disconnects_deleted_AudioBufferInput()
+{
+ QMediaCaptureSession session;
+ QSignalSpy audioBufferInputChanged(&session, &QMediaCaptureSession::audioBufferInputChanged);
+ {
+ QAudioBufferInput input;
+ session.setAudioBufferInput(&input);
+ QTRY_COMPARE(audioBufferInputChanged.size(), 1);
+ }
+ QCOMPARE(session.audioBufferInput(), nullptr);
+ QCOMPARE(audioBufferInputChanged.size(), 2);
+}
+
+void tst_QMediaCaptureSession::can_move_AudioBufferInput_between_sessions()
+{
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QSignalSpy audioBufferInputChanged0(&session0, &QMediaCaptureSession::audioBufferInputChanged);
+ QSignalSpy audioBufferInputChanged1(&session1, &QMediaCaptureSession::audioBufferInputChanged);
+
+ QAudioBufferInput input;
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy audioBufferInputChanged2(&session2,
+ &QMediaCaptureSession::audioBufferInputChanged);
+ session2.setAudioBufferInput(&input);
+ QCOMPARE(audioBufferInputChanged2.size(), 1);
+ }
+ session0.setAudioBufferInput(&input);
+ QCOMPARE(audioBufferInputChanged0.size(), 1);
+ QCOMPARE(session0.audioBufferInput(), &input);
+ QCOMPARE(input.captureSession(), &session0);
+
+ session1.setAudioBufferInput(&input);
+
+ QCOMPARE(audioBufferInputChanged0.size(), 2);
+ QCOMPARE(session0.audioBufferInput(), nullptr);
+ QCOMPARE(audioBufferInputChanged1.size(), 1);
+ QCOMPARE(session1.audioBufferInput(), &input);
+ QCOMPARE(input.captureSession(), &session1);
+}
+
+void tst_QMediaCaptureSession::disconnects_deleted_VideoFrameInput()
+{
+ QMediaCaptureSession session;
+ QSignalSpy videoFrameInputChanged(&session, &QMediaCaptureSession::videoFrameInputChanged);
+ {
+ QVideoFrameInput input;
+ session.setVideoFrameInput(&input);
+ QTRY_COMPARE(videoFrameInputChanged.size(), 1);
+ }
+ QCOMPARE(session.videoFrameInput(), nullptr);
+ QCOMPARE(videoFrameInputChanged.size(), 2);
+}
+
+void tst_QMediaCaptureSession::can_move_VideoFrameInput_between_sessions()
+{
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QSignalSpy videoFrameInputChanged0(&session0, &QMediaCaptureSession::videoFrameInputChanged);
+ QSignalSpy videoFrameInputChanged1(&session1, &QMediaCaptureSession::videoFrameInputChanged);
+
+ QVideoFrameInput input;
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy videoFrameInputChanged2(&session2,
+ &QMediaCaptureSession::videoFrameInputChanged);
+ session2.setVideoFrameInput(&input);
+ QCOMPARE(videoFrameInputChanged2.size(), 1);
+ }
+ session0.setVideoFrameInput(&input);
+ QCOMPARE(videoFrameInputChanged0.size(), 1);
+ QCOMPARE(session0.videoFrameInput(), &input);
+ QCOMPARE(input.captureSession(), &session0);
+
+ session1.setVideoFrameInput(&input);
+
+ QCOMPARE(videoFrameInputChanged0.size(), 2);
+ QCOMPARE(session0.videoFrameInput(), nullptr);
+ QCOMPARE(videoFrameInputChanged1.size(), 1);
+ QCOMPARE(session1.videoFrameInput(), &input);
+ QCOMPARE(input.captureSession(), &session1);
+}
+
+void tst_QMediaCaptureSession::can_add_and_remove_Camera()
+{
+ QCamera camera;
+
+ if (!camera.isAvailable())
+ QSKIP("No video input is available");
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+
+ session.setCamera(&camera);
+ camera.setActive(true);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ session.setCamera(nullptr);
+ QTRY_COMPARE(cameraChanged.size(), 2);
+
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 3);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_move_Camera_between_sessions()
+{
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QSignalSpy cameraChanged0(&session0, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy cameraChanged1(&session1, &QMediaCaptureSession::cameraChanged);
+ {
+ QCamera camera;
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy cameraChanged2(&session2, &QMediaCaptureSession::cameraChanged);
+ session2.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged2.size(), 1);
+ }
+ QVERIFY(camera.captureSession() == nullptr);
+
+ session0.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged0.size(), 1);
+ QVERIFY(session0.camera() == &camera);
+ QVERIFY(camera.captureSession() == &session0);
+
+ session1.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged0.size(), 2);
+ QVERIFY(session0.camera() == nullptr);
+ QTRY_COMPARE(cameraChanged1.size(), 1);
+ QVERIFY(session1.camera() == &camera);
+ QVERIFY(camera.captureSession() == &session1);
+ }
+ QTRY_COMPARE(cameraChanged1.size(), 2);
+ QVERIFY(session1.camera() == nullptr);
+}
+
+void tst_QMediaCaptureSession::can_disconnect_Camera_when_recording()
+{
+ QCamera camera;
+
+ if (!camera.isAvailable())
+ QSKIP("No video input is available");
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+
+ camera.setActive(true);
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ durationChanged.clear();
+ recorder.record();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
+ QTRY_VERIFY(durationChanged.size() > 0);
+
+ session.setCamera(nullptr);
+ QTRY_COMPARE(cameraChanged.size(), 2);
+
+ recorder.stop();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
+ QVERIFY(recorderErrorSignal.isEmpty());
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QTRY_VERIFY(QFileInfo(fileName).size() > 0);
+ QFile(fileName).remove();
+}
+
+void tst_QMediaCaptureSession::can_add_and_remove_different_Cameras()
+{
+ auto cameraDevices = QMediaDevices().videoInputs();
+
+ if (cameraDevices.size() < 2)
+ QSKIP("Two video input are not available");
+
+ QCamera camera(cameraDevices[0]);
+ QCamera camera2(cameraDevices[1]);
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+
+ camera.setActive(true);
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ session.setCamera(nullptr);
+ QTRY_COMPARE(cameraChanged.size(), 2);
+
+ session.setCamera(&camera2);
+ camera2.setActive(true);
+ QTRY_COMPARE(cameraChanged.size(), 3);
+ QTRY_COMPARE(camera2.isActive(), true);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_change_CameraDevice_on_attached_Camera()
+{
+ auto cameraDevices = QMediaDevices().videoInputs();
+
+ if (cameraDevices.size() < 2)
+ QSKIP("Two video input are not available");
+
+ QCamera camera(cameraDevices[0]);
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy cameraDeviceChanged(&camera, &QCamera::cameraDeviceChanged);
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+
+ recordFail(session);
+ QVERIFY(!QTest::currentTestFailed());
+
+ camera.setActive(true);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+
+ camera.setCameraDevice(cameraDevices[1]);
+ camera.setActive(true);
+ QTRY_COMPARE(cameraDeviceChanged.size(), 1);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_change_VideoOutput_with_and_without_camera()
+{
+ QCamera camera;
+ if (!camera.isAvailable())
+ QSKIP("No video input is available");
+
+ QVideoWidget videoOutput;
+ QVideoWidget videoOutput2;
+ videoOutput.show();
+ videoOutput2.show();
+
+ QMediaCaptureSession session;
+
+ QSignalSpy videoOutputChanged(&session, &QMediaCaptureSession::videoOutputChanged);
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+
+ session.setVideoOutput(&videoOutput);
+ QTRY_COMPARE(videoOutputChanged.size(), 1);
+
+ session.setVideoOutput(nullptr);
+ QTRY_COMPARE(videoOutputChanged.size(), 2);
+
+ session.setVideoOutput(&videoOutput2);
+ QTRY_COMPARE(videoOutputChanged.size(), 3);
+
+ session.setCamera(nullptr);
+ QTRY_COMPARE(cameraChanged.size(), 2);
+
+ session.setVideoOutput(nullptr);
+ QTRY_COMPARE(videoOutputChanged.size(), 4);
+}
+
+void tst_QMediaCaptureSession::can_change_VideoOutput_when_recording()
+{
+ QCamera camera;
+ if (!camera.isAvailable())
+ QSKIP("No video input is available");
+
+ QVideoWidget videoOutput;
+ videoOutput.show();
+
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+
+ session.setRecorder(&recorder);
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+ QSignalSpy videoOutputChanged(&session, &QMediaCaptureSession::videoOutputChanged);
+
+ camera.setActive(true);
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ recorder.record();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
+ QVERIFY(durationChanged.wait(2000));
+
+ session.setVideoOutput(&videoOutput);
+ QTRY_COMPARE(videoOutputChanged.size(), 1);
+
+ session.setVideoOutput(nullptr);
+ QTRY_COMPARE(videoOutputChanged.size(), 2);
+
+ session.setVideoOutput(&videoOutput);
+ QTRY_COMPARE(videoOutputChanged.size(), 3);
+
+ recorder.stop();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
+ QVERIFY(recorderErrorSignal.isEmpty());
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QTRY_VERIFY(QFileInfo(fileName).size() > 0);
+ QFile(fileName).remove();
+}
+
+void tst_QMediaCaptureSession::can_add_and_remove_recorders()
+{
+ QAudioInput input;
+ if (input.device().isNull())
+ QSKIP("Recording source not available");
+
+ QMediaRecorder recorder;
+ QMediaRecorder recorder2;
+ QMediaCaptureSession session;
+
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+
+ session.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged.size(), 1);
+
+ session.setRecorder(&recorder2);
+ QTRY_COMPARE(recorderChanged.size(), 2);
+
+ session.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged.size(), 3);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_move_Recorder_between_sessions()
+{
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QSignalSpy recorderChanged0(&session0, &QMediaCaptureSession::recorderChanged);
+ QSignalSpy recorderChanged1(&session1, &QMediaCaptureSession::recorderChanged);
+ {
+ QMediaRecorder recorder;
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy recorderChanged2(&session2, &QMediaCaptureSession::recorderChanged);
+ session2.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged2.size(), 1);
+ }
+ QVERIFY(recorder.captureSession() == nullptr);
+
+ session0.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged0.size(), 1);
+ QVERIFY(session0.recorder() == &recorder);
+ QVERIFY(recorder.captureSession() == &session0);
+
+ session1.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged0.size(), 2);
+ QVERIFY(session0.recorder() == nullptr);
+ QTRY_COMPARE(recorderChanged1.size(), 1);
+ QVERIFY(session1.recorder() == &recorder);
+ QVERIFY(recorder.captureSession() == &session1);
+ }
+ QTRY_COMPARE(recorderChanged1.size(), 2);
+ QVERIFY(session1.recorder() == nullptr);
+}
+
+void tst_QMediaCaptureSession::cannot_record_without_Camera_and_AudioInput()
+{
+ QMediaCaptureSession session;
+ recordFail(session);
+}
+
+void tst_QMediaCaptureSession::can_record_AudioInput_with_null_AudioDevice()
+{
+ if (QMediaDevices().audioInputs().size() == 0)
+ QSKIP("No audio input is not available");
+
+ QAudioDevice nullDevice;
+ QAudioInput input(nullDevice);
+
+ QMediaCaptureSession session;
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::can_record_Camera_with_null_CameraDevice()
+{
+ if (QMediaDevices().videoInputs().size() == 0)
+ QSKIP("No video input is not available");
+
+ QCameraDevice nullDevice;
+ QCamera camera(nullDevice);
+
+ QMediaCaptureSession session;
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+
+ camera.setActive(true);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ recordOk(session);
+ QVERIFY(!QTest::currentTestFailed());
+}
+
+void tst_QMediaCaptureSession::recording_stops_when_recorder_removed()
+{
+ QAudioInput input;
+ if (input.device().isNull())
+ QSKIP("Recording source not available");
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged);
+ QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+
+ session.setAudioInput(&input);
+ QTRY_COMPARE(audioInputChanged.size(), 1);
+
+ session.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged.size(), 1);
+
+ recorder.record();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
+ QVERIFY(durationChanged.wait(2000));
+
+ session.setRecorder(nullptr);
+ QTRY_COMPARE(recorderChanged.size(), 2);
+
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
+ QVERIFY(recorderErrorSignal.isEmpty());
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QTRY_VERIFY(QFileInfo(fileName).size() > 0);
+ QFile(fileName).remove();
+}
+
+void tst_QMediaCaptureSession::can_add_and_remove_ImageCapture()
+{
+ QCamera camera;
+
+ if (!camera.isAvailable())
+ QSKIP("No video input available");
+
+ QImageCapture capture;
+ QMediaCaptureSession session;
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy imageCaptureChanged(&session, &QMediaCaptureSession::imageCaptureChanged);
+ QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
+
+ QVERIFY(!capture.isAvailable());
+ QVERIFY(!capture.isReadyForCapture());
+
+ session.setImageCapture(&capture);
+ QTRY_COMPARE(imageCaptureChanged.size(), 1);
+ QVERIFY(!capture.isAvailable());
+ QVERIFY(!capture.isReadyForCapture());
+
+ session.setCamera(&camera);
+ QTRY_COMPARE(cameraChanged.size(), 1);
+ QVERIFY(capture.isAvailable());
+
+ QVERIFY(!capture.isReadyForCapture());
+
+ camera.setActive(true);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ QTRY_COMPARE(readyForCaptureChanged.size(), 1);
+ QVERIFY(capture.isReadyForCapture());
+
+ session.setImageCapture(nullptr);
+ QTRY_COMPARE(imageCaptureChanged.size(), 2);
+ QTRY_COMPARE(readyForCaptureChanged.size(), 2);
+
+ QVERIFY(!capture.isAvailable());
+ QVERIFY(!capture.isReadyForCapture());
+
+ session.setImageCapture(&capture);
+ QTRY_COMPARE(imageCaptureChanged.size(), 3);
+ QTRY_COMPARE(readyForCaptureChanged.size(), 3);
+ QVERIFY(capture.isAvailable());
+ QVERIFY(capture.isReadyForCapture());
+}
+
+void tst_QMediaCaptureSession::can_move_ImageCapture_between_sessions()
+{
+ QSKIP_GSTREAMER("QTBUG-124005: Spurious failure on CI");
+
+ QMediaCaptureSession session0;
+ QMediaCaptureSession session1;
+ QSignalSpy imageCaptureChanged0(&session0, &QMediaCaptureSession::imageCaptureChanged);
+ QSignalSpy imageCaptureChanged1(&session1, &QMediaCaptureSession::imageCaptureChanged);
+ {
+ QImageCapture imageCapture;
+ {
+ QMediaCaptureSession session2;
+ QSignalSpy imageCaptureChanged2(&session2, &QMediaCaptureSession::imageCaptureChanged);
+ session2.setImageCapture(&imageCapture);
+ QTRY_COMPARE(imageCaptureChanged2.size(), 1);
+ }
+ QVERIFY(imageCapture.captureSession() == nullptr);
+
+ session0.setImageCapture(&imageCapture);
+ QTRY_COMPARE(imageCaptureChanged0.size(), 1);
+ QVERIFY(session0.imageCapture() == &imageCapture);
+ QVERIFY(imageCapture.captureSession() == &session0);
+
+ session1.setImageCapture(&imageCapture);
+ QTRY_COMPARE(imageCaptureChanged0.size(), 2);
+ QVERIFY(session0.imageCapture() == nullptr);
+ QTRY_COMPARE(imageCaptureChanged1.size(), 1);
+ QVERIFY(session1.imageCapture() == &imageCapture);
+ QVERIFY(imageCapture.captureSession() == &session1);
+ }
+ QTRY_COMPARE(imageCaptureChanged1.size(), 2);
+ QVERIFY(session1.imageCapture() == nullptr);
+}
+
+void tst_QMediaCaptureSession::capture_is_not_available_when_Camera_is_null()
+{
+ QCamera camera;
+
+ if (!camera.isAvailable())
+ QSKIP("No video input available");
+
+ QImageCapture capture;
+ QMediaCaptureSession session;
+
+ QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged);
+ QSignalSpy capturedSignal(&capture, &QImageCapture::imageCaptured);
+ QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
+
+ session.setImageCapture(&capture);
+ session.setCamera(&camera);
+ camera.setActive(true);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ QTRY_COMPARE(readyForCaptureChanged.size(), 1);
+ QVERIFY(capture.isReadyForCapture());
+
+ QVERIFY(capture.capture() >= 0);
+ QTRY_COMPARE(capturedSignal.size(), 1);
+
+ QVERIFY(capture.isReadyForCapture());
+ int readyCount = readyForCaptureChanged.size();
+
+ session.setCamera(nullptr);
+
+ QTRY_COMPARE(readyForCaptureChanged.size(), readyCount + 1);
+ QVERIFY(!capture.isReadyForCapture());
+ QVERIFY(!capture.isAvailable());
+ QVERIFY(capture.capture() < 0);
+}
+
+void tst_QMediaCaptureSession::can_add_ImageCapture_and_capture_during_recording()
+{
+ QCamera camera;
+
+ if (!camera.isAvailable())
+ QSKIP("No video input available");
+
+ QImageCapture capture;
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+
+ QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged);
+ QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+ QSignalSpy imageCaptureChanged(&session, &QMediaCaptureSession::imageCaptureChanged);
+ QSignalSpy readyForCaptureChanged(&capture, &QImageCapture::readyForCaptureChanged);
+ QSignalSpy capturedSignal(&capture, &QImageCapture::imageCaptured);
+
+ session.setCamera(&camera);
+ camera.setActive(true);
+ QTRY_COMPARE(camera.isActive(), true);
+
+ session.setRecorder(&recorder);
+ QTRY_COMPARE(recorderChanged.size(), 1);
+
+ recorder.record();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState);
+ QVERIFY(durationChanged.wait(2000));
+
+ session.setImageCapture(&capture);
+ QTRY_COMPARE(imageCaptureChanged.size(), 1);
+ QTRY_COMPARE(readyForCaptureChanged.size(), 1);
+ QVERIFY(capture.isReadyForCapture());
+
+ QVERIFY(capture.capture() >= 0);
+ QTRY_COMPARE(capturedSignal.size(), 1);
+
+ session.setImageCapture(nullptr);
+ QVERIFY(readyForCaptureChanged.size() >= 2);
+ QVERIFY(!capture.isReadyForCapture());
+
+ recorder.stop();
+ QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState);
+ QVERIFY(recorderErrorSignal.isEmpty());
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QTRY_VERIFY(QFileInfo(fileName).size() > 0);
+ QFile(fileName).remove();
+}
+
+void tst_QMediaCaptureSession::testAudioMute()
+{
+ QAudioInput audioInput;
+ if (audioInput.device().isNull())
+ QSKIP("No audio input available");
+
+ QMediaRecorder recorder;
+ QMediaCaptureSession session;
+
+ session.setRecorder(&recorder);
+
+ session.setAudioInput(&audioInput);
+
+ session.setCamera(nullptr);
+ recorder.setOutputLocation(QStringLiteral("test"));
+
+ QSignalSpy spy(&audioInput, &QAudioInput::mutedChanged);
+ QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged);
+
+ QMediaFormat format;
+ format.setAudioCodec(QMediaFormat::AudioCodec::Wave);
+ recorder.setMediaFormat(format);
+
+ audioInput.setMuted(true);
+
+ recorder.record();
+
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(spy.last()[0], true);
+
+ QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 2000);
+ QVERIFY(durationChanged.wait(2000));
+
+ recorder.stop();
+
+ QTRY_COMPARE(recorder.recorderState(), QMediaRecorder::StoppedState);
+
+ QString actualLocation = recorder.actualLocation().toLocalFile();
+
+ QVERIFY2(!actualLocation.isEmpty(), "Recorder did not save a file");
+ QTRY_VERIFY2(QFileInfo(actualLocation).size() > 0, "Recorded file is empty (zero bytes)");
+
+ QAudioDecoder decoder;
+ QAudioBuffer buffer;
+ decoder.setSource(QUrl::fromLocalFile(actualLocation));
+
+ decoder.start();
+
+ // Wait a while
+ QTRY_VERIFY(decoder.bufferAvailable());
+
+ while (decoder.bufferAvailable()) {
+ buffer = decoder.read();
+ QVERIFY(buffer.isValid());
+
+ const void *data = buffer.constData<void *>();
+ QVERIFY(data != nullptr);
+
+ const unsigned int *idata = reinterpret_cast<const unsigned int *>(data);
+ QCOMPARE(*idata, 0U);
+ }
+
+ decoder.stop();
+
+ QFile(actualLocation).remove();
+
+ audioInput.setMuted(false);
+
+ QCOMPARE(spy.size(), 2);
+ QCOMPARE(spy.last()[0], false);
+}
+
+void tst_QMediaCaptureSession::can_reset_audio_input_output()
+{
+ QAudioInput in1;
+ QMediaCaptureSession session;
+ session.setAudioInput(&in1);
+ QVERIFY(session.audioInput() != nullptr);
+ QAudioInput in2;
+ QSignalSpy changeSpy1(&session, &QMediaCaptureSession::audioInputChanged);
+ session.setAudioInput(&in2);
+ QVERIFY(session.audioInput() != nullptr);
+ QCOMPARE(changeSpy1.count(), 1);
+
+ QAudioOutput out1;
+ session.setAudioOutput(&out1);
+ QVERIFY(session.audioOutput() != nullptr);
+ QSignalSpy changeSpy2(&session, &QMediaCaptureSession::audioOutputChanged);
+ QAudioOutput out2;
+ session.setAudioOutput(&out2);
+ QVERIFY(session.audioOutput() != nullptr);
+ QCOMPARE(changeSpy2.count(), 1);
+}
+
+QTEST_MAIN(tst_QMediaCaptureSession)
+
+#include "tst_qmediacapturesession.moc"
diff --git a/tests/auto/integration/qmediaframeinputsbackend/CMakeLists.txt b/tests/auto/integration/qmediaframeinputsbackend/CMakeLists.txt
new file mode 100644
index 000000000..8d35b1de0
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/CMakeLists.txt
@@ -0,0 +1,22 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qmediaframeinputsbackend Test:
+#####################################################################
+
+qt_internal_add_test(tst_qmediaframeinputsbackend
+ SOURCES
+ tst_qmediaframeinputsbackend.cpp tst_qmediaframeinputsbackend.h
+ capturesessionfixture.cpp capturesessionfixture.h
+ mediainfo.h
+ framegenerator.cpp framegenerator.h
+ ../shared/testvideosink.h
+ LIBRARIES
+ Qt::Multimedia
+ Qt::MultimediaPrivate
+ Qt::Gui
+ Qt::Widgets
+)
+
+
diff --git a/tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.cpp b/tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.cpp
new file mode 100644
index 000000000..aae03df60
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.cpp
@@ -0,0 +1,88 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "capturesessionfixture.h"
+#include <QtTest/qtest.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace std::chrono;
+
+CaptureSessionFixture::CaptureSessionFixture(StreamType streamType, AutoStop autoStop)
+ : m_streamType{ streamType }
+{
+ m_recorder.setQuality(QMediaRecorder::VeryHighQuality);
+ m_session.setRecorder(&m_recorder);
+
+ if (hasVideo()) {
+ m_session.setVideoFrameInput(&m_videoInput);
+
+ QObject::connect(&m_videoGenerator, &VideoGenerator::frameCreated, //
+ &m_videoInput, &QVideoFrameInput::sendVideoFrame);
+
+ if (autoStop == AutoStop::EmitEmpty) {
+ m_recorder.setAutoStop(true);
+ m_videoGenerator.emitEmptyFrameOnStop();
+ }
+ }
+
+ if (hasAudio()) {
+ m_session.setAudioBufferInput(&m_audioInput);
+
+ QObject::connect(&m_audioGenerator, &AudioGenerator::audioBufferCreated, //
+ &m_audioInput, &QAudioBufferInput::sendAudioBuffer);
+
+ if (autoStop == AutoStop::EmitEmpty) {
+ m_recorder.setAutoStop(true);
+ m_audioGenerator.emitEmptyBufferOnStop();
+ }
+ }
+
+ m_tempFile.open();
+ m_recorder.setOutputLocation(m_tempFile.fileName());
+}
+
+CaptureSessionFixture::~CaptureSessionFixture()
+{
+ QFile::remove(m_recorder.actualLocation().toLocalFile());
+}
+
+void CaptureSessionFixture::connectPullMode()
+{
+ if (hasVideo())
+ QObject::connect(&m_videoInput, &QVideoFrameInput::readyToSendVideoFrame, //
+ &m_videoGenerator, &VideoGenerator::nextFrame);
+
+ if (hasAudio())
+ QObject::connect(&m_audioInput, &QAudioBufferInput::readyToSendAudioBuffer, //
+ &m_audioGenerator, &AudioGenerator::nextBuffer);
+}
+
+bool CaptureSessionFixture::waitForRecorderStopped(milliseconds duration)
+{
+ // StoppedState is emitted when media is finalized.
+ const bool stopped = QTest::qWaitFor(
+ [&] { //
+ return recorderStateChanged.contains(
+ QList<QVariant>{ QMediaRecorder::RecorderState::StoppedState });
+ },
+ duration);
+
+ if (!stopped)
+ return false;
+
+ return m_recorder.recorderState() == QMediaRecorder::StoppedState
+ && m_recorder.error() == QMediaRecorder::NoError;
+}
+
+bool CaptureSessionFixture::hasAudio() const
+{
+ return m_streamType == StreamType::Audio || m_streamType == StreamType::AudioAndVideo;
+}
+
+bool CaptureSessionFixture::hasVideo() const
+{
+ return m_streamType == StreamType::Video || m_streamType == StreamType::AudioAndVideo;
+}
+
+QT_END_NAMESPACE
diff --git a/tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.h b/tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.h
new file mode 100644
index 000000000..f7aa27a65
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/capturesessionfixture.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef CAPTURESESSIONFIXTURE_H
+#define CAPTURESESSIONFIXTURE_H
+
+#include "framegenerator.h"
+#include <QtMultimedia/qvideoframeinput.h>
+#include <QtMultimedia/qaudioinput.h>
+#include <QtMultimedia/qmediacapturesession.h>
+#include <QtMultimedia/qmediarecorder.h>
+#include <QtMultimedia/qaudiobufferinput.h>
+#include <QtCore/qtemporaryfile.h>
+
+#include <../shared/testvideosink.h>
+#include <QtTest/qsignalspy.h>
+
+QT_BEGIN_NAMESPACE
+
+enum class StreamType { Audio, Video, AudioAndVideo };
+enum class AutoStop { EmitEmpty, No };
+
+struct CaptureSessionFixture
+{
+ explicit CaptureSessionFixture(StreamType streamType, AutoStop autoStop);
+ ~CaptureSessionFixture();
+
+ void connectPullMode();
+ bool waitForRecorderStopped(milliseconds duration);
+ bool hasAudio() const;
+ bool hasVideo() const;
+
+ VideoGenerator m_videoGenerator;
+ AudioGenerator m_audioGenerator;
+ QVideoFrameInput m_videoInput;
+ QAudioBufferInput m_audioInput;
+ QMediaCaptureSession m_session;
+ QMediaRecorder m_recorder;
+ QTemporaryFile m_tempFile;
+ StreamType m_streamType = StreamType::Video;
+
+ QSignalSpy readyToSendVideoFrame{ &m_videoInput, &QVideoFrameInput::readyToSendVideoFrame };
+ QSignalSpy readyToSendAudioBuffer{ &m_audioInput, &QAudioBufferInput::readyToSendAudioBuffer };
+ QSignalSpy recorderStateChanged{ &m_recorder, &QMediaRecorder::recorderStateChanged };
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/tests/auto/integration/qmediaframeinputsbackend/framegenerator.cpp b/tests/auto/integration/qmediaframeinputsbackend/framegenerator.cpp
new file mode 100644
index 000000000..c84ee9078
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/framegenerator.cpp
@@ -0,0 +1,119 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "framegenerator.h"
+#include <QtCore/qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+void VideoGenerator::setFrameCount(int count)
+{
+ m_maxFrameCount = count;
+}
+
+void VideoGenerator::setSize(QSize size)
+{
+ m_size = size;
+}
+
+void VideoGenerator::setFrameRate(double rate)
+{
+ m_frameRate = rate;
+}
+
+void VideoGenerator::setPeriod(milliseconds period)
+{
+ m_period = period;
+}
+
+void VideoGenerator::emitEmptyFrameOnStop()
+{
+ m_emitEmptyFrameOnStop = true;
+}
+
+QVideoFrame VideoGenerator::createFrame()
+{
+ QImage image(m_size, QImage::Format_ARGB32);
+ image.fill(colors[m_frameIndex % colors.size()]);
+ QVideoFrame frame(image);
+
+ if (m_frameRate)
+ frame.setStreamFrameRate(*m_frameRate);
+
+ if (m_period) {
+ frame.setStartTime(duration_cast<microseconds>(*m_period).count() * m_frameIndex);
+ frame.setEndTime(duration_cast<microseconds>(*m_period).count() * (m_frameIndex + 1));
+ }
+
+ return frame;
+}
+
+void VideoGenerator::nextFrame()
+{
+ if (m_frameIndex == m_maxFrameCount) {
+ emit done();
+ if (m_emitEmptyFrameOnStop)
+ emit frameCreated({});
+ return;
+ }
+
+ const QVideoFrame frame = createFrame();
+ emit frameCreated(frame);
+ ++m_frameIndex;
+}
+
+AudioGenerator::AudioGenerator()
+{
+ m_format.setSampleFormat(QAudioFormat::UInt8);
+ m_format.setSampleRate(8000);
+ m_format.setChannelConfig(QAudioFormat::ChannelConfigMono);
+}
+
+void AudioGenerator::setFormat(const QAudioFormat &format)
+{
+ m_format = format;
+}
+
+void AudioGenerator::setBufferCount(int count)
+{
+ m_maxBufferCount = count;
+}
+
+void AudioGenerator::setDuration(microseconds duration)
+{
+ m_duration = duration;
+}
+
+void AudioGenerator::emitEmptyBufferOnStop()
+{
+ m_emitEmptyBufferOnStop = true;
+}
+
+QAudioBuffer AudioGenerator::createAudioBuffer()
+{
+ const microseconds bufferDuration = m_duration / m_maxBufferCount.value_or(1);
+ const qint32 byteCount = m_format.bytesForDuration(bufferDuration.count());
+ const QByteArray data(byteCount, '\0');
+
+ QAudioBuffer buffer(data, m_format);
+ return buffer;
+}
+
+void AudioGenerator::nextBuffer()
+{
+ if (m_bufferIndex == m_maxBufferCount) {
+ emit done();
+ if (m_emitEmptyBufferOnStop)
+ emit audioBufferCreated({});
+ return;
+ }
+
+ const QAudioBuffer buffer = createAudioBuffer();
+
+ emit audioBufferCreated(buffer);
+ ++m_bufferIndex;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_framegenerator.cpp"
diff --git a/tests/auto/integration/qmediaframeinputsbackend/framegenerator.h b/tests/auto/integration/qmediaframeinputsbackend/framegenerator.h
new file mode 100644
index 000000000..866707685
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/framegenerator.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef FRAMEGENERATOR_H
+#define FRAMEGENERATOR_H
+
+#include <QtCore/qobject.h>
+#include <QtCore/qlist.h>
+#include <QtMultimedia/qvideoframe.h>
+#include <QtMultimedia/qaudiobuffer.h>
+#include <functional>
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+using namespace std::chrono;
+
+class VideoGenerator : public QObject
+{
+ Q_OBJECT
+public:
+ void setFrameCount(int count);
+ void setSize(QSize size);
+ void setFrameRate(double rate);
+ void setPeriod(milliseconds period);
+ void emitEmptyFrameOnStop();
+ QVideoFrame createFrame();
+
+signals:
+ void done();
+ void frameCreated(const QVideoFrame &frame);
+
+public slots:
+ void nextFrame();
+
+private:
+ QList<QColor> colors = { Qt::red, Qt::green, Qt::blue, Qt::black, Qt::white };
+ QSize m_size{ 640, 480 };
+ std::optional<int> m_maxFrameCount;
+ int m_frameIndex = 0;
+ std::optional<double> m_frameRate;
+ std::optional<milliseconds> m_period;
+ bool m_emitEmptyFrameOnStop = false;
+};
+
+class AudioGenerator : public QObject
+{
+ Q_OBJECT
+public:
+ AudioGenerator();
+ void setFormat(const QAudioFormat &format);
+ void setBufferCount(int count);
+ void setDuration(microseconds duration);
+ void emitEmptyBufferOnStop();
+ QAudioBuffer createAudioBuffer();
+
+signals:
+ void done();
+ void audioBufferCreated(const QAudioBuffer &buffer);
+
+public slots:
+ void nextBuffer();
+
+private:
+ std::optional<int> m_maxBufferCount;
+ microseconds m_duration = 1s;
+ int m_bufferIndex = 0;
+ QAudioFormat m_format;
+ bool m_emitEmptyBufferOnStop = false;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/tests/auto/integration/qmediaframeinputsbackend/mediainfo.h b/tests/auto/integration/qmediaframeinputsbackend/mediainfo.h
new file mode 100644
index 000000000..e7525080d
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/mediainfo.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef MEDIAINFO_H
+#define MEDIAINFO_H
+
+#include <QtTest/QTest>
+#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/qmediametadata.h>
+#include <QtMultimedia/qaudiooutput.h>
+#include "../shared/testvideosink.h"
+#include <chrono>
+
+QT_USE_NAMESPACE
+
+using namespace std::chrono;
+
+// Extracts media metadata from a input media file
+struct MediaInfo
+{
+ static std::optional<MediaInfo> create(const QUrl &fileLocation)
+ {
+ QMediaPlayer player;
+ const QSignalSpy mediaStatusChanged{ &player, &QMediaPlayer::mediaStatusChanged };
+
+ QAudioOutput audioOutput;
+ player.setAudioOutput(&audioOutput);
+
+ TestVideoSink sink;
+ player.setVideoSink(&sink);
+
+ player.setSource(fileLocation);
+
+ // Loop through all frames to be able to count them
+ player.setPlaybackRate(50); // let's speed it up
+ player.play();
+
+ const bool endReached = QTest::qWaitFor(
+ [&] {
+ return mediaStatusChanged.contains(QList<QVariant>{ QMediaPlayer::EndOfMedia })
+ || mediaStatusChanged.contains(
+ QList<QVariant>{ QMediaPlayer::InvalidMedia });
+ },
+ 10min);
+
+ if (!endReached)
+ return {};
+
+ MediaInfo info{};
+ info.m_frameRate = player.metaData().value(QMediaMetaData::VideoFrameRate).toReal();
+ info.m_size = player.metaData().value(QMediaMetaData::Resolution).toSize();
+
+ info.m_duration = milliseconds{ player.duration() };
+ info.m_frameCount = sink.m_totalFrames - 1;
+ info.m_frameTimes = sink.m_frameTimes;
+ info.m_hasVideo = player.hasVideo();
+ info.m_hasAudio = player.hasAudio();
+ return info;
+ }
+
+ int m_frameCount = 0;
+ qreal m_frameRate = 0.0f;
+ QSize m_size;
+ milliseconds m_duration;
+ bool m_hasVideo = false;
+ bool m_hasAudio = false;
+
+ std::vector<TestVideoSink::TimePoint> m_frameTimes;
+};
+
+#endif
diff --git a/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.cpp b/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.cpp
new file mode 100644
index 000000000..ee4b78606
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.cpp
@@ -0,0 +1,302 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "capturesessionfixture.h"
+#include "tst_qmediaframeinputsbackend.h"
+
+#include "mediainfo.h"
+#include <QtTest/QtTest>
+#include <qvideoframeinput.h>
+#include <qaudiobufferinput.h>
+#include <qsignalspy.h>
+#include <qmediarecorder.h>
+#include <qmediaplayer.h>
+#include <../shared/testvideosink.h>
+#include <../shared/mediabackendutils.h>
+
+QT_BEGIN_NAMESPACE
+
+void tst_QMediaFrameInputsBackend::initTestCase()
+{
+ QSKIP_GSTREAMER("Not implemented in the gstreamer backend");
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderWritesAudio_whenAudioFramesInputSends_data()
+{
+ QTest::addColumn<int>("bufferCount");
+ QTest::addColumn<QAudioFormat::SampleFormat>("sampleFormat");
+ QTest::addColumn<QAudioFormat::ChannelConfig>("channelConfig");
+ QTest::addColumn<int>("sampleRate");
+ QTest::addColumn<milliseconds>("duration");
+
+#ifndef Q_OS_WINDOWS // sample rate 8000 is not supported. TODO: investigate.
+ QTest::addRow("bufferCount: 20; sampleFormat: Int16; channelConfig: Mono; sampleRate: 8000; "
+ "duration: 1000")
+ << 20 << QAudioFormat::Int16 << QAudioFormat::ChannelConfigMono << 8000 << 1000ms;
+#endif
+ QTest::addRow("bufferCount: 30; sampleFormat: Int32; channelConfig: Stereo; sampleRate: "
+ "12000; duration: 2000")
+ << 30 << QAudioFormat::Int32 << QAudioFormat::ChannelConfigStereo << 12000 << 2000ms;
+
+ // TODO: investigate fails of channels configuration
+ // QTest::addRow("bufferCount: 10; sampleFormat: UInt8; channelConfig: 2Dot1; sampleRate:
+ // 40000; duration: 1500")
+ // << 10 << QAudioFormat::UInt8 << QAudioFormat::ChannelConfig2Dot1 << 40000 << 1500;
+ // QTest::addRow("bufferCount: 10; sampleFormat: Float; channelConfig: 3Dot0; sampleRate:
+ // 50000; duration: 2500")
+ // << 40 << QAudioFormat::Float << QAudioFormat::ChannelConfig3Dot0 << 50000 << 2500;
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderWritesAudio_whenAudioFramesInputSends()
+{
+ QFETCH(const int, bufferCount);
+ QFETCH(const QAudioFormat::SampleFormat, sampleFormat);
+ QFETCH(const QAudioFormat::ChannelConfig, channelConfig);
+ QFETCH(const int, sampleRate);
+ QFETCH(const milliseconds, duration);
+
+ CaptureSessionFixture f{ StreamType::Audio, AutoStop::EmitEmpty };
+ f.connectPullMode();
+
+ QAudioFormat format;
+ format.setSampleFormat(sampleFormat);
+ format.setSampleRate(sampleRate);
+ format.setChannelConfig(channelConfig);
+
+ f.m_audioGenerator.setFormat(format);
+ f.m_audioGenerator.setBufferCount(bufferCount);
+ f.m_audioGenerator.setDuration(duration);
+
+ f.m_recorder.record();
+
+ QVERIFY(f.waitForRecorderStopped(60s));
+
+ auto info = MediaInfo::create(f.m_recorder.actualLocation());
+
+ QVERIFY(info->m_hasAudio);
+ QCOMPARE_GE(info->m_duration, duration - 50ms);
+ QCOMPARE_LE(info->m_duration, duration + 50ms);
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderWritesVideo_whenVideoFramesInputSendsFrames_data()
+{
+ QTest::addColumn<int>("framesNumber");
+ QTest::addColumn<milliseconds>("frameDuration");
+ QTest::addColumn<QSize>("resolution");
+ QTest::addColumn<bool>("setTimeStamp");
+
+ QTest::addRow("framesNumber: 5; frameRate: 2; resolution: 50x80; with time stamps")
+ << 5 << 500ms << QSize(50, 80) << true;
+ QTest::addRow("framesNumber: 20; frameRate: 1; resolution: 200x100; with time stamps")
+ << 20 << 1000ms << QSize(200, 100) << true;
+
+ QTest::addRow("framesNumber: 20; frameRate: 30; resolution: 200x100; with frame rate")
+ << 20 << 250ms << QSize(200, 100) << false;
+ QTest::addRow("framesNumber: 60; frameRate: 4; resolution: 200x100; with frame rate")
+ << 60 << 24ms << QSize(200, 100) << false;
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderWritesVideo_whenVideoFramesInputSendsFrames()
+{
+ QFETCH(const int, framesNumber);
+ QFETCH(const milliseconds, frameDuration);
+ QFETCH(const QSize, resolution);
+ QFETCH(const bool, setTimeStamp);
+
+ CaptureSessionFixture f{ StreamType::Video, AutoStop::EmitEmpty };
+ f.connectPullMode();
+ f.m_videoGenerator.setFrameCount(framesNumber);
+ f.m_videoGenerator.setSize(resolution);
+
+ const qreal frameRate = 1e6 / duration_cast<microseconds>(frameDuration).count();
+ if (setTimeStamp)
+ f.m_videoGenerator.setPeriod(frameDuration);
+ else
+ f.m_videoGenerator.setFrameRate(frameRate);
+
+ f.m_recorder.record();
+
+ QVERIFY(f.waitForRecorderStopped(60s));
+
+ auto info = MediaInfo::create(f.m_recorder.actualLocation());
+
+ QCOMPARE_LT(info->m_frameRate, frameRate * 1.001);
+ QCOMPARE_GT(info->m_frameRate, frameRate * 0.999);
+
+ QCOMPARE_LT(info->m_duration, frameDuration * framesNumber * 1.001);
+ QCOMPARE_GE(info->m_duration, frameDuration * framesNumber * 0.999);
+
+ QCOMPARE(info->m_size, resolution);
+ QCOMPARE_EQ(info->m_frameCount, framesNumber);
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderWritesVideo_withSingleFrame()
+{
+ CaptureSessionFixture f{ StreamType::Video, AutoStop::EmitEmpty };
+ f.connectPullMode();
+ f.m_videoGenerator.setFrameCount(1);
+ f.m_videoGenerator.setSize({ 640, 480 });
+ f.m_videoGenerator.setPeriod(1s);
+ f.m_recorder.record();
+ QVERIFY(f.waitForRecorderStopped(60s));
+ auto info = MediaInfo::create(f.m_recorder.actualLocation());
+
+ QCOMPARE_EQ(info->m_frameCount, 1);
+ QCOMPARE_EQ(info->m_duration, 1s);
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderStopsRecording_whenInputsReportedEndOfStream_data()
+{
+ QTest::addColumn<bool>("audioStopsFirst");
+
+ QTest::addRow("audio stops first") << true;
+ QTest::addRow("video stops first") << true;
+}
+
+void tst_QMediaFrameInputsBackend::mediaRecorderStopsRecording_whenInputsReportedEndOfStream()
+{
+ QFETCH(const bool, audioStopsFirst);
+
+ CaptureSessionFixture f{ StreamType::AudioAndVideo, AutoStop::No };
+ f.m_recorder.setAutoStop(true);
+ f.connectPullMode();
+
+ f.m_audioGenerator.setBufferCount(30);
+ f.m_videoGenerator.setFrameCount(30);
+
+ QSignalSpy audioDone{ &f.m_audioGenerator, &AudioGenerator::done };
+ QSignalSpy videoDone{ &f.m_videoGenerator, &VideoGenerator::done };
+
+ f.m_recorder.record();
+
+ audioDone.wait();
+ videoDone.wait();
+
+ if (audioStopsFirst) {
+ f.m_audioInput.sendAudioBuffer({});
+ QVERIFY(!f.waitForRecorderStopped(300ms)); // Should not stop until both streams stopped
+ f.m_videoInput.sendVideoFrame({});
+ } else {
+ f.m_videoInput.sendVideoFrame({});
+ QVERIFY(!f.waitForRecorderStopped(300ms)); // Should not stop until both streams stopped
+ f.m_audioInput.sendAudioBuffer({});
+ }
+
+ QVERIFY(f.waitForRecorderStopped(60s));
+
+ // check if the file has been written
+
+ const std::optional<MediaInfo> mediaInfo = MediaInfo::create(f.m_recorder.actualLocation());
+
+ QVERIFY(mediaInfo);
+ QVERIFY(mediaInfo->m_hasVideo);
+ QVERIFY(mediaInfo->m_hasAudio);
+}
+
+void tst_QMediaFrameInputsBackend::readyToSend_isEmitted_whenRecordingStarts_data()
+{
+ QTest::addColumn<StreamType>("streamType");
+ QTest::addRow("audio") << StreamType::Audio;
+ QTest::addRow("video") << StreamType::Video;
+ QTest::addRow("audioAndVideo") << StreamType::AudioAndVideo;
+}
+
+void tst_QMediaFrameInputsBackend::readyToSend_isEmitted_whenRecordingStarts()
+{
+ QFETCH(StreamType, streamType);
+
+ CaptureSessionFixture f{ streamType, AutoStop::No };
+
+ f.m_recorder.record();
+
+ if (f.hasAudio())
+ QTRY_COMPARE_EQ(f.readyToSendAudioBuffer.size(), 1);
+
+ if (f.hasVideo())
+ QTRY_COMPARE_EQ(f.readyToSendVideoFrame.size(), 1);
+}
+
+void tst_QMediaFrameInputsBackend::readyToSendVideoFrame_isEmitted_whenSendVideoFrameIsCalled()
+{
+ CaptureSessionFixture f{ StreamType::Video, AutoStop::No };
+
+ f.m_recorder.record();
+ QVERIFY(f.readyToSendVideoFrame.wait());
+
+ f.m_videoInput.sendVideoFrame(f.m_videoGenerator.createFrame());
+ QVERIFY(f.readyToSendVideoFrame.wait());
+
+ f.m_videoInput.sendVideoFrame(f.m_videoGenerator.createFrame());
+ QVERIFY(f.readyToSendVideoFrame.wait());
+}
+
+void tst_QMediaFrameInputsBackend::readyToSendAudioBuffer_isEmitted_whenSendAudioBufferIsCalled()
+{
+ CaptureSessionFixture f{ StreamType::Audio, AutoStop::No };
+
+ f.m_recorder.record();
+ QVERIFY(f.readyToSendAudioBuffer.wait());
+
+ f.m_audioInput.sendAudioBuffer(f.m_audioGenerator.createAudioBuffer());
+ QVERIFY(f.readyToSendAudioBuffer.wait());
+
+ f.m_audioInput.sendAudioBuffer(f.m_audioGenerator.createAudioBuffer());
+ QVERIFY(f.readyToSendAudioBuffer.wait());
+}
+
+void tst_QMediaFrameInputsBackend::readyToSendVideoFrame_isEmittedRepeatedly_whenPullModeIsEnabled()
+{
+ CaptureSessionFixture f{ StreamType::Video, AutoStop::EmitEmpty };
+ f.connectPullMode();
+
+ constexpr int expectedSignalCount = 4;
+ f.m_videoGenerator.setFrameCount(expectedSignalCount - 1);
+
+ f.m_recorder.record();
+
+ f.waitForRecorderStopped(60s);
+
+ QCOMPARE_EQ(f.readyToSendVideoFrame.size(), expectedSignalCount);
+}
+
+void tst_QMediaFrameInputsBackend::
+ readyToSendAudioBuffer_isEmittedRepeatedly_whenPullModeIsEnabled()
+{
+ CaptureSessionFixture f{ StreamType::Audio, AutoStop::EmitEmpty };
+ f.connectPullMode();
+
+ constexpr int expectedSignalCount = 4;
+ f.m_audioGenerator.setBufferCount(expectedSignalCount - 1);
+
+ f.m_recorder.record();
+
+ f.waitForRecorderStopped(60s);
+
+ QCOMPARE_EQ(f.readyToSendAudioBuffer.size(), expectedSignalCount);
+}
+
+void tst_QMediaFrameInputsBackend::
+ readyToSendAudioBufferAndVideoFrame_isEmittedRepeatedly_whenPullModeIsEnabled()
+{
+ CaptureSessionFixture f{ StreamType::AudioAndVideo, AutoStop::EmitEmpty };
+ f.connectPullMode();
+
+ constexpr int expectedSignalCount = 4;
+ f.m_audioGenerator.setBufferCount(expectedSignalCount - 1);
+ f.m_videoGenerator.setFrameCount(expectedSignalCount - 1);
+
+ f.m_recorder.record();
+
+ f.waitForRecorderStopped(60s);
+
+ QCOMPARE_EQ(f.readyToSendAudioBuffer.size(), expectedSignalCount);
+ QCOMPARE_EQ(f.readyToSendVideoFrame.size(), expectedSignalCount);
+}
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+QTEST_MAIN(tst_QMediaFrameInputsBackend)
+
+#include "moc_tst_qmediaframeinputsbackend.cpp"
diff --git a/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.h b/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.h
new file mode 100644
index 000000000..61e4d3118
--- /dev/null
+++ b/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef TST_QMEDIAFRAMEINPUTSBACKEND_H
+#define TST_QMEDIAFRAMEINPUTSBACKEND_H
+
+#include <QObject>
+
+QT_BEGIN_NAMESPACE
+
+class tst_QMediaFrameInputsBackend : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+
+ void mediaRecorderWritesAudio_whenAudioFramesInputSends_data();
+ void mediaRecorderWritesAudio_whenAudioFramesInputSends();
+
+ void mediaRecorderWritesVideo_whenVideoFramesInputSendsFrames_data();
+ void mediaRecorderWritesVideo_whenVideoFramesInputSendsFrames();
+
+ void mediaRecorderWritesVideo_withSingleFrame();
+
+ void mediaRecorderStopsRecording_whenInputsReportedEndOfStream_data();
+ void mediaRecorderStopsRecording_whenInputsReportedEndOfStream();
+
+ void readyToSend_isEmitted_whenRecordingStarts_data();
+ void readyToSend_isEmitted_whenRecordingStarts();
+
+ void readyToSendVideoFrame_isEmitted_whenSendVideoFrameIsCalled();
+ void readyToSendAudioBuffer_isEmitted_whenSendAudioBufferIsCalled();
+
+ void readyToSendVideoFrame_isEmittedRepeatedly_whenPullModeIsEnabled();
+ void readyToSendAudioBuffer_isEmittedRepeatedly_whenPullModeIsEnabled();
+ void readyToSendAudioBufferAndVideoFrame_isEmittedRepeatedly_whenPullModeIsEnabled();
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/tests/auto/integration/qmediaplayerbackend/BLACKLIST b/tests/auto/integration/qmediaplayerbackend/BLACKLIST
index e91f47755..1f24e07a1 100644
--- a/tests/auto/integration/qmediaplayerbackend/BLACKLIST
+++ b/tests/auto/integration/qmediaplayerbackend/BLACKLIST
@@ -1,32 +1,6 @@
-# QTBUG-46368
-
-osx
-windows-7
-windows-7sp1
-windows-10 msvc-2015
-windows-10 msvc-2017
-windows-10 msvc-2019
# Media player plugin not built at the moment on this platform
opensuse-13.1 64bit
-[loadMedia]
-windows 64bit developer-build
-
-[unloadMedia]
-windows 64bit developer-build
-
-[playPauseStop]
-windows 64bit developer-build
-
-[processEOS]
-windows 64bit developer-build
-
-[deleteLaterAtEOS]
-windows 64bit developer-build
-
-[initialVolume]
-windows 64bit developer-build
-
[playlist]
redhatenterpriselinuxworkstation-6.6
diff --git a/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt
new file mode 100644
index 000000000..3a9d25926
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qmediaplayerbackend.pro.
+
+#####################################################################
+## tst_qmediaplayerbackend Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ testdata/*)
+list(APPEND testdata_resource_files ${test_data_glob})
+
+qt_internal_add_test(tst_qmediaplayerbackend
+ SOURCES
+ ../shared/mediafileselector.h
+ ../shared/mediabackendutils.h
+ ../shared/testvideosink.h
+ mediaplayerstate.h
+ fake.h
+ fixture.h
+ server.h
+ tst_qmediaplayerbackend.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ Qt::MultimediaQuickPrivate
+ Qt::Qml
+ Qt::Quick
+ Qt::QuickPrivate
+ BUILTIN_TESTDATA
+ TESTDATA
+ ${testdata_resource_files}
+ "LazyLoad.qml"
+ INCLUDE_DIRECTORIES
+ ../shared/
+)
diff --git a/tests/auto/integration/qmediaplayerbackend/LazyLoad.qml b/tests/auto/integration/qmediaplayerbackend/LazyLoad.qml
new file mode 100644
index 000000000..04af13186
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/LazyLoad.qml
@@ -0,0 +1,52 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtMultimedia
+
+Rectangle {
+ id: root
+ width: 600
+ height: 800
+ color: "black"
+
+ Component {
+ id: videoOutputComponent
+
+ Item {
+ objectName: "videoPlayer"
+ property alias mediaPlayer: mediaPlayer
+ property alias videoOutput: videoOutput
+ property alias videoSink: videoOutput.videoSink
+
+ property alias playbackState: mediaPlayer.playbackState
+ property alias error: mediaPlayer.error
+
+
+ MediaPlayer {
+ id: mediaPlayer
+ objectName: "mediaPlayer"
+ source: "qrc:/testdata/colors.mp4"
+ }
+ VideoOutput {
+ id: videoOutput
+ objectName: "videoOutput"
+ anchors.fill: parent
+ }
+ }
+ }
+
+ Loader {
+ id: loader
+ objectName: "loader"
+ sourceComponent: videoOutputComponent
+ anchors.fill: parent
+ active: false
+ onActiveChanged: {
+ if (active) {
+ loader.item.mediaPlayer.videoOutput = loader.item.videoOutput
+ loader.item.mediaPlayer.play()
+ }
+ }
+ }
+}
diff --git a/tests/auto/integration/qmediaplayerbackend/fake.h b/tests/auto/integration/qmediaplayerbackend/fake.h
new file mode 100644
index 000000000..9a68741d0
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/fake.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef FAKE_H
+#define FAKE_H
+
+#include <testvideosink.h>
+
+QT_USE_NAMESPACE
+
+class TestVideoOutput : public QObject
+{
+ Q_OBJECT
+public:
+ TestVideoOutput() = default;
+
+ Q_INVOKABLE QVideoSink *videoSink() { return &m_sink; }
+
+ TestVideoSink m_sink;
+};
+
+inline void setVideoSinkAsyncFramesCounter(QVideoSink &sink, std::atomic_int &counter)
+{
+ QObject::connect(
+ &sink, &QVideoSink::videoFrameChanged, &sink, [&counter]() { ++counter; },
+ Qt::DirectConnection);
+}
+
+#endif // FAKE_H
diff --git a/tests/auto/integration/qmediaplayerbackend/fixture.h b/tests/auto/integration/qmediaplayerbackend/fixture.h
new file mode 100644
index 000000000..bf539d7ff
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/fixture.h
@@ -0,0 +1,79 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef FIXTURE_H
+#define FIXTURE_H
+
+#include <qmediaplayer.h>
+#include <qaudiooutput.h>
+#include <qtest.h>
+#include <qsignalspy.h>
+
+#include "fake.h"
+#include "testvideosink.h"
+
+QT_USE_NAMESPACE
+
+struct Fixture : QObject
+{
+ Q_OBJECT
+public:
+ Fixture()
+ : playbackStateChanged(&player, &QMediaPlayer::playbackStateChanged),
+ errorOccurred(&player, &QMediaPlayer::errorOccurred),
+ sourceChanged(&player, &QMediaPlayer::sourceChanged),
+ mediaStatusChanged(&player, &QMediaPlayer::mediaStatusChanged),
+ positionChanged(&player, &QMediaPlayer::positionChanged),
+ durationChanged(&player, &QMediaPlayer::durationChanged),
+ playbackRateChanged(&player, &QMediaPlayer::playbackRateChanged),
+ metadataChanged(&player, &QMediaPlayer::metaDataChanged),
+ volumeChanged(&output, &QAudioOutput::volumeChanged),
+ mutedChanged(&output, &QAudioOutput::mutedChanged),
+ bufferProgressChanged(&player, &QMediaPlayer::bufferProgressChanged),
+ destroyed(&player, &QObject::destroyed)
+ {
+ setVideoSinkAsyncFramesCounter(surface, framesCount);
+
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
+ }
+
+ void clearSpies()
+ {
+ playbackStateChanged.clear();
+ errorOccurred.clear();
+ sourceChanged.clear();
+ mediaStatusChanged.clear();
+ positionChanged.clear();
+ durationChanged.clear();
+ playbackRateChanged.clear();
+ metadataChanged.clear();
+ volumeChanged.clear();
+ mutedChanged.clear();
+ bufferProgressChanged.clear();
+ destroyed.clear();
+ }
+
+ QMediaPlayer player;
+ QAudioOutput output;
+ TestVideoSink surface;
+ std::atomic_int framesCount = 0;
+
+ QSignalSpy playbackStateChanged;
+ QSignalSpy errorOccurred;
+ QSignalSpy sourceChanged;
+ QSignalSpy mediaStatusChanged;
+ QSignalSpy positionChanged;
+ QSignalSpy durationChanged;
+ QSignalSpy playbackRateChanged;
+ QSignalSpy metadataChanged;
+ QSignalSpy volumeChanged;
+ QSignalSpy mutedChanged;
+ QSignalSpy bufferProgressChanged;
+ QSignalSpy destroyed;
+};
+
+// Helper to create an object that is comparable to a QSignalSpy
+using SignalList = QList<QList<QVariant>>;
+
+#endif // FIXTURE_H
diff --git a/tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h b/tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h
new file mode 100644
index 000000000..d9f2cc875
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/mediaplayerstate.h
@@ -0,0 +1,167 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef MEDIAPLAYERSTATE_H
+#define MEDIAPLAYERSTATE_H
+
+#include <QtCore/qlist.h>
+#include <QtCore/qurl.h>
+#include <QtMultimedia/qaudiooutput.h>
+#include <QtMultimedia/qmediametadata.h>
+#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/qmediatimerange.h>
+#include <QtTest/qtestcase.h>
+
+#include <optional>
+
+QT_USE_NAMESPACE
+
+/*!
+ * Helper class that simplifies testing the state of
+ * a media player against an expected state.
+ *
+ * Use the COMPARE_MEDIA_PLAYER_STATE_EQ macro to compare
+ * the media player state against the expected state.
+ *
+ * Individual properties can be ignored by comparison by
+ * assigning the std::nullopt value to the property of the
+ * expected state.
+ */
+struct MediaPlayerState
+{
+ std::optional<QList<QMediaMetaData>> audioTracks;
+ std::optional<QList<QMediaMetaData>> videoTracks;
+ std::optional<QList<QMediaMetaData>> subtitleTracks;
+ std::optional<int> activeAudioTrack;
+ std::optional<int> activeVideoTrack;
+ std::optional<int> activeSubtitleTrack;
+ std::optional<QAudioOutput*> audioOutput;
+ std::optional<QObject*> videoOutput;
+ std::optional<QVideoSink*> videoSink;
+ std::optional<QUrl> source;
+ std::optional<QIODevice const*> sourceDevice;
+ std::optional<QMediaPlayer::PlaybackState> playbackState;
+ std::optional<QMediaPlayer::MediaStatus> mediaStatus;
+ std::optional<qint64> duration;
+ std::optional<qint64> position;
+ std::optional<bool> hasAudio;
+ std::optional<bool> hasVideo;
+ std::optional<float> bufferProgress;
+ std::optional<QMediaTimeRange> bufferedTimeRange;
+ std::optional<bool> isSeekable;
+ std::optional<qreal> playbackRate;
+ std::optional<bool> isPlaying;
+ std::optional<int> loops;
+ std::optional<QMediaPlayer::Error> error;
+ std::optional<bool> isAvailable;
+ std::optional<QMediaMetaData> metaData;
+
+ /*!
+ * Read the state from an existing media player
+ */
+ explicit MediaPlayerState(const QMediaPlayer &player)
+ : audioTracks{ player.audioTracks() },
+ videoTracks{ player.videoTracks() },
+ subtitleTracks{ player.subtitleTracks() },
+ activeAudioTrack{ player.activeAudioTrack() },
+ activeVideoTrack{ player.activeVideoTrack() },
+ activeSubtitleTrack{ player.activeSubtitleTrack() },
+ audioOutput{ player.audioOutput() },
+ videoOutput{ player.videoOutput() },
+ videoSink{ player.videoSink() },
+ source{ player.source() },
+ sourceDevice{ player.sourceDevice() },
+ playbackState{ player.playbackState() },
+ mediaStatus{ player.mediaStatus() },
+ duration{ player.duration() },
+ position{ player.position() },
+ hasAudio{ player.hasAudio() },
+ hasVideo{ player.hasVideo() },
+ bufferProgress{ player.bufferProgress() },
+ bufferedTimeRange{ player.bufferedTimeRange() },
+ isSeekable{ player.isSeekable() },
+ playbackRate{ player.playbackRate() },
+ isPlaying{ player.isPlaying() },
+ loops{ player.loops() },
+ error{ player.error() },
+ isAvailable{ player.isAvailable() },
+ metaData{ player.metaData() }
+ {
+ }
+
+ /*!
+ * Creates the default state of a media player. The default state
+ * is the state the player should have when it is default constructed.
+ */
+ static MediaPlayerState defaultState()
+ {
+ MediaPlayerState state{};
+ state.audioTracks = QList<QMediaMetaData>{};
+ state.videoTracks = QList<QMediaMetaData>{};
+ state.subtitleTracks = QList<QMediaMetaData>{};
+ state.activeAudioTrack = -1;
+ state.activeVideoTrack = -1;
+ state.activeSubtitleTrack = -1;
+ state.audioOutput = nullptr;
+ state.videoOutput = nullptr;
+ state.videoSink = nullptr;
+ state.source = QUrl{};
+ state.sourceDevice = nullptr;
+ state.playbackState = QMediaPlayer::StoppedState;
+ state.mediaStatus = QMediaPlayer::NoMedia;
+ state.duration = 0;
+ state.position = 0;
+ state.hasAudio = false;
+ state.hasVideo = false;
+ state.bufferProgress = 0.0f;
+ state.bufferedTimeRange = QMediaTimeRange{};
+ state.isSeekable = false;
+ state.playbackRate = static_cast<qreal>(1);
+ state.isPlaying = false;
+ state.loops = 1;
+ state.error = QMediaPlayer::NoError;
+ state.isAvailable = true;
+ state.metaData = QMediaMetaData{};
+ return state;
+ }
+
+private:
+ MediaPlayerState() = default;
+
+};
+
+#define COMPARE_EQ_IGNORE_OPTIONAL(actual, expected) \
+ do { \
+ if ((expected).has_value()) { \
+ QCOMPARE_EQ(actual, expected); \
+ } \
+ } while (false)
+
+#define COMPARE_MEDIA_PLAYER_STATE_EQ(actual, expected) \
+ do { \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).audioTracks, (expected).audioTracks); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).videoTracks, (expected).videoTracks); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).subtitleTracks, (expected).subtitleTracks); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).activeAudioTrack, (expected).activeAudioTrack); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).activeVideoTrack, (expected).activeVideoTrack); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).activeSubtitleTrack, (expected).activeSubtitleTrack); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).source, (expected).source); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).sourceDevice, (expected).sourceDevice); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).playbackState, (expected).playbackState); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).mediaStatus, (expected).mediaStatus); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).duration, (expected).duration); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).position, (expected).position); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).hasAudio, (expected).hasAudio); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).hasVideo, (expected).hasVideo); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).bufferProgress, (expected).bufferProgress); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).bufferedTimeRange, (expected).bufferedTimeRange); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).isSeekable, (expected).isSeekable); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).playbackRate, (expected).playbackRate); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).isPlaying, (expected).isPlaying); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).loops, (expected).loops); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).error, (expected).error); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).isAvailable, (expected).isAvailable); \
+ COMPARE_EQ_IGNORE_OPTIONAL((actual).metaData, (expected).metaData); \
+ } while (false)
+
+#endif // MEDIAPLAYERSTATE_H
diff --git a/tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro b/tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro
deleted file mode 100644
index 6dd1e8d62..000000000
--- a/tests/auto/integration/qmediaplayerbackend/qmediaplayerbackend.pro
+++ /dev/null
@@ -1,20 +0,0 @@
-TARGET = tst_qmediaplayerbackend
-
-QT += multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-
-SOURCES += \
- tst_qmediaplayerbackend.cpp
-
-HEADERS += \
- ../shared/mediafileselector.h
-
-TESTDATA += testdata/*
-
-boot2qt: {
- # OGV testing is unstable with qemu
- QMAKE_CXXFLAGS += -DSKIP_OGV_TEST
-}
diff --git a/tests/auto/integration/qmediaplayerbackend/server.h b/tests/auto/integration/qmediaplayerbackend/server.h
new file mode 100644
index 000000000..a6fde1a7a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/server.h
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef SERVER_H
+#define SERVER_H
+
+#include <private/qglobal_p.h>
+
+#ifdef QT_FEATURE_network
+
+#include <qstring.h>
+#include <qtcpserver.h>
+#include <qtest.h>
+#include <qurl.h>
+
+QT_USE_NAMESPACE
+
+class UnResponsiveRtspServer : public QObject
+{
+ Q_OBJECT
+public:
+ UnResponsiveRtspServer() : m_server{ new QTcpServer{ this } }
+ {
+ connect(m_server, &QTcpServer::newConnection, this, [&] { m_connected = true; });
+ }
+
+ bool listen() { return m_server->listen(QHostAddress::LocalHost); }
+
+ bool waitForConnection()
+ {
+ return QTest::qWaitFor([this] { return m_connected; });
+ }
+
+ QUrl address() const
+ {
+ return QUrl{ QString{ "rtsp://%1:%2" }
+ .arg(m_server->serverAddress().toString())
+ .arg(m_server->serverPort()) };
+ }
+
+private:
+ QTcpServer *m_server;
+ bool m_connected = false;
+};
+
+#endif // QT_FEATURE_network
+
+#endif // SERVER_H
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4
new file mode 100644
index 000000000..96ae2e4e3
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4
new file mode 100644
index 000000000..6cf08011c
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/BigBuckBunny.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4
new file mode 100644
index 000000000..dfeec1546
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4
new file mode 100644
index 000000000..147adf777
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm b/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm
new file mode 100644
index 000000000..048d02e8a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4
new file mode 100644
index 000000000..8824fe8c9
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/busMpeg4.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4
new file mode 100644
index 000000000..a3661b9d2
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4
new file mode 100644
index 000000000..9a60850d6
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4
new file mode 100644
index 000000000..b3ecd486d
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4
new file mode 100644
index 000000000..dc620d05f
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm b/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm
new file mode 100644
index 000000000..87b737949
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov b/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov
new file mode 100644
index 000000000..c5a508a1f
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3
index 2435f65b8..892c2a89e 100644
--- a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4
new file mode 100644
index 000000000..6b67a3433
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4
new file mode 100644
index 000000000..b0d9b3593
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4
new file mode 100644
index 000000000..55baed13e
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
index b0d84b836..209295e78 100644
--- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
+++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp
@@ -1,48 +1,71 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QDebug>
-#include <qabstractvideosurface.h>
-#include "qmediaservice.h"
#include "qmediaplayer.h"
-#include "qaudioprobe.h"
-#include "qvideoprobe.h"
-#include <qmediaplaylist.h>
+#include "mediaplayerstate.h"
+#include "fake.h"
+#include "fixture.h"
+#include "server.h"
#include <qmediametadata.h>
-
-#include "../shared/mediafileselector.h"
-//TESTED_COMPONENT=src/multimedia
-
+#include <qaudiobuffer.h>
+#include <qvideosink.h>
+#include <qvideoframe.h>
+#include <qaudiooutput.h>
+#if QT_CONFIG(process)
+#include <qprocess.h>
+#endif
+#include <private/qglobal_p.h>
+#ifdef QT_FEATURE_network
+#include <qtcpserver.h>
+#endif
+#include <qmediatimerange.h>
+#include <private/qplatformvideosink_p.h>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQml/qqmlproperty.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+#include <QtQuick/private/qquickloader_p.h>
+
+#include "mediafileselector.h"
+#include "mediabackendutils.h"
#include <QtMultimedia/private/qtmultimedia-config_p.h>
+#include "private/qquickvideooutput_p.h"
+
+#include <array>
QT_USE_NAMESPACE
+using namespace Qt::Literals;
+
+namespace {
+static qreal colorDifference(QRgb first, QRgb second)
+{
+ const auto diffVector = QVector3D(qRed(first), qGreen(first), qBlue(first))
+ - QVector3D(qRed(second), qGreen(second), qBlue(second));
+ static const auto normalizationFactor = 1. / (255 * qSqrt(3.));
+ return diffVector.length() * normalizationFactor;
+}
+
+template <typename It>
+It findSimilarColor(It it, It end, QRgb color)
+{
+ return std::min_element(it, end, [color](QRgb first, QRgb second) {
+ return colorDifference(first, color) < colorDifference(second, color);
+ });
+}
+
+template <typename Colors>
+auto findSimilarColorIndex(const Colors &colors, QRgb color)
+{
+ return std::distance(std::begin(colors),
+ findSimilarColor(std::begin(colors), std::end(colors), color));
+}
+}
+
/*
This is the backend conformance test.
@@ -54,529 +77,1645 @@ class tst_QMediaPlayerBackend : public QObject
{
Q_OBJECT
public slots:
- void init();
- void cleanup();
void initTestCase();
+ void init() { m_fixture = std::make_unique<Fixture>(); }
+ void cleanup() { m_fixture = nullptr; }
private slots:
- void construction();
- void loadMedia();
- void unloadMedia();
- void loadMediaInLoadingState();
- void playPauseStop();
+ void testMediaFilesAreSupported();
+ void destructor_cancelsPreviousSetSource_whenServerDoesNotRespond();
+ void destructor_emitsOnlyQObjectDestroyedSignal_whenPlayerIsRunning();
+
+ void getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer_data() const;
+ void getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer() const;
+
+ void setSource_emitsSourceChanged_whenCalledWithInvalidFile();
+ void setSource_emitsError_whenCalledWithInvalidFile();
+ void setSource_emitsMediaStatusChange_whenCalledWithInvalidFile();
+ void setSource_doesNotEmitPlaybackStateChange_whenCalledWithInvalidFile();
+ void setSource_setsSourceMediaStatusAndError_whenCalledWithInvalidFile();
+ void setSource_silentlyCancelsPreviousCall_whenServerDoesNotRespond();
+ void setSource_changesSourceAndMediaStatus_whenCalledWithValidFile();
+ void setSource_updatesExpectedAttributes_whenMediaHasLoaded();
+ void setSource_stopsAndEntersErrorState_whenPlayerWasPlaying();
+ void setSource_loadsAudioTrack_whenCalledWithValidWavFile();
+ void setSource_resetsState_whenCalledWithEmptyUrl();
+ void setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded();
+ void setSource_loadsCorrectTracks_whenLoadingMediaInSequence();
+ void setSource_remainsInStoppedState_whenPlayerWasStopped();
+ void setSource_entersStoppedState_whenPlayerWasPlaying();
+ void setSource_emitsError_whenSdpFileIsLoaded();
+ void setSource_updatesTrackProperties_data();
+ void setSource_updatesTrackProperties();
+ void setSource_emitsTracksChanged_data();
+ void setSource_emitsTracksChanged();
+
+ void setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio_data();
+ void setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio();
+
+ void pause_doesNotChangePlayerState_whenInvalidFileLoaded();
+ void pause_doesNothing_whenMediaIsNotLoaded();
+ void pause_entersPauseState_whenPlayerWasPlaying();
+
+ void play_resetsErrorState_whenCalledWithInvalidFile();
+ void play_resumesPlaying_whenValidMediaIsProvidedAfterInvalidMedia();
+ void play_doesNothing_whenMediaIsNotLoaded();
+ void play_setsPlaybackStateAndMediaStatus_whenValidFileIsLoaded();
+ void play_startsPlaybackAndChangesPosition_whenValidFileIsLoaded();
+ void play_doesNotEnterMediaLoadingState_whenResumingPlayingAfterStop();
+ void playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl();
+ void play_createsFramesWithExpectedContentAndIncreasingFrameTime_whenPlayingRtspMediaStream();
+ void play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames();
+ void play_startsPlayback_withAndWithoutOutputsConnected();
+ void play_startsPlayback_withAndWithoutOutputsConnected_data();
+ void play_playsRtpStream_whenSdpFileIsLoaded();
+
+ void stop_entersStoppedState_whenPlayerWasPaused();
+ void stop_setsPositionToZero_afterPlayingToEndOfMedia();
+
+ void playbackRate_returnsOne_byDefault();
+ void setPlaybackRate_changesPlaybackRateAndEmitsSignal_data();
+ void setPlaybackRate_changesPlaybackRateAndEmitsSignal();
+
+ void setVolume_changesVolume_whenVolumeIsInRange();
+ void setVolume_clampsToRange_whenVolumeIsOutsideRange();
+ void setVolume_doesNotChangeMutedState();
+
+ void setMuted_changesMutedState_whenMutedStateChanged();
+ void setMuted_doesNotChangeVolume();
+
void processEOS();
void deleteLaterAtEOS();
- void volumeAndMuted();
+
void volumeAcrossFiles_data();
void volumeAcrossFiles();
void initialVolume();
void seekPauseSeek();
void seekInStoppedState();
void subsequentPlayback();
- void probes();
- void playlist();
- void playlistObject();
- void surfaceTest_data();
void surfaceTest();
- void multipleSurfaces();
void metadata();
+ void metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail_data();
+ void metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail();
+ void metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent_data();
+ void metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent();
void playerStateAtEOS();
void playFromBuffer();
+ void audioVideoAvailable();
+ void audioVideoAvailable_updatedOnNewMedia();
+ void isSeekable();
+ void positionAfterSeek();
+ void pause_rendersVideoAtCorrectResolution_data();
+ void pause_rendersVideoAtCorrectResolution();
+ void position();
+ void multipleMediaPlayback();
+ void multiplePlaybackRateChangingStressTest();
+ void multipleSeekStressTest();
+ void setPlaybackRate_changesActualRateAndFramesRenderingTime_data();
+ void setPlaybackRate_changesActualRateAndFramesRenderingTime();
+ void durationDetectionIssues_data();
+ void durationDetectionIssues();
+ void finiteLoops();
+ void infiniteLoops();
+ void seekOnLoops();
+ void changeLoopsOnTheFly();
+ void seekAfterLoopReset();
+ void changeVideoOutputNoFramesLost();
+ void cleanSinkAndNoMoreFramesAfterStop();
+ void lazyLoadVideo();
+ void videoSinkSignals();
+ void nonAsciiFileName();
+ void setMedia_setsVideoSinkSize_beforePlaying();
+ void play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata_data();
+ void play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata();
private:
- QMediaContent selectVideoFile(const QStringList& mediaCandidates);
- bool isWavSupported();
+ QUrl selectVideoFile(const QStringList &mediaCandidates);
- //one second local wav file
- QMediaContent localWavFile;
- QMediaContent localWavFile2;
- QMediaContent localVideoFile;
- QMediaContent localCompressedSoundFile;
- QMediaContent localFileWithMetadata;
-
- bool m_inCISystem;
+ bool canCreateRtpStream() const;
+#if QT_CONFIG(process)
+ std::unique_ptr<QProcess> createRtpStreamProcess(QString fileName, QString sdpUrl);
+#endif
+ void detectVlcCommand();
+
+ // one second local wav file
+ MaybeUrl m_localWavFile = QUnexpect{};
+ MaybeUrl m_localWavFile2 = QUnexpect{};
+ MaybeUrl m_localVideoFile = QUnexpect{};
+ MaybeUrl m_localVideoFile2 = QUnexpect{};
+ MaybeUrl m_av1File = QUnexpect{};
+ MaybeUrl m_videoDimensionTestFile = QUnexpect{};
+ MaybeUrl m_localCompressedSoundFile = QUnexpect{};
+ MaybeUrl m_localFileWithMetadata = QUnexpect{};
+ MaybeUrl m_localVideoFile3ColorsWithSound = QUnexpect{};
+ MaybeUrl m_videoFileWithJpegThumbnail = QUnexpect{};
+ MaybeUrl m_videoFileWithPngThumbnail = QUnexpect{};
+ MaybeUrl m_oneRedFrameVideo = QUnexpect{};
+ MaybeUrl m_192x108_PAR_2_3_Video = QUnexpect{};
+ MaybeUrl m_192x108_PAR_3_2_Video = QUnexpect{};
+ MaybeUrl m_colorMatrixVideo = QUnexpect{};
+ MaybeUrl m_colorMatrix90degClockwiseVideo = QUnexpect{};
+ MaybeUrl m_colorMatrix180degClockwiseVideo = QUnexpect{};
+ MaybeUrl m_colorMatrix270degClockwiseVideo = QUnexpect{};
+ MaybeUrl m_hdrVideo = QUnexpect{};
+
+ MediaFileSelector m_mediaSelector;
+
+ const std::array<QRgb, 3> m_video3Colors = { { 0xFF0000, 0x00FF00, 0x0000FF } };
+ QString m_vlcCommand;
+
+ std::unique_ptr<Fixture> m_fixture;
};
-/*
- This is a simple video surface which records all presented frames.
-*/
-class TestVideoSurface : public QAbstractVideoSurface
+static bool commandExists(const QString &command)
{
- Q_OBJECT
-public:
- explicit TestVideoSurface(bool storeFrames = true);
- void setSupportedFormats(const QList<QVideoFrame::PixelFormat>& formats) { m_supported = formats; }
+#if defined(Q_OS_WINDOWS)
+ static constexpr QChar separator = ';';
+#else
+ static constexpr QChar separator = ':';
+#endif
+ static const QStringList pathDirs = qEnvironmentVariable("PATH").split(separator);
+ return std::any_of(pathDirs.cbegin(), pathDirs.cend(), [&command](const QString &dir) {
+ QString fullPath = QDir(dir).filePath(command);
+ return QFile::exists(fullPath);
+ });
+}
- //video surface
- QList<QVideoFrame::PixelFormat> supportedPixelFormats(
- QAbstractVideoBuffer::HandleType handleType = QAbstractVideoBuffer::NoHandle) const;
+static std::unique_ptr<QTemporaryFile> copyResourceToTemporaryFile(QString resource,
+ QString filePattern)
+{
+ QFile resourceFile(resource);
+ if (!resourceFile.open(QIODeviceBase::ReadOnly))
+ return nullptr;
- bool start(const QVideoSurfaceFormat &format);
- void stop();
- bool present(const QVideoFrame &frame);
+ auto temporaryFile = std::make_unique<QTemporaryFile>(filePattern);
+ if (!temporaryFile->open())
+ return nullptr;
- QList<QVideoFrame> m_frameList;
- int m_totalFrames; // used instead of the list when frames are not stored
+ QByteArray bytes = resourceFile.readAll();
+ QDataStream stream(temporaryFile.get());
+ stream.writeRawData(bytes.data(), bytes.length());
-private:
- bool m_storeFrames;
- QList<QVideoFrame::PixelFormat> m_supported;
-};
+ temporaryFile->close();
-class ProbeDataHandler : public QObject
+ return temporaryFile;
+}
+
+void tst_QMediaPlayerBackend::detectVlcCommand()
{
- Q_OBJECT
+ m_vlcCommand = qEnvironmentVariable("QT_VLC_COMMAND");
-public:
- ProbeDataHandler() : isVideoFlushCalled(false) { }
+ if (!m_vlcCommand.isEmpty())
+ return;
- QList<QVideoFrame> m_frameList;
- QList<QAudioBuffer> m_bufferList;
- bool isVideoFlushCalled;
+#if defined(Q_OS_WINDOWS)
+ m_vlcCommand = "vlc.exe";
+#else
+ m_vlcCommand = "vlc";
+#endif
+ if (commandExists(m_vlcCommand))
+ return;
-public slots:
- void processFrame(const QVideoFrame&);
- void processBuffer(const QAudioBuffer&);
- void flushVideo();
- void flushAudio();
-};
+ m_vlcCommand.clear();
+
+#if defined(Q_OS_MACOS)
+ m_vlcCommand = "/Applications/VLC.app/Contents/MacOS/VLC";
+#elif defined(Q_OS_WINDOWS)
+ m_vlcCommand = "C:/Program Files/VideoLAN/VLC/vlc.exe";
+#endif
-void tst_QMediaPlayerBackend::init()
+ if (!QFile::exists(m_vlcCommand))
+ m_vlcCommand.clear();
+}
+
+bool tst_QMediaPlayerBackend::canCreateRtpStream() const
{
+ return !m_vlcCommand.isEmpty();
}
-QMediaContent tst_QMediaPlayerBackend::selectVideoFile(const QStringList& mediaCandidates)
+void tst_QMediaPlayerBackend::initTestCase()
{
- // select supported video format
+#ifdef Q_OS_ANDROID
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-118571");
+#endif
+
QMediaPlayer player;
- TestVideoSurface *surface = new TestVideoSurface;
- player.setVideoOutput(surface);
+ if (!player.isAvailable())
+ QSKIP("Media player service is not available");
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ qRegisterMetaType<MaybeUrl>();
- for (const QString &s : mediaCandidates) {
- QFileInfo videoFile(s);
- if (!videoFile.exists())
- continue;
- QMediaContent media = QMediaContent(QUrl::fromLocalFile(videoFile.absoluteFilePath()));
- player.setMedia(media);
- player.pause();
+ m_localWavFile = m_mediaSelector.select("qrc:/testdata/test.wav");
+ m_localWavFile2 = m_mediaSelector.select("qrc:/testdata/_test.wav");
- for (int i = 0; i < 2000 && surface->m_frameList.isEmpty() && errorSpy.isEmpty(); i+=50) {
- QTest::qWait(50);
- }
+ m_localVideoFile =
+ m_mediaSelector.select("qrc:/testdata/colors.mp4", "qrc:/testdata/colors.ogv");
- if (!surface->m_frameList.isEmpty() && errorSpy.isEmpty()) {
- return media;
- }
- errorSpy.clear();
- }
+ m_localVideoFile3ColorsWithSound =
+ m_mediaSelector.select("qrc:/testdata/3colors_with_sound_1s.mp4");
+
+ m_videoFileWithJpegThumbnail =
+ m_mediaSelector.select("qrc:/testdata/audio_video_with_jpg_thumbnail.mp4");
+
+ m_videoFileWithPngThumbnail =
+ m_mediaSelector.select("qrc:/testdata/audio_video_with_png_thumbnail.mp4");
+
+#ifndef Q_OS_MACOS // QTBUG-119711 Add support for AV1 decoding with the FFmpeg backend in online installer
+ m_av1File = m_mediaSelector.select("qrc:/testdata/busAv1.webm");
+#endif
+
+ m_localVideoFile2 =
+ m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4", "qrc:/testdata/busMpeg4.mp4");
+
+ m_videoDimensionTestFile = m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4");
+
+ m_localCompressedSoundFile =
+ m_mediaSelector.select("qrc:/testdata/nokia-tune.mp3", "qrc:/testdata/nokia-tune.mkv");
+
+ m_localFileWithMetadata = m_mediaSelector.select("qrc:/testdata/nokia-tune.mp3");
+
+ m_oneRedFrameVideo = m_mediaSelector.select("qrc:/testdata/one_red_frame.mp4");
+
+ m_192x108_PAR_2_3_Video = m_mediaSelector.select("qrc:/testdata/par_2_3.mp4");
+ m_192x108_PAR_3_2_Video = m_mediaSelector.select("qrc:/testdata/par_3_2.mp4");
- return QMediaContent();
+ m_colorMatrixVideo = m_mediaSelector.select("qrc:/testdata/color_matrix.mp4");
+ m_colorMatrix90degClockwiseVideo =
+ m_mediaSelector.select("qrc:/testdata/color_matrix_90_deg_clockwise.mp4");
+ m_colorMatrix180degClockwiseVideo =
+ m_mediaSelector.select("qrc:/testdata/color_matrix_180_deg_clockwise.mp4");
+ m_colorMatrix270degClockwiseVideo =
+ m_mediaSelector.select("qrc:/testdata/color_matrix_270_deg_clockwise.mp4");
+
+ m_hdrVideo = m_mediaSelector.select("qrc:/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov");
+
+ detectVlcCommand();
}
-bool tst_QMediaPlayerBackend::isWavSupported()
+void tst_QMediaPlayerBackend::testMediaFilesAreSupported()
{
- return !localWavFile.isNull();
+ const auto mediaSelectionErrors = m_mediaSelector.dumpErrors();
+ if (!mediaSelectionErrors.isEmpty())
+ qDebug().noquote() << "Dump media selection errors:\n" << mediaSelectionErrors;
+
+ // TODO: probalbly, we should check errors anyway; TBD.
+ QCOMPARE(m_mediaSelector.failedSelectionsCount(), 0);
}
-void tst_QMediaPlayerBackend::initTestCase()
+void tst_QMediaPlayerBackend::destructor_cancelsPreviousSetSource_whenServerDoesNotRespond()
+{
+#ifdef QT_FEATURE_network
+ UnResponsiveRtspServer server;
+ QVERIFY(server.listen());
+
+ auto player = std::make_unique<QMediaPlayer>();
+ player->setSource(server.address());
+
+ QVERIFY(server.waitForConnection());
+
+ // Cancel connection (should be fast, but can't be reliably verified
+ // in a test. For now we just verify that we don't crash.
+ player = nullptr;
+#else
+ QSKIP("Test requires network feature");
+#endif
+}
+
+void tst_QMediaPlayerBackend::destructor_emitsOnlyQObjectDestroyedSignal_whenPlayerIsRunning()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+
+ // Wait for started
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.~QMediaPlayer();
+ new (&m_fixture->player) QMediaPlayer;
+
+ // Assert
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 0);
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+ QCOMPARE(m_fixture->sourceChanged.size(), 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.size(), 0);
+ QCOMPARE(m_fixture->positionChanged.size(), 0);
+ QCOMPARE(m_fixture->durationChanged.size(), 0);
+ QCOMPARE(m_fixture->metadataChanged.size(), 0);
+ QCOMPARE(m_fixture->volumeChanged.size(), 0);
+ QCOMPARE(m_fixture->mutedChanged.size(), 0);
+ QCOMPARE(m_fixture->bufferProgressChanged.size(), 0);
+ QCOMPARE(m_fixture->destroyed.size(), 1);
+}
+
+void tst_QMediaPlayerBackend::
+ getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer_data() const
+{
+ QTest::addColumn<bool>("hasAudioOutput");
+ QTest::addColumn<bool>("hasVideoOutput");
+ QTest::addColumn<bool>("hasVideoSink");
+
+ QTest::newRow("noOutput") << false << false << false;
+ QTest::newRow("withAudioOutput") << true << false << false;
+ QTest::newRow("withVideoOutput") << false << true << false;
+ QTest::newRow("withVideoSink") << false << false << true;
+ QTest::newRow("withAllOutputs") << true << true << true;
+}
+
+void tst_QMediaPlayerBackend::getters_returnExpectedValues_whenCalledWithDefaultConstructedPlayer()
+ const
{
+ QFETCH(const bool, hasAudioOutput);
+ QFETCH(const bool, hasVideoOutput);
+ QFETCH(const bool, hasVideoSink);
+
+ QAudioOutput audioOutput;
+ TestVideoOutput videoOutput;
+
QMediaPlayer player;
- if (!player.isAvailable())
- QSKIP("Media player service is not available");
- qRegisterMetaType<QMediaContent>();
+ if (hasAudioOutput)
+ player.setAudioOutput(&audioOutput);
+
+ if (hasVideoOutput)
+ player.setVideoOutput(&videoOutput);
+
+ if (hasVideoSink)
+ player.setVideoSink(videoOutput.videoSink());
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.audioOutput = hasAudioOutput ? &audioOutput : nullptr;
+ expectedState.videoOutput = (hasVideoOutput && !hasVideoSink) ? &videoOutput : nullptr;
+ expectedState.videoSink = (hasVideoSink || hasVideoOutput) ? videoOutput.videoSink() : nullptr;
+
+ const MediaPlayerState actualState{ player };
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::setSource_emitsSourceChanged_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ QCOMPARE_EQ(m_fixture->sourceChanged, SignalList({ { QUrl("Some not existing media") } }));
+}
+
+void tst_QMediaPlayerBackend::setSource_emitsError_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ QCOMPARE_EQ(m_fixture->errorOccurred[0][0], QMediaPlayer::ResourceError);
+}
+
+void tst_QMediaPlayerBackend::setSource_emitsMediaStatusChange_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia }, { QMediaPlayer::InvalidMedia } }));
+}
+
+void tst_QMediaPlayerBackend::setSource_doesNotEmitPlaybackStateChange_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
- localWavFile = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/test.wav"));
- localWavFile2 = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/_test.wav"));;
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+}
+
+void tst_QMediaPlayerBackend::setSource_setsSourceMediaStatusAndError_whenCalledWithInvalidFile()
+{
+ const QUrl invalidFile{ "Some not existing media" };
- QStringList mediaCandidates;
- mediaCandidates << QFINDTESTDATA("testdata/colors.mp4");
-#ifndef SKIP_OGV_TEST
- mediaCandidates << QFINDTESTDATA("testdata/colors.ogv");
+ m_fixture->player.setSource(invalidFile);
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.source = invalidFile;
+ expectedState.mediaStatus = QMediaPlayer::InvalidMedia;
+ expectedState.error = QMediaPlayer::ResourceError;
+
+ const MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::setSource_silentlyCancelsPreviousCall_whenServerDoesNotRespond()
+{
+#ifdef QT_FEATURE_network
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ UnResponsiveRtspServer server;
+
+ QVERIFY(server.listen());
+
+ m_fixture->player.setSource(server.address());
+ QVERIFY(server.waitForConnection());
+
+ m_fixture->player.setSource(*m_localVideoFile);
+
+ // Cancellation can not be reliably verified due to relatively short timeout,
+ // but we can verify that the player is in the correct state.
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Cancellation is silent
+ QVERIFY(m_fixture->errorOccurred.empty());
+
+ // Media status is emitted as if only one file was loaded
+ const SignalList expectedMediaStatus = { { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia } };
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged, expectedMediaStatus);
+
+ // Two media source changed signals should be emitted still
+ const SignalList expectedSource = { { server.address() }, { *m_localVideoFile } };
+ QCOMPARE_EQ(m_fixture->sourceChanged, expectedSource);
+
+#else
+ QSKIP("Test requires network feature");
#endif
- localVideoFile = MediaFileSelector::selectMediaFile(mediaCandidates);
+}
+
+void tst_QMediaPlayerBackend::setSource_changesSourceAndMediaStatus_whenCalledWithValidFile()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
- mediaCandidates.clear();
- mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mp3");
- mediaCandidates << QFINDTESTDATA("testdata/nokia-tune.mkv");
- localCompressedSoundFile = MediaFileSelector::selectMediaFile(mediaCandidates);
+ m_fixture->player.setSource(*m_localVideoFile);
- localFileWithMetadata = MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA("testdata/nokia-tune.mp3"));
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadingMedia } }));
- qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10);
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ expectedState.source = *m_localVideoFile;
+ expectedState.mediaStatus = QMediaPlayer::LoadingMedia;
+
+ if (isGStreamerPlatform()) // gstreamer synchronously identifies file streams as seekable
+ expectedState.isSeekable = true;
+
+ MediaPlayerState actualState{ m_fixture->player };
+
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures");
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
}
-void tst_QMediaPlayerBackend::cleanup()
+void tst_QMediaPlayerBackend::setSource_updatesExpectedAttributes_whenMediaHasLoaded()
{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ m_fixture->player.setSource(*m_localVideoFile);
+
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ MediaPlayerState expectedState = MediaPlayerState::defaultState();
+
+ // Modify all attributes that are supposed to change with this media file
+ // All other state variables are verified to be unchanged.
+ expectedState.source = *m_localVideoFile;
+ expectedState.mediaStatus = QMediaPlayer::LoadedMedia;
+ expectedState.audioTracks = std::nullopt; // Don't compare
+ expectedState.videoTracks = std::nullopt; // Don't compare
+ expectedState.activeAudioTrack = 0;
+ expectedState.activeVideoTrack = 0;
+
+ if (isGStreamerPlatform())
+ expectedState.duration = 15019;
+ else if (isDarwinPlatform())
+ expectedState.duration = 15000;
+ else
+ expectedState.duration = 15018;
+ expectedState.hasAudio = true;
+ expectedState.hasVideo = true;
+ expectedState.isSeekable = true;
+ expectedState.metaData = std::nullopt; // Don't compare
+
+ if (isGStreamerPlatform())
+ expectedState.bufferProgress = std::nullopt; // QTBUG-124633: can change before play()
+
+ MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
}
-void tst_QMediaPlayerBackend::construction()
+void tst_QMediaPlayerBackend::setSource_stopsAndEntersErrorState_whenPlayerWasPlaying()
{
- QMediaPlayer player;
- QTRY_VERIFY(player.isAvailable());
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->framesCount > 0);
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+
+ // Act
+ m_fixture->player.setSource(QUrl("Some not existing media"));
+
+ // Assert
+ const int savedFramesCount = m_fixture->framesCount;
+
+ QCOMPARE(m_fixture->player.source(), QUrl("Some not existing media"));
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::InvalidMedia);
+ QTRY_COMPARE(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ QVERIFY(!m_fixture->surface.videoFrame().isValid());
+
+ QCOMPARE(m_fixture->errorOccurred.size(), 1);
+
+ QTest::qWait(20);
+ QCOMPARE(m_fixture->framesCount, savedFramesCount);
}
-void tst_QMediaPlayerBackend::loadMedia()
+void tst_QMediaPlayerBackend::setSource_loadsAudioTrack_whenCalledWithValidWavFile()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
- QMediaPlayer player;
+ m_fixture->player.setSource(*m_localWavFile);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
+ QVERIFY(m_fixture->player.mediaStatus() != QMediaPlayer::NoMedia);
+ QVERIFY(m_fixture->player.mediaStatus() != QMediaPlayer::InvalidMedia);
+ QVERIFY(m_fixture->player.source() == *m_localWavFile);
- player.setMedia(localWavFile);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 0);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->sourceChanged.size(), 1);
+ QCOMPARE(m_fixture->sourceChanged.last()[0].value<QUrl>(), *m_localWavFile);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QVERIFY(player.mediaStatus() != QMediaPlayer::NoMedia);
- QVERIFY(player.mediaStatus() != QMediaPlayer::InvalidMedia);
- QVERIFY(player.media() == localWavFile);
- QVERIFY(player.currentMedia() == localWavFile);
+ QVERIFY(m_fixture->player.hasAudio());
+ QVERIFY(!m_fixture->player.hasVideo());
+}
- QCOMPARE(stateSpy.count(), 0);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(mediaSpy.last()[0].value<QMediaContent>(), localWavFile);
- QCOMPARE(currentMediaSpy.last()[0].value<QMediaContent>(), localWavFile);
+void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ // Load valid media and start playing
+ m_fixture->player.setSource(*m_localWavFile);
- QVERIFY(player.isAudioAvailable());
- QVERIFY(!player.isVideoAvailable());
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ QVERIFY(m_fixture->player.position() == 0);
+#ifdef Q_OS_QNX
+ // QNX mm-renderer only updates the duration when 'play' is triggered
+ QVERIFY(m_fixture->player.duration() == 0);
+#else
+ QVERIFY(m_fixture->player.duration() > 0);
+#endif
+
+ m_fixture->player.play();
+
+ QTRY_VERIFY(m_fixture->player.position() > 0);
+ QVERIFY(m_fixture->player.duration() > 0);
+
+ // Set empty URL and verify that state is fully reset to default
+ m_fixture->clearSpies();
+
+ m_fixture->player.setSource(QUrl());
+
+ QVERIFY(!m_fixture->mediaStatusChanged.isEmpty());
+ QVERIFY(!m_fixture->sourceChanged.isEmpty());
+
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
}
-void tst_QMediaPlayerBackend::unloadMedia()
+void tst_QMediaPlayerBackend::setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Load media and wait for it to completely load
+ m_fixture->player.setSource(*m_localWavFile2);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Load another media file, play it, and wait for it to enter playing state
+ m_fixture->player.setSource(*m_localWavFile);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ // Load first file again, and wait for it to start loading
+ m_fixture->player.setSource(*m_localWavFile2);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadingMedia);
+}
- QMediaPlayer player;
- player.setNotifyInterval(50);
+void tst_QMediaPlayerBackend::setSource_loadsCorrectTracks_whenLoadingMediaInSequence()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+ CHECK_SELECTED_URL(m_localWavFile2);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
- QSignalSpy durationSpy(&player, SIGNAL(positionChanged(qint64)));
+ // Load audio/video file, play it, and verify that both tracks are loaded
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_COMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QVERIFY(m_fixture->surface.waitForFrame().isValid());
+ QVERIFY(m_fixture->player.hasAudio());
+ QVERIFY(m_fixture->player.hasVideo());
- player.setMedia(localWavFile);
+ m_fixture->clearSpies();
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ // Load an audio file, and verify that only audio track is loaded
+ m_fixture->player.setSource(*m_localWavFile2);
- QVERIFY(player.position() == 0);
- QVERIFY(player.duration() > 0);
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
- player.play();
+ QCOMPARE(m_fixture->player.source(), *m_localWavFile2);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 1);
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+ QVERIFY(m_fixture->player.hasAudio());
+ QVERIFY(!m_fixture->player.hasVideo());
+ QVERIFY(!m_fixture->surface.videoFrame().isValid());
- QTRY_VERIFY(player.position() > 0);
- QVERIFY(player.duration() > 0);
+ m_fixture->player.play();
- stateSpy.clear();
- statusSpy.clear();
- mediaSpy.clear();
- currentMediaSpy.clear();
- positionSpy.clear();
- durationSpy.clear();
+ // Load video only file, and verify that only video track is loaded
+ m_fixture->player.setSource(*m_localVideoFile2);
- player.setMedia(QMediaContent());
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
- QVERIFY(player.position() <= 0);
- QVERIFY(player.duration() <= 0);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(player.media(), QMediaContent());
- QCOMPARE(player.currentMedia(), QMediaContent());
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QVERIFY(m_fixture->player.hasVideo());
+ QVERIFY(!m_fixture->player.hasAudio());
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
+}
- QVERIFY(!stateSpy.isEmpty());
- QVERIFY(!statusSpy.isEmpty());
- QVERIFY(!mediaSpy.isEmpty());
- QVERIFY(!currentMediaSpy.isEmpty());
- QVERIFY(!positionSpy.isEmpty());
+void tst_QMediaPlayerBackend::setSource_remainsInStoppedState_whenPlayerWasStopped()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.stop();
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.setSource(*m_localWavFile2);
+
+ // Assert
+ QTRY_VERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia }, { QMediaPlayer::LoadedMedia } }));
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QVERIFY(m_fixture->playbackStateChanged.empty());
}
-void tst_QMediaPlayerBackend::loadMediaInLoadingState()
+void tst_QMediaPlayerBackend::setSource_entersStoppedState_whenPlayerWasPlaying()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile2);
+ m_fixture->clearSpies();
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+
+ // Act
+ m_fixture->player.setSource(*m_localWavFile);
+
+ // Assert
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124005: GStreamer has lots of state changes
+ QTRY_COMPARE(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia } }));
+ }
- QMediaPlayer player;
- player.setMedia(localWavFile);
- player.play();
- QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia);
- // Sets new media while old has not been finished.
- player.setMedia(localWavFile);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->playbackStateChanged,
+ SignalList({ { QMediaPlayer::PlayingState }, { QMediaPlayer::StoppedState } }));
+
+ QTRY_VERIFY(!m_fixture->positionChanged.empty()
+ && m_fixture->positionChanged.last()[0].value<qint64>() == 0);
+
+ QCOMPARE(m_fixture->player.position(), 0);
}
-void tst_QMediaPlayerBackend::playPauseStop()
+void tst_QMediaPlayerBackend::setSource_emitsError_whenSdpFileIsLoaded()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+#if !QT_CONFIG(process)
+ QSKIP("This test requires QProcess support");
+#else
+ // NOTE: This test checks that playing rtp streams using local .sdp file as a source is blocked
+ // by default. For when the user wants to override these defaults, see
+ // play_playsRtpStream_whenSdpFileIsLoaded
- QMediaPlayer player;
- player.setNotifyInterval(50);
+ if (!isFFMPEGPlatform())
+ QSKIP("This test is only for FFmpeg backend");
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ // Create stream
+ if (!canCreateRtpStream())
+ QSKIP("Rtp stream cannot be created");
- // Check play() without a media
- player.play();
+ // Make sure the default whitelist is used
+ qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST");
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(player.error(), QMediaPlayer::NoError);
- QCOMPARE(player.position(), 0);
- QCOMPARE(stateSpy.count(), 0);
- QCOMPARE(statusSpy.count(), 0);
- QCOMPARE(positionSpy.count(), 0);
- QCOMPARE(errorSpy.count(), 0);
+ auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4");
+ QVERIFY(temporaryFile);
- // Check pause() without a media
- player.pause();
+ // Pass a "file:" URL to VLC in order to generate an .sdp file
+ const QUrl sdpUrl = QUrl::fromLocalFile(QFileInfo("test.sdp").absoluteFilePath());
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(player.error(), QMediaPlayer::NoError);
- QCOMPARE(player.position(), 0);
- QCOMPARE(stateSpy.count(), 0);
- QCOMPARE(statusSpy.count(), 0);
- QCOMPARE(positionSpy.count(), 0);
- QCOMPARE(errorSpy.count(), 0);
+ auto process = createRtpStreamProcess(temporaryFile->fileName(), sdpUrl.toString());
+ QVERIFY2(process, "Cannot start rtp process");
- // The rest is with a valid media
+ auto processCloser = qScopeGuard([&process, &sdpUrl]() {
+ // End stream
+ process->close();
- player.setMedia(localWavFile);
+ // Remove .sdp file created by VLC
+ QFile(sdpUrl.toLocalFile()).remove();
+ });
- QCOMPARE(player.position(), qint64(0));
+ m_fixture->player.setSource(sdpUrl);
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+#endif // QT_CONFIG(process)
+}
- player.play();
+void tst_QMediaPlayerBackend::setSource_updatesTrackProperties_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<int>("numberOfVideoTracks");
+ QTest::addColumn<int>("numberOfAudioTracks");
+ QTest::addColumn<int>("numberOfSubtitleTracks");
+
+ QTest::addRow("video file with audio") << m_localVideoFile3ColorsWithSound << 1 << 1 << 0;
+ QTest::addRow("video file without audio") << m_colorMatrixVideo << 1 << 0 << 0;
+ QTest::addRow("uncompressed audio file") << m_localWavFile << 0 << 1 << 0;
+ QTest::addRow("compressed audio file") << m_localCompressedSoundFile << 0 << 1 << 0;
+}
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
+void tst_QMediaPlayerBackend::setSource_updatesTrackProperties()
+{
+ QFETCH(MaybeUrl, url);
+ QFETCH(int, numberOfVideoTracks);
+ QFETCH(int, numberOfAudioTracks);
+ QFETCH(int, numberOfSubtitleTracks);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ QMediaPlayer &player = m_fixture->player;
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PlayingState);
- QTRY_VERIFY(statusSpy.count() > 0 &&
- statusSpy.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::BufferedMedia);
+ CHECK_SELECTED_URL(url);
- QTRY_VERIFY(player.position() > 100);
- QVERIFY(player.duration() > 0);
- QVERIFY(positionSpy.count() > 0);
- QVERIFY(positionSpy.last()[0].value<qint64>() > 0);
+ player.setSource(*url);
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ QTRY_COMPARE(player.videoTracks().size(), numberOfVideoTracks);
+ QTRY_COMPARE(player.audioTracks().size(), numberOfAudioTracks);
+ QTRY_COMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks);
+}
- qint64 positionBeforePause = player.position();
- player.pause();
+void tst_QMediaPlayerBackend::setSource_emitsTracksChanged_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<int>("numberOfVideoTracks");
+ QTest::addColumn<int>("numberOfAudioTracks");
+ QTest::addColumn<int>("numberOfSubtitleTracks");
+
+ QTest::addRow("video file with audio") << m_localVideoFile3ColorsWithSound << 1 << 1 << 0;
+ QTest::addRow("video file without audio") << m_colorMatrixVideo << 1 << 0 << 0;
+ QTest::addRow("uncompressed audio file") << m_localWavFile << 0 << 1 << 0;
+ QTest::addRow("compressed audio file") << m_localCompressedSoundFile << 0 << 1 << 0;
+}
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QCOMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+void tst_QMediaPlayerBackend::setSource_emitsTracksChanged()
+{
+ QFETCH(MaybeUrl, url);
+ QFETCH(int, numberOfVideoTracks);
+ QFETCH(int, numberOfAudioTracks);
+ QFETCH(int, numberOfSubtitleTracks);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PausedState);
+ QMediaPlayer &player = m_fixture->player;
- QTest::qWait(2000);
+ CHECK_SELECTED_URL(url);
- QVERIFY(qAbs(player.position() - positionBeforePause) < 150);
- QCOMPARE(positionSpy.count(), 1);
+ QSignalSpy tracksChanged(&player, &QMediaPlayer::tracksChanged);
+ player.setSource(*url);
- stateSpy.clear();
- statusSpy.clear();
+ QVERIFY(tracksChanged.wait());
- player.stop();
+ QCOMPARE(player.videoTracks().size(), numberOfVideoTracks);
+ QCOMPARE(player.audioTracks().size(), numberOfAudioTracks);
+ QCOMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks);
+}
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+void tst_QMediaPlayerBackend::
+ setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio_data()
+{
+ QTest::addColumn<MaybeUrl>("url");
+ QTest::addColumn<QSize>("expectedVideoSize");
+
+ QTest::addRow("Horizontal expanding (par=3/2)")
+ << m_192x108_PAR_3_2_Video << QSize(192 * 3 / 2, 108);
+
+ if (isGStreamerPlatform())
+ // QTBUG-125249: gstreamer tries "to keep the input height (because of interlacing)"
+ QTest::addRow("Horizontal shrinking (par=2/3)")
+ << m_192x108_PAR_2_3_Video << QSize(192 * 2 / 3, 108);
+ else
+ QTest::addRow("Vertical expanding (par=2/3)")
+ << m_192x108_PAR_2_3_Video << QSize(192, 108 * 3 / 2);
+}
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
- //it's allowed to emit statusChanged() signal async
- QTRY_COMPARE(statusSpy.count(), 1);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia);
+void tst_QMediaPlayerBackend::
+ setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio()
+{
+ if (isGStreamerPlatform() && isCI())
+ QSKIP("QTBUG-124005: Fails with gstreamer on CI");
- //ensure the position is reset to 0 at stop and positionChanged(0) is emitted
- QCOMPARE(player.position(), qint64(0));
- QCOMPARE(positionSpy.last()[0].value<qint64>(), qint64(0));
- QVERIFY(player.duration() > 0);
+ QFETCH(MaybeUrl, url);
+ QFETCH(QSize, expectedVideoSize);
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ CHECK_SELECTED_URL(url);
- player.play();
+ m_fixture->player.setSource(*url);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->player.metaData().value(QMediaMetaData::Resolution), QSize(192, 108));
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PlayingState);
- QCOMPARE(statusSpy.count(), 1); // Should not go through Loading again when play -> stop -> play
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->surface.videoSize(), expectedVideoSize);
- player.stop();
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ m_fixture->player.play();
- player.setMedia(localWavFile2);
+ auto frame = m_fixture->surface.waitForFrame();
+ QVERIFY(frame.isValid());
+ QCOMPARE(frame.size(), expectedVideoSize);
+ QCOMPARE(frame.surfaceFormat().frameSize(), expectedVideoSize);
+ QCOMPARE(frame.surfaceFormat().viewport(), QRect(QPoint(), expectedVideoSize));
- QTRY_VERIFY(statusSpy.count() > 0);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
- player.play();
+ auto image = frame.toImage();
+ QCOMPARE(frame.size(), expectedVideoSize);
+
+ // clang-format off
+
+ // Video schema:
+ //
+ // 192
+ // *---------------------*
+ // | White | |
+ // | | |
+ // |----------/ | 108
+ // | Red |
+ // | |
+ // *---------------------*
+
+ // clang-format on
+
+ // check the proper scaling
+ const std::vector<QRgb> colors = { 0xFFFFFF, 0xFF0000, 0xFF00, 0xFF, 0x0 };
+
+ const auto pixelsOffset = 4;
+ const auto halfSize = expectedVideoSize / 2;
+
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(0, 0)), 0);
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(halfSize.width() - pixelsOffset, 0)), 0);
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(0, halfSize.height() - pixelsOffset)), 0);
+ QCOMPARE(findSimilarColorIndex(colors,
+ image.pixel(halfSize.width() - pixelsOffset,
+ halfSize.height() - pixelsOffset)),
+ 0);
+
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(halfSize.width() + pixelsOffset, 0)), 1);
+ QCOMPARE(findSimilarColorIndex(colors, image.pixel(0, halfSize.height() + pixelsOffset)), 1);
+ QCOMPARE(findSimilarColorIndex(colors,
+ image.pixel(halfSize.width() + pixelsOffset,
+ halfSize.height() + pixelsOffset)),
+ 1);
+}
- QTRY_VERIFY(player.position() > 100);
+void tst_QMediaPlayerBackend::pause_doesNotChangePlayerState_whenInvalidFileLoaded()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
- player.setMedia(localWavFile);
+ const MediaPlayerState expectedState{ m_fixture->player };
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), 0);
+ m_fixture->player.pause();
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ const MediaPlayerState actualState{ m_fixture->player };
- player.play();
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+}
+
+void tst_QMediaPlayerBackend::pause_doesNothing_whenMediaIsNotLoaded()
+{
+ m_fixture->player.pause();
- QTRY_VERIFY(player.position() > 100);
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
- player.setMedia(QMediaContent());
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::NoMedia);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::NoMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), 0);
- QCOMPARE(player.duration(), 0);
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+ QVERIFY(m_fixture->mediaStatusChanged.empty());
+ QVERIFY(m_fixture->positionChanged.empty());
+ QVERIFY(m_fixture->errorOccurred.empty());
}
+void tst_QMediaPlayerBackend::pause_entersPauseState_whenPlayerWasPlaying()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
-void tst_QMediaPlayerBackend::processEOS()
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_COMPARE_GT(m_fixture->player.position(), 100);
+ m_fixture->clearSpies();
+ const qint64 positionBeforePause = m_fixture->player.position();
+
+ // Act
+ m_fixture->player.pause();
+
+ // Assert
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PausedState);
+ QCOMPARE_EQ(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PausedState } }));
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+
+ QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200);
+
+ QTest::qWait(500);
+
+ QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200);
+}
+
+void tst_QMediaPlayerBackend::play_resetsErrorState_whenCalledWithInvalidFile()
+{
+ m_fixture->player.setSource({ "Some not existing media" });
+ QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError);
+
+ MediaPlayerState expectedState{ m_fixture->player };
+
+ m_fixture->player.play();
+
+ expectedState.error = QMediaPlayer::NoError;
+ COMPARE_MEDIA_PLAYER_STATE_EQ(MediaPlayerState{ m_fixture->player }, expectedState);
+
+ QTest::qWait(150); // wait a bit and check position is not changed
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(MediaPlayerState{ m_fixture->player }, expectedState);
+ QCOMPARE(m_fixture->surface.m_totalFrames, 0);
+}
+
+void tst_QMediaPlayerBackend::play_resumesPlaying_whenValidMediaIsProvidedAfterInvalidMedia()
+{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->framesCount > 0);
+ m_fixture->player.setSource(QUrl("Some not existing media"));
+ QTRY_COMPARE(m_fixture->player.error(), QMediaPlayer::ResourceError);
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QTRY_VERIFY(m_fixture->framesCount > 0);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+ QCOMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QCOMPARE(m_fixture->player.error(), QMediaPlayer::NoError);
+}
+
+void tst_QMediaPlayerBackend::play_doesNothing_whenMediaIsNotLoaded()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ m_fixture->player.play();
+
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
+
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+ QVERIFY(m_fixture->playbackStateChanged.empty());
+ QVERIFY(m_fixture->mediaStatusChanged.empty());
+ QVERIFY(m_fixture->positionChanged.empty());
+ QVERIFY(m_fixture->errorOccurred.empty());
+}
+
+void tst_QMediaPlayerBackend::play_setsPlaybackStateAndMediaStatus_whenValidFileIsLoaded()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ m_fixture->player.setSource(*m_localVideoFile);
+ m_fixture->player.play();
+
+ QTRY_COMPARE_EQ(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+
+ QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } }));
+
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124005: GStreamer has lots of state changes
+ QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia } }));
+ }
+
+ QTRY_COMPARE_GT(m_fixture->bufferProgressChanged.size(), 0);
+ QTRY_COMPARE_NE(m_fixture->bufferProgressChanged.front().front(), 0.f);
+ QTRY_COMPARE(m_fixture->bufferProgressChanged.back().front(), 1.f);
+}
+
+void tst_QMediaPlayerBackend::play_startsPlaybackAndChangesPosition_whenValidFileIsLoaded()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ m_fixture->player.setSource(*m_localVideoFile);
+ m_fixture->player.play();
+
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ QTRY_VERIFY(!m_fixture->durationChanged.empty());
+ QTRY_VERIFY(!m_fixture->positionChanged.empty());
+ QTRY_VERIFY(m_fixture->positionChanged.last()[0].value<qint64>() > 100);
+}
+
+void tst_QMediaPlayerBackend::play_doesNotEnterMediaLoadingState_whenResumingPlayingAfterStop()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ // Arrange: go through a play->pause->stop sequence
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.pause();
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ m_fixture->player.stop();
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+ QTRY_VERIFY(m_fixture->playbackStateChanged.contains({ QMediaPlayer::PlayingState }));
+
+ // Note: Should not go through Loading again when play -> stop -> play
+ if (!isGStreamerPlatform()) {
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ }));
+ } else {
+ QCOMPARE_EQ(m_fixture->mediaStatusChanged,
+ // gstreamer may see EndOfMedia
+ SignalList({
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::EndOfMedia },
+ }));
+ }
+}
+
+void tst_QMediaPlayerBackend::playAndSetSource_emitsExpectedSignalsAndStopsPlayback_whenSetSourceWasCalledWithEmptyUrl()
+{
+ CHECK_SELECTED_URL(m_localWavFile2);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile2);
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.setSource(QUrl());
+
+ // Assert
+ const MediaPlayerState expectedState = MediaPlayerState::defaultState();
+ const MediaPlayerState actualState{ m_fixture->player };
+ COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState);
+
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124005: GStreamer may see QMediaPlayer::EndOfMedia
+ QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::NoMedia } }));
+ }
+
+ QTRY_COMPARE_EQ(m_fixture->playbackStateChanged,
+ SignalList({ { QMediaPlayer::PlayingState }, { QMediaPlayer::StoppedState } }));
+
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QCOMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), 0);
+}
+
+void tst_QMediaPlayerBackend::
+ play_createsFramesWithExpectedContentAndIncreasingFrameTime_whenPlayingRtspMediaStream()
+{
+#if !QT_CONFIG(process)
+ QSKIP("This test requires QProcess support");
+#else
+ if (!canCreateRtpStream())
+ QSKIP("Rtsp stream cannot be created");
+
+ QSKIP_GSTREAMER("GStreamer tests fail");
+
+ auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4");
+ QVERIFY(temporaryFile);
+
+ const QString streamUrl = "rtsp://localhost:8083/stream";
+
+ auto process = createRtpStreamProcess(temporaryFile->fileName(), streamUrl);
+ QVERIFY2(process, "Cannot start rtsp process");
+
+ auto processCloser = qScopeGuard([&process]() { process->close(); });
+
+ TestVideoSink surface(false);
QMediaPlayer player;
- player.setNotifyInterval(50);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ QSignalSpy errorSpy(&player, &QMediaPlayer::errorOccurred);
+
+ player.setVideoSink(&surface);
+ // Ignore audio output to check timings accuratelly
+ // player.setAudioOutput(&output);
- player.setMedia(localWavFile);
+ player.setSource(streamUrl);
player.play();
- player.setPosition(900);
- //wait up to 5 seconds for EOS
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
+ const auto colors = { qRgb(0, 0, 0xFF), qRgb(0xFF, 0, 0), qRgb(0, 0xFE, 0) };
+ const auto colorInterval = 5000;
- //at EOS the position stays at the end of file
- QCOMPARE(player.position(), player.duration());
- QVERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), player.duration());
+ for (auto pos : { colorInterval / 2, colorInterval + 100 }) {
+ qDebug() << "Waiting for position:" << pos;
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ QTRY_COMPARE_GT(player.position(), pos);
- player.play();
+ auto frame1 = surface.waitForFrame();
+ QVERIFY(frame1.isValid());
+ QCOMPARE(frame1.size(), QSize(213, 120));
- //position is reset to start
- QTRY_VERIFY(player.position() < 100);
- QTRY_VERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.first()[0].value<qint64>(), 0);
+ QCOMPARE_GT(frame1.startTime(), pos * 1000);
+
+ auto frameTime = frame1.startTime();
+ const auto coloIndex = frameTime / (colorInterval * 1000);
+ QCOMPARE_LT(coloIndex, 2);
+
+ const auto image1 = frame1.toImage();
+ QVERIFY(!image1.isNull());
+ QCOMPARE(findSimilarColorIndex(colors, image1.pixel(1, 1)), coloIndex);
+ QCOMPARE(findSimilarColorIndex(colors, image1.pixel(100, 100)), coloIndex);
+
+ auto frame2 = surface.waitForFrame();
+ QVERIFY(frame2.isValid());
+ QCOMPARE_GT(frame2.startTime(), frame1.startTime());
+ }
+
+ player.stop();
+
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(errorSpy.size(), 0);
+#endif //QT_CONFIG(process)
+}
+
+void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames()
+{
+ if (isCI() && isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer");
+
+ CHECK_SELECTED_URL(m_oneRedFrameVideo);
+
+ m_fixture->surface.setStoreFrames(true);
+
+ m_fixture->player.setSource(*m_oneRedFrameVideo);
+ m_fixture->player.play();
+
+ QTRY_COMPARE_GT(m_fixture->surface.m_totalFrames, 0);
+ QVERIFY(m_fixture->surface.m_frameList.front().isValid());
+
+ QElapsedTimer timer;
+ timer.start();
+
+ QTRY_COMPARE_GT(m_fixture->surface.m_totalFrames, 1);
+ const auto elapsed = timer.elapsed();
+
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124005: GStreamer timing seems to be off
+
+ // 1000 is expected
+ QCOMPARE_GT(elapsed, 900);
+ QCOMPARE_LT(elapsed, 1400);
+ }
+
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->surface.m_totalFrames, 2);
+ QVERIFY(!m_fixture->surface.m_frameList.back().isValid());
+}
+
+void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected()
+{
+ QFETCH(const bool, audioConnected);
+ QFETCH(const bool, videoConnected);
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ if (!videoConnected && !audioConnected)
+ QSKIP_FFMPEG("FFMPEG backend playback fails when no output is connected");
+
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ if (!audioConnected)
+ m_fixture->player.setAudioOutput(nullptr);
+
+ if (!videoConnected)
+ m_fixture->player.setVideoOutput(nullptr);
+
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.play();
+
+ // Assert
+ QTRY_VERIFY(!m_fixture->mediaStatusChanged.empty()
+ && m_fixture->mediaStatusChanged.back()
+ == QList<QVariant>{ QMediaPlayer::EndOfMedia });
+
+ QTRY_COMPARE_EQ(m_fixture->playbackStateChanged,
+ SignalList({
+ { QMediaPlayer::PlayingState },
+ { QMediaPlayer::StoppedState },
+ }));
+}
+
+void tst_QMediaPlayerBackend::play_startsPlayback_withAndWithoutOutputsConnected_data()
+{
+ QTest::addColumn<bool>("videoConnected");
+ QTest::addColumn<bool>("audioConnected");
+
+ QTest::addRow("all connected") << true << true;
+ QTest::addRow("video connected") << true << false;
+ QTest::addRow("audio connected") << false << true;
+ QTest::addRow("no output connected") << false << false;
+}
+
+void tst_QMediaPlayerBackend::play_playsRtpStream_whenSdpFileIsLoaded()
+{
+#if !QT_CONFIG(process)
+ QSKIP("This test requires QProcess support");
+#else
+ if (!isFFMPEGPlatform())
+ QSKIP("This test is only for FFmpeg backend");
+
+ // Create stream
+ if (!canCreateRtpStream())
+ QSKIP("Rtp stream cannot be created");
+
+ auto temporaryFile = copyResourceToTemporaryFile(":/testdata/colors.mp4", "colors.XXXXXX.mp4");
+ QVERIFY(temporaryFile);
+
+ // Pass a "file:" URL to VLC in order to generate an .sdp file
+ const QUrl sdpUrl = QUrl::fromLocalFile(QFileInfo("test.sdp").absoluteFilePath());
+
+ auto process = createRtpStreamProcess(temporaryFile->fileName(), sdpUrl.toString());
+ QVERIFY2(process, "Cannot start rtp process");
+
+ // Set reasonable protocol whitelist that includes rtp and udp
+ qputenv("QT_FFMPEG_PROTOCOL_WHITELIST", "file,crypto,data,rtp,udp");
+
+ auto processCloser = qScopeGuard([&process, &sdpUrl]() {
+ // End stream
+ process->close();
+
+ // Remove .sdp file created by VLC
+ QFile(sdpUrl.toLocalFile()).remove();
+
+ // Unset environment variable
+ qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST");
+ });
+
+ m_fixture->player.setSource(sdpUrl);
+
+ // Play
+ m_fixture->player.play();
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+#endif // QT_CONFIG(process)
+}
+
+void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ // Arrange
+ m_fixture->player.setSource(*m_localWavFile);
+ m_fixture->player.play();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ m_fixture->player.pause();
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.stop();
+
+ // Assert
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::StoppedState } }));
+ // it's allowed to emit statusChanged() signal async
+ QTRY_COMPARE(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadedMedia } }));
+
+ if (!isGStreamerPlatform())
+ // QTBUG-124517: for some media types gstreamer does not emit buffer progress messages
+ QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } }));
+
+ QTRY_COMPARE(m_fixture->player.position(), qint64(0));
+ if (isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer ");
+
+ QTRY_VERIFY(!m_fixture->positionChanged.empty());
+ QCOMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), qint64(0));
+ QVERIFY(m_fixture->player.duration() > 0);
+}
+
+void tst_QMediaPlayerBackend::stop_setsPositionToZero_afterPlayingToEndOfMedia()
+{
+ // Arrange
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.play();
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ // Act
+ m_fixture->player.stop();
+
+ // Assert
+ QCOMPARE(m_fixture->player.position(), qint64(0));
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ m_fixture->player.play();
+
+ if (isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer");
+
+ QVERIFY(m_fixture->surface.waitForFrame().isValid());
+}
+
+
+void tst_QMediaPlayerBackend::playbackRate_returnsOne_byDefault()
+{
+ QCOMPARE_EQ(m_fixture->player.playbackRate(), static_cast<qreal>(1.0f));
+}
+
+void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal_data()
+{
+ QTest::addColumn<float>("initialPlaybackRate");
+ QTest::addColumn<float>("targetPlaybackRate");
+ QTest::addColumn<float>("expectedPlaybackRate");
+ QTest::addColumn<bool>("signalExpected");
+
+ QTest::addRow("Increase") << 1.0f << 2.0f << 2.0f << true;
+ QTest::addRow("Decrease") << 1.0f << 0.5f << 0.5f << true;
+ QTest::addRow("Keep") << 0.5f << 0.5f << 0.5f << false;
+
+ bool backendSupportsNegativePlayback =
+ isWindowsPlatform() || isDarwinPlatform() || isGStreamerPlatform();
+
+ if (backendSupportsNegativePlayback) {
+ QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << -0.5f << true;
+ QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << -0.6f << true;
+ } else {
+ QTest::addRow("DecreaseBelowZero") << 0.5f << -0.5f << 0.0f << true;
+ QTest::addRow("KeepDecreasingBelowZero") << -0.5f << -0.6f << 0.0f << false;
+ }
+}
+
+void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackRateAndEmitsSignal()
+{
+ QFETCH(const float, initialPlaybackRate);
+ QFETCH(const float, targetPlaybackRate);
+ QFETCH(const float, expectedPlaybackRate);
+ QFETCH(const bool, signalExpected);
+
+ // Arrange
+ m_fixture->player.setPlaybackRate(initialPlaybackRate);
+ m_fixture->clearSpies();
+
+ // Act
+ m_fixture->player.setPlaybackRate(targetPlaybackRate);
+
+ // Assert
+ if (signalExpected)
+ QCOMPARE_EQ(m_fixture->playbackRateChanged, SignalList({ { expectedPlaybackRate } }));
+ else
+ QVERIFY(m_fixture->playbackRateChanged.empty());
+
+ QCOMPARE_EQ(m_fixture->player.playbackRate(), expectedPlaybackRate);
+}
+
+void tst_QMediaPlayerBackend::setVolume_changesVolume_whenVolumeIsInRange()
+{
+ m_fixture->output.setVolume(0.0f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f } }));
+
+ m_fixture->output.setVolume(0.5f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.5f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f }, { 0.5f } }));
+
+ m_fixture->output.setVolume(1.0f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 1.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f }, { 0.5f }, { 1.0f } }));
+}
+
+void tst_QMediaPlayerBackend::setVolume_clampsToRange_whenVolumeIsOutsideRange()
+{
+ m_fixture->output.setVolume(-0.1f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f } }));
+
+ m_fixture->output.setVolume(1.1f);
+ QCOMPARE_EQ(m_fixture->output.volume(), 1.0f);
+ QCOMPARE(m_fixture->volumeChanged, SignalList({ { 0.0f }, { 1.0f } }));
+}
+
+void tst_QMediaPlayerBackend::setVolume_doesNotChangeMutedState()
+{
+ m_fixture->output.setMuted(true);
+ m_fixture->output.setVolume(0.5f);
+ QVERIFY(m_fixture->output.isMuted());
+
+ m_fixture->output.setMuted(false);
+ m_fixture->output.setVolume(0.0f);
+ QVERIFY(!m_fixture->output.isMuted());
+}
+
+void tst_QMediaPlayerBackend::setMuted_changesMutedState_whenMutedStateChanged()
+{
+ m_fixture->output.setMuted(true);
+ QVERIFY(m_fixture->output.isMuted());
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true } }));
+
+ // No new events emitted when muted state did not change
+ m_fixture->output.setMuted(true);
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true } }));
+
+ m_fixture->output.setMuted(false);
+ QVERIFY(!m_fixture->output.isMuted());
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true }, { false } }));
+
+ // No new events emitted when muted state did not change
+ m_fixture->output.setMuted(false);
+ QCOMPARE(m_fixture->mutedChanged, SignalList({ { true }, { false } }));
+}
+
+void tst_QMediaPlayerBackend::setMuted_doesNotChangeVolume()
+{
+ m_fixture->output.setVolume(0.5f);
+
+ m_fixture->output.setMuted(true);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.5f);
+
+ m_fixture->output.setMuted(false);
+ QCOMPARE_EQ(m_fixture->output.volume(), 0.5f);
+}
+
+void tst_QMediaPlayerBackend::processEOS()
+{
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failure with gstreamer");
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ if (!isGStreamerPlatform()) {
+ // QTBUG-124517: for some media types, including wav files, gstreamer does not emit buffer
+ // progress messages
+ CHECK_SELECTED_URL(m_localWavFile);
+ m_fixture->player.setSource(*m_localWavFile);
+ } else {
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ }
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PlayingState);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+ m_fixture->player.play();
+ m_fixture->player.setPosition(900);
- player.setPosition(900);
//wait up to 5 seconds for EOS
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 2);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
+
+ //at EOS the position stays at the end of file
+ QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), m_fixture->player.duration());
+
+ m_fixture->playbackStateChanged.clear();
+ m_fixture->mediaStatusChanged.clear();
+ m_fixture->positionChanged.clear();
+
+ m_fixture->player.play();
+
+ //position is reset to start
+ QTRY_COMPARE_LT(m_fixture->player.position(), 500);
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QCOMPARE(m_fixture->positionChanged.first()[0].value<qint64>(), 0);
+
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 1);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+
+ m_fixture->positionChanged.clear();
+ QTRY_VERIFY(m_fixture->player.position() > 100);
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0 && m_fixture->positionChanged.last()[0].value<qint64>() > 100);
+ m_fixture->player.setPosition(900);
+ //wait up to 5 seconds for EOS
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 2);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
+
+ QCOMPARE_GT(m_fixture->bufferProgressChanged.size(), 1);
+ QCOMPARE(m_fixture->bufferProgressChanged.back().front(), 0.f);
- //position stays at the end of file
- QCOMPARE(player.position(), player.duration());
- QVERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.last()[0].value<qint64>(), player.duration());
+ // position stays at the end of file
+ QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), m_fixture->player.duration());
//after setPosition EndOfMedia status should be reset to Loaded
- stateSpy.clear();
- statusSpy.clear();
- player.setPosition(500);
+ m_fixture->playbackStateChanged.clear();
+ m_fixture->mediaStatusChanged.clear();
+ m_fixture->player.setPosition(500);
//this transition can be async, so allow backend to perform it
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(stateSpy.count(), 0);
- QTRY_VERIFY(statusSpy.count() > 0 &&
- statusSpy.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::LoadedMedia);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 0);
+ QTRY_VERIFY(m_fixture->mediaStatusChanged.size() > 0 &&
+ m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::LoadedMedia);
- player.play();
- player.setPosition(900);
+ m_fixture->player.play();
+ m_fixture->player.setPosition(900);
//wait up to 5 seconds for EOS
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), player.duration());
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->player.position(), m_fixture->player.duration());
- stateSpy.clear();
- statusSpy.clear();
- positionSpy.clear();
+ m_fixture->playbackStateChanged.clear();
+ m_fixture->mediaStatusChanged.clear();
+ m_fixture->positionChanged.clear();
// pause() should reset position to beginning and status to Buffered
- player.pause();
+ m_fixture->player.pause();
- QTRY_COMPARE(player.position(), 0);
- QTRY_VERIFY(positionSpy.count() > 0);
- QCOMPARE(positionSpy.first()[0].value<qint64>(), 0);
+ QTRY_COMPARE(m_fixture->player.position(), 0);
+ QTRY_VERIFY(m_fixture->positionChanged.size() > 0);
+ QTRY_COMPARE(m_fixture->positionChanged.first()[0].value<qint64>(), 0);
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PausedState);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QCOMPARE(stateSpy.count(), 1);
- QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::State>(), QMediaPlayer::PausedState);
- QVERIFY(statusSpy.count() > 0);
- QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->playbackStateChanged.size(), 1);
+ QCOMPARE(m_fixture->playbackStateChanged.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PausedState);
+ QVERIFY(m_fixture->mediaStatusChanged.size() > 0);
+ QCOMPARE(m_fixture->mediaStatusChanged.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia);
}
// Helper class for tst_QMediaPlayerBackend::deleteLaterAtEOS()
@@ -600,8 +1739,8 @@ private slots:
void onMediaStatusChanged(QMediaPlayer::MediaStatus status)
{
if (status == QMediaPlayer::EndOfMedia) {
- player-> deleteLater();
- player = 0;
+ player->deleteLater();
+ player = nullptr;
}
}
@@ -613,73 +1752,28 @@ private:
// QTBUG-24927 - deleteLater() called to QMediaPlayer from its signal handler does not work as expected
void tst_QMediaPlayerBackend::deleteLaterAtEOS()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
QPointer<QMediaPlayer> player(new QMediaPlayer);
+ QAudioOutput output;
+ player->setAudioOutput(&output);
+ player->setPosition(800); // don't wait as long for EOS
DeleteLaterAtEos deleter(player);
- player->setMedia(localWavFile);
+ player->setSource(*m_localWavFile);
// Create an event loop for verifying deleteLater behavior instead of using
// QTRY_VERIFY or QTest::qWait. QTest::qWait makes extra effort to process
// DeferredDelete events during the wait, which interferes with this test.
QEventLoop loop;
- QTimer::singleShot(0, &deleter, SLOT(play()));
- QTimer::singleShot(5000, &loop, SLOT(quit()));
- connect(player.data(), SIGNAL(destroyed()), &loop, SLOT(quit()));
+ QTimer::singleShot(0, &deleter, &DeleteLaterAtEos::play);
+ QTimer::singleShot(5000, &loop, &QEventLoop::quit);
+ connect(player.data(), &QObject::destroyed, &loop, &QEventLoop::quit);
loop.exec();
// Verify that the player was destroyed within the event loop.
// This check will fail without the fix for QTBUG-24927.
QVERIFY(player.isNull());
}
-void tst_QMediaPlayerBackend::volumeAndMuted()
-{
- //volume and muted properties should be independent
- QMediaPlayer player;
- QVERIFY(player.volume() > 0);
- QVERIFY(!player.isMuted());
-
- player.setMedia(localWavFile);
- player.pause();
-
- QVERIFY(player.volume() > 0);
- QVERIFY(!player.isMuted());
-
- QSignalSpy volumeSpy(&player, SIGNAL(volumeChanged(int)));
- QSignalSpy mutedSpy(&player, SIGNAL(mutedChanged(bool)));
-
- //setting volume to 0 should not trigger muted
- player.setVolume(0);
- QTRY_COMPARE(player.volume(), 0);
- QVERIFY(!player.isMuted());
- QCOMPARE(volumeSpy.count(), 1);
- QCOMPARE(volumeSpy.last()[0].toInt(), player.volume());
- QCOMPARE(mutedSpy.count(), 0);
-
- player.setVolume(50);
- QTRY_COMPARE(player.volume(), 50);
- QVERIFY(!player.isMuted());
- QCOMPARE(volumeSpy.count(), 2);
- QCOMPARE(volumeSpy.last()[0].toInt(), player.volume());
- QCOMPARE(mutedSpy.count(), 0);
-
- player.setMuted(true);
- QTRY_VERIFY(player.isMuted());
- QVERIFY(player.volume() > 0);
- QCOMPARE(volumeSpy.count(), 2);
- QCOMPARE(mutedSpy.count(), 1);
- QCOMPARE(mutedSpy.last()[0].toBool(), player.isMuted());
-
- player.setMuted(false);
- QTRY_VERIFY(!player.isMuted());
- QVERIFY(player.volume() > 0);
- QCOMPARE(volumeSpy.count(), 2);
- QCOMPARE(mutedSpy.count(), 2);
- QCOMPARE(mutedSpy.last()[0].toBool(), player.isMuted());
-
-}
-
void tst_QMediaPlayerBackend::volumeAcrossFiles_data()
{
QTest::addColumn<int>("volume");
@@ -695,166 +1789,173 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles_data()
void tst_QMediaPlayerBackend::volumeAcrossFiles()
{
-#ifdef Q_OS_LINUX
- if (m_inCISystem)
- QSKIP("QTBUG-26577 Fails with gstreamer backend on ubuntu 10.4");
-#endif
+ CHECK_SELECTED_URL(m_localWavFile);
QFETCH(int, volume);
QFETCH(bool, muted);
+ float vol = volume/100.;
+ QAudioOutput output;
QMediaPlayer player;
+ player.setAudioOutput(&output);
+
//volume and muted should not be preserved between player instances
- QVERIFY(player.volume() > 0);
- QVERIFY(!player.isMuted());
+ QVERIFY(output.volume() > 0);
+ QVERIFY(!output.isMuted());
- player.setVolume(volume);
- player.setMuted(muted);
+ output.setVolume(vol);
+ output.setMuted(muted);
- QTRY_COMPARE(player.volume(), volume);
- QTRY_COMPARE(player.isMuted(), muted);
+ QTRY_COMPARE(output.volume(), vol);
+ QTRY_COMPARE(output.isMuted(), muted);
- player.setMedia(localWavFile);
- QCOMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ player.setSource(*m_localWavFile);
+ QCOMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
player.pause();
//to ensure the backend doesn't change volume/muted
//async during file loading.
- QTRY_COMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ QTRY_COMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
- player.setMedia(QMediaContent());
- QTRY_COMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ player.setSource(QUrl());
+ QTRY_COMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
- player.setMedia(localWavFile);
+ player.setSource(*m_localWavFile);
player.pause();
- QTRY_COMPARE(player.volume(), volume);
- QCOMPARE(player.isMuted(), muted);
+ QTRY_COMPARE(output.volume(), vol);
+ QCOMPARE(output.isMuted(), muted);
}
void tst_QMediaPlayerBackend::initialVolume()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
{
+ QAudioOutput output;
QMediaPlayer player;
- player.setVolume(1);
- player.setMedia(localWavFile);
- QCOMPARE(player.volume(), 1);
+ player.setAudioOutput(&output);
+ output.setVolume(1);
+ player.setSource(*m_localWavFile);
+ QCOMPARE(output.volume(), 1);
player.play();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.volume(), 1);
+ QCOMPARE(output.volume(), 1);
}
{
+ QAudioOutput output;
QMediaPlayer player;
- player.setMedia(localWavFile);
- QCOMPARE(player.volume(), 100);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localWavFile);
+ QCOMPARE(output.volume(), 1);
player.play();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.volume(), 100);
+ QCOMPARE(output.volume(), 1);
}
}
void tst_QMediaPlayerBackend::seekPauseSeek()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ CHECK_SELECTED_URL(m_localVideoFile);
+ TestVideoSink surface(true);
+ QAudioOutput output;
QMediaPlayer player;
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ player.setAudioOutput(&output);
+
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
- TestVideoSurface *surface = new TestVideoSurface;
- player.setVideoOutput(surface);
+ player.setVideoOutput(&surface);
- player.setMedia(localVideoFile);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QVERIFY(surface->m_frameList.isEmpty()); // frame must not appear until we call pause() or play()
+ player.setSource(*m_localVideoFile);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QVERIFY(surface.m_frameList.isEmpty()); // frame must not appear until we call pause() or play()
positionSpy.clear();
qint64 position = 7000;
player.setPosition(position);
- QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - position) < (qint64)500);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QTRY_VERIFY(!positionSpy.isEmpty());
+ QTRY_COMPARE(player.position(), position);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
QTest::qWait(250); // wait a bit to ensure the frame is not rendered
- QVERIFY(surface->m_frameList.isEmpty()); // still no frame, we must call pause() or play() to see a frame
+ QVERIFY(surface.m_frameList
+ .isEmpty()); // still no frame, we must call pause() or play() to see a frame
player.pause();
- QTRY_COMPARE(player.state(), QMediaPlayer::PausedState); // it might take some time for the operation to be completed
- QTRY_VERIFY_WITH_TIMEOUT(!surface->m_frameList.isEmpty(), 10000); // we must see a frame at position 7000 here
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PausedState); // it might take some time for the operation to be completed
+ QTRY_VERIFY(!surface.m_frameList.isEmpty()); // we must see a frame at position 7000 here
// Make sure that the frame has a timestamp before testing - not all backends provides this
- if (!surface->m_frameList.back().isValid() || surface->m_frameList.back().startTime() < 0)
+ if (!surface.m_frameList.back().isValid() || surface.m_frameList.back().startTime() < 0)
QSKIP("No timestamp");
{
- QVideoFrame frame = surface->m_frameList.back();
-#if !QT_CONFIG(directshow)
+ QVideoFrame frame = surface.m_frameList.back();
const qint64 elapsed = (frame.startTime() / 1000) - position; // frame.startTime() is microsecond, position is milliseconds.
QVERIFY2(qAbs(elapsed) < (qint64)500, QByteArray::number(elapsed).constData());
-#endif
- QCOMPARE(frame.width(), 160);
+ QCOMPARE(frame.width(), 213);
QCOMPARE(frame.height(), 120);
// create QImage for QVideoFrame to verify RGB pixel colors
- QVERIFY(frame.map(QAbstractVideoBuffer::ReadOnly));
- QImage image(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
+ QImage image = frame.toImage();
QVERIFY(!image.isNull());
QVERIFY(qRed(image.pixel(0, 0)) >= 230); // conversion from YUV => RGB, that's why it's not 255
QVERIFY(qGreen(image.pixel(0, 0)) < 20);
QVERIFY(qBlue(image.pixel(0, 0)) < 20);
- frame.unmap();
}
- surface->m_frameList.clear();
+ surface.m_frameList.clear();
positionSpy.clear();
position = 12000;
player.setPosition(position);
QTRY_VERIFY(!positionSpy.isEmpty() && qAbs(player.position() - position) < (qint64)500);
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QVERIFY(!surface->m_frameList.isEmpty());
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
+ QTRY_VERIFY(!surface.m_frameList.isEmpty());
{
- QVideoFrame frame = surface->m_frameList.back();
-#if !QT_CONFIG(directshow)
+ QVideoFrame frame = surface.m_frameList.back();
const qint64 elapsed = (frame.startTime() / 1000) - position;
QVERIFY2(qAbs(elapsed) < (qint64)500, QByteArray::number(elapsed).constData());
-#endif
- QCOMPARE(frame.width(), 160);
+ QCOMPARE(frame.width(), 213);
QCOMPARE(frame.height(), 120);
- QVERIFY(frame.map(QAbstractVideoBuffer::ReadOnly));
- QImage image(frame.bits(), frame.width(), frame.height(), QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()));
+ QImage image = frame.toImage();
QVERIFY(!image.isNull());
QVERIFY(qRed(image.pixel(0, 0)) < 20);
QVERIFY(qGreen(image.pixel(0, 0)) >= 230);
QVERIFY(qBlue(image.pixel(0, 0)) < 20);
- frame.unmap();
}
}
void tst_QMediaPlayerBackend::seekInStoppedState()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ CHECK_SELECTED_URL(m_localVideoFile);
+ TestVideoSink surface(false);
+ QAudioOutput output;
QMediaPlayer player;
- player.setNotifyInterval(500);
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64)));
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
+
+ QSignalSpy stateSpy(&player, &QMediaPlayer::playbackStateChanged);
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
- player.setMedia(localVideoFile);
+ player.setSource(*m_localVideoFile);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
QCOMPARE(player.position(), 0);
QVERIFY(player.isSeekable());
@@ -864,12 +1965,12 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
qint64 position = 5000;
player.setPosition(position);
- QTRY_VERIFY(qAbs(player.position() - position) < qint64(500));
- QCOMPARE(positionSpy.count(), 1);
- QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(500));
+ QTRY_VERIFY(qAbs(player.position() - position) < qint64(200));
+ QTRY_VERIFY(positionSpy.size() > 0);
+ QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200));
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(stateSpy.size(), 0);
QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
@@ -877,35 +1978,34 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
player.play();
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QVERIFY(qAbs(player.position() - position) < qint64(500));
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(player.position() > position);
- QTest::qWait(2000);
+ QTest::qWait(100);
// Check that it never played from the beginning
- QVERIFY(player.position() > (position - 500));
- for (int i = 0; i < positionSpy.count(); ++i)
- QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 500));
+ QVERIFY(player.position() > position);
+ for (int i = 0; i < positionSpy.size(); ++i)
+ QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200));
// ------
// Same tests but after play() --> stop()
player.stop();
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- QCOMPARE(player.position(), 0);
+ QTRY_COMPARE(player.position(), 0);
stateSpy.clear();
positionSpy.clear();
player.setPosition(position);
- QTRY_VERIFY(qAbs(player.position() - position) < qint64(500));
- QCOMPARE(positionSpy.count(), 1);
- QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(500));
+ QTRY_VERIFY(qAbs(player.position() - position) < qint64(200));
+ QTRY_VERIFY(positionSpy.size() > 0);
+ QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200));
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(stateSpy.size(), 0);
QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
@@ -913,560 +2013,552 @@ void tst_QMediaPlayerBackend::seekInStoppedState()
player.play();
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QVERIFY(qAbs(player.position() - position) < qint64(500));
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QVERIFY(qAbs(player.position() - position) < qint64(200));
- QTest::qWait(2000);
+ QTest::qWait(500);
// Check that it never played from the beginning
- QVERIFY(player.position() > (position - 500));
- for (int i = 0; i < positionSpy.count(); ++i)
- QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 500));
+ QVERIFY(player.position() > (position - 200));
+ for (int i = 0; i < positionSpy.size(); ++i)
+ QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200));
// ------
// Same tests but after reaching the end of the media
- player.setPosition(player.duration() - 500);
+ player.setPosition(player.duration() - 100);
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(player.position(), player.duration());
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QVERIFY(qAbs(player.position() - player.duration()) < 10);
stateSpy.clear();
positionSpy.clear();
player.setPosition(position);
- QTRY_VERIFY(qAbs(player.position() - position) < qint64(500));
- QCOMPARE(positionSpy.count(), 1);
- QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(500));
-
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
- QCOMPARE(stateSpy.count(), 0);
+ QTRY_VERIFY(qAbs(player.position() - position) < qint64(200));
+ QTRY_VERIFY(positionSpy.size() > 0);
+ QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200));
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(stateSpy.size(), 0);
QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
- positionSpy.clear();
-
player.play();
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || player.mediaStatus() == QMediaPlayer::EndOfMedia);
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia);
- QVERIFY(qAbs(player.position() - position) < qint64(500));
+ positionSpy.clear();
+ QTRY_VERIFY(player.position() > (position - 200));
- QTest::qWait(2000);
+ QTest::qWait(500);
// Check that it never played from the beginning
- QVERIFY(player.position() > (position - 500));
- for (int i = 0; i < positionSpy.count(); ++i)
- QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 500));
+ QVERIFY(player.position() > (position - 200));
+ for (int i = 0; i < positionSpy.size(); ++i)
+ QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200));
}
void tst_QMediaPlayerBackend::subsequentPlayback()
{
-#ifdef Q_OS_LINUX
- if (m_inCISystem)
- QSKIP("QTBUG-26769 Fails with gstreamer backend on ubuntu 10.4, setPosition(0)");
-#endif
+ QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures with gstreamer");
- if (localCompressedSoundFile.isNull())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localCompressedSoundFile);
+ QAudioOutput output;
QMediaPlayer player;
- player.setMedia(localCompressedSoundFile);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localCompressedSoundFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_VERIFY(player.isSeekable());
+ player.setPosition(5000);
player.play();
QCOMPARE(player.error(), QMediaPlayer::NoError);
- QTRY_COMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 15000);
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_WITH_TIMEOUT(player.mediaStatus(), QMediaPlayer::EndOfMedia, 10s);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
// Could differ by up to 1 compressed frame length
QVERIFY(qAbs(player.position() - player.duration()) < 100);
QVERIFY(player.position() > 0);
player.play();
- QTRY_COMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_VERIFY_WITH_TIMEOUT(player.position() > 2000 && player.position() < 5000, 10000);
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_GT(player.position(), 1000);
player.pause();
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
// make sure position does not "jump" closer to the end of the file
- QVERIFY(player.position() > 2000 && player.position() < 5000);
+ QVERIFY(player.position() > 1000);
// try to seek back to zero
player.setPosition(0);
QTRY_COMPARE(player.position(), qint64(0));
player.play();
- QCOMPARE(player.state(), QMediaPlayer::PlayingState);
- QTRY_VERIFY_WITH_TIMEOUT(player.position() > 2000 && player.position() < 5000, 10000);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_COMPARE_GT(player.position(), 1000);
player.pause();
- QCOMPARE(player.state(), QMediaPlayer::PausedState);
- QVERIFY(player.position() > 2000 && player.position() < 5000);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
+ QCOMPARE_GT(player.position(), 1000);
}
-void tst_QMediaPlayerBackend::probes()
+void tst_QMediaPlayerBackend::multipleMediaPlayback()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ CHECK_SELECTED_URL(m_localVideoFile);
+ CHECK_SELECTED_URL(m_localVideoFile2);
+
+ QAudioOutput output;
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localVideoFile);
+
+ QCOMPARE(player.source(), *m_localVideoFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ player.setPosition(0);
+ player.play();
- QMediaPlayer *player = new QMediaPlayer;
+ QCOMPARE(player.error(), QMediaPlayer::NoError);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QVERIFY(player.isSeekable());
+ QTRY_VERIFY(player.position() > 0);
+ QCOMPARE(player.source(), *m_localVideoFile);
- TestVideoSurface *surface = new TestVideoSurface;
- player->setVideoOutput(surface);
+ player.stop();
- QVideoProbe *videoProbe = new QVideoProbe;
- QAudioProbe *audioProbe = new QAudioProbe;
+ player.setSource(*m_localVideoFile2);
- ProbeDataHandler probeHandler;
- connect(videoProbe, SIGNAL(videoFrameProbed(QVideoFrame)), &probeHandler, SLOT(processFrame(QVideoFrame)));
- connect(videoProbe, SIGNAL(flush()), &probeHandler, SLOT(flushVideo()));
- connect(audioProbe, SIGNAL(audioBufferProbed(QAudioBuffer)), &probeHandler, SLOT(processBuffer(QAudioBuffer)));
- connect(audioProbe, SIGNAL(flush()), &probeHandler, SLOT(flushAudio()));
+ QCOMPARE(player.source(), *m_localVideoFile2);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QTRY_VERIFY(player.isSeekable());
- if (!videoProbe->setSource(player))
- QSKIP("QVideoProbe is not supported");
- audioProbe->setSource(player);
+ player.setPosition(0);
+ player.play();
- player->setMedia(localVideoFile);
- QTRY_COMPARE(player->mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE(player.error(), QMediaPlayer::NoError);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ QTRY_VERIFY(player.position() > 0);
+ QCOMPARE(player.source(), *m_localVideoFile2);
- player->pause();
- QTRY_COMPARE(surface->m_frameList.size(), 1);
- QVERIFY(!probeHandler.m_frameList.isEmpty());
- QTRY_VERIFY(!probeHandler.m_bufferList.isEmpty());
+ player.stop();
- delete player;
- QTRY_VERIFY(probeHandler.isVideoFlushCalled);
- delete videoProbe;
- delete audioProbe;
+ QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState);
}
-void tst_QMediaPlayerBackend::playlist()
+void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest()
{
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ if (isCI()) {
+ if (isDarwinPlatform())
+ QSKIP("SKIP on macOS CI since multiple fake drawing on macOS CI platform causes UB. To "
+ "be investigated.");
+
+ if (isGStreamerPlatform())
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer");
+ }
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
QMediaPlayer player;
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy mediaStatusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
- QFileInfo fileInfo(QFINDTESTDATA("testdata/sample.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ player.setSource(*m_localVideoFile3ColorsWithSound);
player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
-
- if (player.mediaStatus() == QMediaPlayer::InvalidMedia || mediaSpy.count() == 1)
- QSKIP("QMediaPlayer does not support loading M3U playlists as QMediaPlaylist");
-
- QCOMPARE(mediaSpy.count(), 2);
- // sample.m3u -> sample.m3u resolved -> test.wav ->
- // nested1.m3u -> nested1.m3u resolved -> test.wav ->
- // nested2.m3u -> nested2.m3u resolved ->
- // test.wav -> _test.wav
- // currentMediaChanged signals not emmitted for
- // nested1.m3u\_test.wav and nested2.m3u\_test.wav
- // because current media stays the same
- QCOMPARE(currentMediaSpy.count(), 11);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 19); // 6 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
- QCOMPARE(mediaSpy.count(), 0);
- QCOMPARE(currentMediaSpy.count(), 8);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 19); // 6 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ surface.waitForFrame();
- // <<< Invalid - 1st pass >>>
- fileInfo.setFile(QFINDTESTDATA("testdata/invalid_media.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ QSignalSpy spy(&player, &QMediaPlayer::playbackStateChanged);
- player.play();
- QTRY_COMPARE(player.state(), QMediaPlayer::StoppedState);
- // playlist -> resolved playlist
- QCOMPARE(mediaSpy.count(), 2);
- // playlist -> resolved playlist -> invalid -> ""
- QCOMPARE(currentMediaSpy.count(), 4);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 3); // LoadingMedia -> InvalidMedia -> NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ constexpr qint64 expectedVideoDuration = 3000;
+ constexpr int waitingInterval = 200;
+ constexpr qint64 maxDuration = expectedVideoDuration + 2000;
+ constexpr qint64 minDuration = expectedVideoDuration - 100;
+ constexpr qint64 maxFrameDelay = 2000;
- // <<< Invalid - 2nd pass >>>
- player.play();
- QTRY_COMPARE(player.state(), QMediaPlayer::StoppedState);
- // media is not changed
- QCOMPARE(mediaSpy.count(), 0);
- // resolved playlist -> invalid -> ""
- QCOMPARE(currentMediaSpy.count(), 3);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 3); // LoadingMedia -> InvalidMedia -> NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ surface.m_elapsedTimer.start();
- // <<< Invalid2 - 1st pass >>>
- fileInfo.setFile(QFINDTESTDATA("/testdata/invalid_media2.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ qint64 duration = 0;
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- // playlist -> resolved playlist
- QCOMPARE(mediaSpy.count(), 2);
- // playlist -> resolved playlist -> test.wav -> invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 6);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 9); // 3 x LoadingMedia + 2 x (BufferedMedia -> EndOfMedia) + InvalidMedia + NoMedia (not in this order)
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ for (int i = 0; !spy.wait(waitingInterval); ++i) {
+ duration += waitingInterval * player.playbackRate();
- // <<< Invalid2 - 2nd pass >>>
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- // playlist -> resolved playlist
- QCOMPARE(mediaSpy.count(), 0);
- // playlist -> test.wav -> invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 5);
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 9); // 3 x LoadingMedia + 2 x (BufferedMedia -> EndOfMedia) + InvalidMedia + NoMedia (not in this order)
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ player.setPlaybackRate(0.5 * (i % 4 + 1));
- // <<< Recursive - 1st pass >>>
- fileInfo.setFile(QFINDTESTDATA("testdata/recursive_master.m3u"));
- player.setMedia(QUrl::fromLocalFile(fileInfo.absoluteFilePath()));
+ QCOMPARE_LE(duration, maxDuration);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- // master playlist -> resolved master playlist
- QCOMPARE(mediaSpy.count(), 2);
- // master playlist -> resolved master playlist ->
- // recursive playlist -> resolved recursive playlist ->
- // recursive playlist (this URL is already in the chain of playlists, so the playlist is not resolved) ->
- // invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 8);
- QCOMPARE(stateSpy.count(), 2);
- // there is one invalid media in the master playlist
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 6); // LoadingMedia -> InvalidMedia -> LoadingMedia -> BufferedMedia
- // -> EndOfMedia -> NoMedia
-
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ QVERIFY2(surface.m_elapsedTimer.elapsed() < maxFrameDelay,
+ "If the delay is more than 2s, we consider the video playing is hanging.");
- // <<< Recursive - 2nd pass >>>
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 20000);
- QCOMPARE(mediaSpy.count(), 0);
- // resolved master playlist ->
- // resolved recursive playlist ->
- // recursive playlist (this URL is already in the chain of playlists, so the playlist is not resolved) ->
- // invalid -> test.wav -> ""
- QCOMPARE(currentMediaSpy.count(), 6);
- QCOMPARE(stateSpy.count(), 2);
- // there is one invalid media in the master playlist
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 6); // LoadingMedia -> InvalidMedia -> LoadingMedia -> BufferedMedia
- // -> EndOfMedia -> NoMedia
+ /* Some debug code for windows. Use the code instead of the check above to debug the bug.
+ * https://bugreports.qt.io/browse/QTBUG-105940.
+ * TODO: fix hanging on windows and remove.
+ if ( surface.m_elapsedTimer.elapsed() > maxFrameDelay ) {
+ qDebug() << "pause/play";
+ player.pause();
+ player.play();
+ surface.m_elapsedTimer.restart();
+ spy.clear();
+ }*/
+ }
+
+ duration += waitingInterval * player.playbackRate();
+
+ QCOMPARE_GT(duration, minDuration);
+
+ QCOMPARE(spy.size(), 1);
+ QCOMPARE(spy.at(0).size(), 1);
+ QCOMPARE(spy.at(0).at(0).value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState);
+
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
}
-void tst_QMediaPlayerBackend::playlistObject()
+void tst_QMediaPlayerBackend::multipleSeekStressTest()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ QSKIP_GSTREAMER("QTBUG-124005: spurious test failures with gstreamer");
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
QMediaPlayer player;
- QSignalSpy mediaSpy(&player, SIGNAL(mediaChanged(QMediaContent)));
- QSignalSpy currentMediaSpy(&player, SIGNAL(currentMediaChanged(QMediaContent)));
- QSignalSpy stateSpy(&player, SIGNAL(stateChanged(QMediaPlayer::State)));
- QSignalSpy mediaStatusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)));
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ player.setAudioOutput(&output);
+ player.setVideoOutput(&surface);
- // --- empty playlist
- QMediaPlaylist emptyPlaylist;
- player.setPlaylist(&emptyPlaylist);
+ player.setSource(*m_localVideoFile3ColorsWithSound);
player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 1); // Empty media
- QCOMPARE(stateSpy.count(), 0);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 0);
+ auto waitAndCheckFrame = [&](qint64 pos, QString checkInfo) {
+ auto errorPrintingGuard = qScopeGuard([&]() {
+ qDebug() << "Error:" << checkInfo;
+ qDebug() << "Position:" << pos;
+ });
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ auto frame = surface.waitForFrame();
+ QVERIFY(frame.isValid());
- // --- Valid playlist
- QMediaPlaylist playlist;
- playlist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/test.wav")).absoluteFilePath()));
- playlist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/_test.wav")).absoluteFilePath()));
- player.setPlaylist(&playlist);
+ const auto trackTime = pos * 1000;
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ // in theory, previous frame might be received, in this case we wait for a new one that is
+ // expected to be relevant
+ if (frame.endTime() < trackTime || frame.startTime() > trackTime) {
+ frame = surface.waitForFrame();
+ QVERIFY(frame.isValid());
+ }
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 3); // test.wav -> _test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 7); // 2 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
+ QCOMPARE_GE(frame.startTime(), trackTime - 200'000);
+ QCOMPARE_LE(frame.endTime(), trackTime + 200'000);
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ auto frameImage = frame.toImage();
+ const auto actualColor = frameImage.pixel(1, 1);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ const auto actualColorIndex = findSimilarColorIndex(m_video3Colors, actualColor);
- QCOMPARE(mediaSpy.count(), 0);
- QCOMPARE(currentMediaSpy.count(), 4); // playlist -> test.wav -> _test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 7); // 2 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
+ const auto expectedColorIndex = pos / 1000;
- player.setPlaylist(nullptr);
+ QCOMPARE(actualColorIndex, expectedColorIndex);
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ errorPrintingGuard.dismiss();
+ };
- // --- Nested playlist
- QMediaPlaylist nestedPlaylist;
- nestedPlaylist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/_test.wav")).absoluteFilePath()));
- nestedPlaylist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/test.wav")).absoluteFilePath()));
- nestedPlaylist.addMedia(&playlist);
- player.setPlaylist(&nestedPlaylist);
+ auto seekAndCheck = [&](qint64 pos) {
+ QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged);
+ player.setPosition(pos);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ QTRY_VERIFY(positionSpy.size() >= 1);
+ int setPosition = positionSpy.first().first().toInt();
+ QCOMPARE_GT(setPosition, pos - 100);
+ QCOMPARE_LT(setPosition, pos + 100);
+ };
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 6); // _test.wav -> test.wav -> nested playlist
- // -> test.wav -> _test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 0);
- QCOMPARE(mediaStatusSpy.count(), 13); // 4 x (LoadingMedia -> BufferedMedia -> EndOfMedia) + NoMedia
+ constexpr qint64 posInterval = 10;
- player.setPlaylist(nullptr);
+ {
+ for (qint64 pos = posInterval; pos <= 2200; pos += posInterval)
+ seekAndCheck(pos);
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ waitAndCheckFrame(2200, "emulate fast moving of a seek slider forward");
- // --- playlist with invalid media
- QMediaPlaylist invalidPlaylist;
- invalidPlaylist.addMedia(QUrl("invalid"));
- invalidPlaylist.addMedia(QUrl::fromLocalFile(QFileInfo(QFINDTESTDATA("testdata/test.wav")).absoluteFilePath()));
+ QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ }
- player.setPlaylist(&invalidPlaylist);
+ {
+ for (qint64 pos = 2100; pos >= 800; pos -= posInterval)
+ seekAndCheck(pos);
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+ waitAndCheckFrame(800, "emulate fast moving of a seek slider backward");
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 3); // invalid -> test.wav -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 1);
- QCOMPARE(mediaStatusSpy.count(), 6); // Loading -> Invalid -> Loading -> Buffered -> EndOfMedia -> NoMedia
+ QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState);
+ }
- player.setPlaylist(nullptr);
+ {
+ player.pause();
- mediaSpy.clear();
- currentMediaSpy.clear();
- stateSpy.clear();
- mediaStatusSpy.clear();
- errorSpy.clear();
+ for (qint64 pos = 500; pos <= 1100; pos += posInterval)
+ seekAndCheck(pos);
- // --- playlist with only invalid media
- QMediaPlaylist invalidPlaylist2;
- invalidPlaylist2.addMedia(QUrl("invalid"));
- invalidPlaylist2.addMedia(QUrl("invalid2"));
+ waitAndCheckFrame(1100, "emulate fast moving of a seek slider forward on paused state");
- player.setPlaylist(&invalidPlaylist2);
+ QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ QCOMPARE(player.playbackState(), QMediaPlayer::PausedState);
+ }
+}
- player.play();
- QTRY_COMPARE_WITH_TIMEOUT(player.state(), QMediaPlayer::StoppedState, 10000);
+void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime_data()
+{
+ QTest::addColumn<bool>("withAudio");
+ QTest::addColumn<int>("positionDeviationMs");
+
+ QTest::newRow("Without audio") << false << 170;
- QCOMPARE(mediaSpy.count(), 1);
- QCOMPARE(currentMediaSpy.count(), 3); // invalid -> invalid2 -> NoMedia
- QCOMPARE(stateSpy.count(), 2);
- QCOMPARE(errorSpy.count(), 2);
- QCOMPARE(mediaStatusSpy.count(), 5); // Loading -> Invalid -> Loading -> Invalid -> NoMedia
+ // set greater positionDeviationMs for case with audio due to possible synchronization.
+ QTest::newRow("With audio") << true << 200;
}
-void tst_QMediaPlayerBackend::surfaceTest_data()
+void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime()
{
- QTest::addColumn< QList<QVideoFrame::PixelFormat> >("formatsList");
-
- QList<QVideoFrame::PixelFormat> formatsRGB;
- formatsRGB << QVideoFrame::Format_RGB32
- << QVideoFrame::Format_ARGB32
- << QVideoFrame::Format_RGB565
- << QVideoFrame::Format_BGRA32;
+ QSKIP_GSTREAMER("QTBUG-124005: timing issues");
- QList<QVideoFrame::PixelFormat> formatsYUV;
- formatsYUV << QVideoFrame::Format_YUV420P
- << QVideoFrame::Format_YUV422P
- << QVideoFrame::Format_YV12
- << QVideoFrame::Format_UYVY
- << QVideoFrame::Format_YUYV
- << QVideoFrame::Format_NV12
- << QVideoFrame::Format_NV21;
+ QFETCH(bool, withAudio);
+ QFETCH(int, positionDeviationMs);
- QTest::newRow("RGB formats")
- << formatsRGB;
+#ifdef Q_OS_ANDROID
+ QSKIP("frame.toImage will return null image because of QTBUG-108446");
+#endif
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
-#if !QT_CONFIG(directshow)
- QTest::newRow("YVU formats")
- << formatsYUV;
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("SKIP on macOS CI since multiple fake drawing on macOS CI platform causes UB. To be "
+ "investigated: QTBUG-111744");
#endif
+ m_fixture->player.setAudioOutput(
+ withAudio ? &m_fixture->output
+ : nullptr); // TODO: mock audio output and check sound by frequency
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ auto checkColorAndPosition = [&](qint64 expectedPosition, QString errorTag) {
+ constexpr qint64 intervalTime = 1000;
+
+ const int colorIndex = expectedPosition / intervalTime;
+ const auto expectedColor = m_video3Colors[colorIndex];
+ const auto actualPosition = m_fixture->player.position();
+
+ auto frame = m_fixture->surface.videoFrame();
+ auto image = frame.toImage();
+ QVERIFY(!image.isNull());
+
+ const auto actualColor = image.pixel(1, 1);
+
+ auto errorPrintingGuard = qScopeGuard([&]() {
+ qDebug() << "Error Tag:" << errorTag;
+ qDebug() << " Actual Color:" << QColor(actualColor)
+ << " Expected Color:" << QColor(expectedColor);
+ qDebug() << " Most probable actual color index:"
+ << findSimilarColorIndex(m_video3Colors, actualColor)
+ << "Expected color index:" << colorIndex;
+ qDebug() << " Actual position:" << actualPosition;
+ qDebug() << " Frame start time:" << frame.startTime();
+ });
+
+ // TODO: investigate why frames sometimes are not delivered in time on windows
+ constexpr qreal maxColorDifference = 0.18;
+ QVERIFY(m_fixture->player.isPlaying());
+ QCOMPARE_LE(colorDifference(actualColor, expectedColor), maxColorDifference);
+ QCOMPARE_GT(actualPosition, expectedPosition - positionDeviationMs);
+ QCOMPARE_LT(actualPosition, expectedPosition + positionDeviationMs);
+
+ const auto framePosition = frame.startTime() / 1000;
+
+ QCOMPARE_GT(framePosition, expectedPosition - positionDeviationMs);
+ QCOMPARE_LT(framePosition, expectedPosition + positionDeviationMs);
+ QCOMPARE_LT(qAbs(framePosition - actualPosition), positionDeviationMs);
+
+ errorPrintingGuard.dismiss();
+ };
+
+ m_fixture->player.play();
- QTest::newRow("RGB & YUV formats")
- << formatsRGB + formatsYUV;
+ m_fixture->surface.waitForFrame();
+
+ auto waitUntil = [&](qint64 targetPosition) {
+ const auto position = m_fixture->player.position();
+
+ const auto waitingIntervalMs =
+ static_cast<int>((targetPosition - position) / m_fixture->player.playbackRate());
+
+ if (targetPosition > position)
+ QTest::qWait(waitingIntervalMs);
+
+ qDebug() << "Test waiting:" << waitingIntervalMs << "ms, Position:" << position << "=>"
+ << m_fixture->player.position() << "Expected target position:" << targetPosition
+ << "playbackRate:" << m_fixture->player.playbackRate();
+ };
+
+ waitUntil(400);
+ checkColorAndPosition(400, "Check default playback rate");
+
+ m_fixture->player.setPlaybackRate(2.);
+
+ waitUntil(1400);
+ checkColorAndPosition(1400, "Check 2.0 playback rate");
+
+ m_fixture->player.setPlaybackRate(0.5);
+
+ waitUntil(1800);
+ checkColorAndPosition(1800, "Check 0.5 playback rate");
+
+ m_fixture->player.setPlaybackRate(0.321);
+
+ m_fixture->player.stop();
}
void tst_QMediaPlayerBackend::surfaceTest()
{
- // 25 fps video file
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery");
- QFETCH(QList<QVideoFrame::PixelFormat>, formatsList);
+ CHECK_SELECTED_URL(m_localVideoFile);
+ // 25 fps video file
- TestVideoSurface surface(false);
- surface.setSupportedFormats(formatsList);
+ QAudioOutput output;
+ TestVideoSink surface(false);
QMediaPlayer player;
+ player.setAudioOutput(&output);
player.setVideoOutput(&surface);
- player.setMedia(localVideoFile);
+ player.setSource(*m_localVideoFile);
player.play();
QTRY_VERIFY(player.position() >= 1000);
- if (surface.error() == QAbstractVideoSurface::UnsupportedFormatError)
- QSKIP("None of the pixel formats is supported by the backend");
- QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames)));
+ QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QStringLiteral("Expected >= 25, got %1").arg(surface.m_totalFrames)));
}
-void tst_QMediaPlayerBackend::multipleSurfaces()
+void tst_QMediaPlayerBackend::metadata()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage
+ QMediaMetaData::Key thumbnailKey =
+ isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage;
- QList<QVideoFrame::PixelFormat> formats1;
- formats1 << QVideoFrame::Format_RGB32
- << QVideoFrame::Format_ARGB32;
- QList<QVideoFrame::PixelFormat> formats2;
- formats2 << QVideoFrame::Format_YUV420P
- << QVideoFrame::Format_RGB32;
+ CHECK_SELECTED_URL(m_localFileWithMetadata);
- TestVideoSurface surface1(false);
- surface1.setSupportedFormats(formats1);
- TestVideoSurface surface2(false);
- surface2.setSupportedFormats(formats2);
+ m_fixture->player.setSource(*m_localFileWithMetadata);
- QMediaPlayer player;
- player.setVideoOutput(QList<QAbstractVideoSurface *>() << &surface1 << &surface2);
- player.setMedia(localVideoFile);
- player.play();
- QTRY_VERIFY(player.position() >= 1000);
- QVERIFY2(surface1.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface1.m_totalFrames)));
- QVERIFY2(surface2.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface2.m_totalFrames)));
- QCOMPARE(surface1.m_totalFrames, surface2.m_totalFrames);
+ QTRY_VERIFY(m_fixture->metadataChanged.size() > 0);
+
+ const QMediaMetaData metadata = m_fixture->player.metaData();
+ QCOMPARE(metadata.value(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune"));
+ QCOMPARE(metadata.value(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist"));
+ QCOMPARE(metadata.value(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum"));
+ QCOMPARE(metadata.value(QMediaMetaData::Duration), QVariant(7704));
+ QVERIFY(!metadata.value(thumbnailKey).value<QImage>().isNull());
+ m_fixture->clearSpies();
+
+ m_fixture->player.setSource(QUrl());
+
+ QCOMPARE(m_fixture->metadataChanged.size(), 1);
+ QVERIFY(m_fixture->player.metaData().isEmpty());
}
-void tst_QMediaPlayerBackend::metadata()
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail_data()
{
- if (localFileWithMetadata.isNull())
- QSKIP("No supported media file");
+ QTest::addColumn<MaybeUrl>("mediaUrl");
+ QTest::addColumn<bool>("hasThumbnail");
+ QTest::addColumn<QSize>("expectedSize");
+ QTest::addColumn<QColor>("expectedColor");
+
+ QTest::addRow("jpeg thumbnail") << m_videoFileWithJpegThumbnail << true << QSize{ 20, 28 } << QColor(35, 177, 77);
+ QTest::addRow("png thumbnail") << m_videoFileWithPngThumbnail << true << QSize{ 20, 28 } << QColor(35, 177, 77);
+ QTest::addRow("no thumbnail") << m_localVideoFile3ColorsWithSound << false << QSize{ 0, 0 } << QColor(0, 0, 0);
+}
- QMediaPlayer player;
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail()
+{
+ // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage
+ QMediaMetaData::Key key =
+ isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage;
- QSignalSpy metadataAvailableSpy(&player, SIGNAL(metaDataAvailableChanged(bool)));
- QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged()));
+ // Arrange
+ QFETCH(const MaybeUrl, mediaUrl);
+ QFETCH(const bool, hasThumbnail);
+ QFETCH(const QSize, expectedSize);
+ QFETCH(const QColor, expectedColor);
- player.setMedia(localFileWithMetadata);
+ CHECK_SELECTED_URL(mediaUrl);
- QTRY_VERIFY(player.isMetaDataAvailable());
- QCOMPARE(metadataAvailableSpy.count(), 1);
- QVERIFY(metadataAvailableSpy.last()[0].toBool());
- QVERIFY(metadataChangedSpy.count() > 0);
+ m_fixture->player.setSource(*mediaUrl);
+ QTRY_VERIFY(!m_fixture->metadataChanged.empty());
+
+ // Act
+ const QMediaMetaData metadata = m_fixture->player.metaData();
+ const QImage thumbnail = metadata.value(key).value<QImage>();
+
+ // Assert
+ QCOMPARE_EQ(!thumbnail.isNull(), hasThumbnail);
+ QCOMPARE_EQ(thumbnail.size(), expectedSize);
+
+ if (hasThumbnail) {
+ const QPoint center{ expectedSize.width() / 2, expectedSize.height() / 2 };
+ const auto centerColor = thumbnail.pixelColor(center);
+
+ constexpr int maxChannelDiff = 5;
+ QCOMPARE_LT(std::abs(centerColor.red() - expectedColor.red()), maxChannelDiff);
+ QCOMPARE_LT(std::abs(centerColor.green() - expectedColor.green()), maxChannelDiff);
+ QCOMPARE_LT(std::abs(centerColor.blue() - expectedColor.blue()), maxChannelDiff);
+ }
+}
+
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent_data()
+{
+ QTest::addColumn<MaybeUrl>("mediaUrl");
+ QTest::addColumn<bool>("hasHdrContent");
+
+ QTest::addRow("SDR Video") << m_localVideoFile << false;
+ QTest::addRow("HDR Video") << m_hdrVideo << true;
+}
+
+void tst_QMediaPlayerBackend::metadata_returnsMetadataWithHasHdrContent_whenMediaHasHdrContent()
+{
+ QFETCH(const MaybeUrl, mediaUrl);
+ QFETCH(const bool, hasHdrContent);
- QCOMPARE(player.metaData(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune"));
- QCOMPARE(player.metaData(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist"));
- QCOMPARE(player.metaData(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum"));
+ QSKIP_IF_NOT_FFMPEG();
- metadataAvailableSpy.clear();
- metadataChangedSpy.clear();
+ m_fixture->player.setSource(*mediaUrl);
+ QTRY_VERIFY(!m_fixture->metadataChanged.empty());
- player.setMedia(QMediaContent());
+ const QMediaMetaData metadata = m_fixture->player.videoTracks().front();
+ const bool hdrContent = metadata.value(QMediaMetaData::HasHdrContent).value<bool>();
- QVERIFY(!player.isMetaDataAvailable());
- QCOMPARE(metadataAvailableSpy.count(), 1);
- QVERIFY(!metadataAvailableSpy.last()[0].toBool());
- QCOMPARE(metadataChangedSpy.count(), 1);
- QVERIFY(player.availableMetaData().isEmpty());
+ QCOMPARE_EQ(hasHdrContent, hdrContent);
}
void tst_QMediaPlayerBackend::playerStateAtEOS()
{
- if (!isWavSupported())
- QSKIP("Sound format is not supported");
+ CHECK_SELECTED_URL(m_localWavFile);
+ QAudioOutput output;
QMediaPlayer player;
+ player.setAudioOutput(&output);
bool endOfMediaReceived = false;
- connect(&player, &QMediaPlayer::mediaStatusChanged, [&](QMediaPlayer::MediaStatus status) {
+ connect(&player, &QMediaPlayer::mediaStatusChanged,
+ this, [&](QMediaPlayer::MediaStatus status) {
if (status == QMediaPlayer::EndOfMedia) {
- QCOMPARE(player.state(), QMediaPlayer::StoppedState);
+ QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState);
endOfMediaReceived = true;
}
});
- player.setMedia(localWavFile);
+ player.setSource(*m_localWavFile);
player.play();
QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
@@ -1475,87 +2567,857 @@ void tst_QMediaPlayerBackend::playerStateAtEOS()
void tst_QMediaPlayerBackend::playFromBuffer()
{
- if (localVideoFile.isNull())
- QSKIP("No supported video file");
+ QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery");
- TestVideoSurface surface(false);
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
QMediaPlayer player;
player.setVideoOutput(&surface);
- QFile file(localVideoFile.request().url().toLocalFile());
- if (!file.open(QIODevice::ReadOnly))
- QSKIP("Could not open file");
- player.setMedia(localVideoFile, &file);
+ QFile file(u":"_s + m_localVideoFile->toEncoded(QUrl::RemoveScheme));
+ QVERIFY(file.open(QIODevice::ReadOnly));
+
+ player.setSourceDevice(&file, *m_localVideoFile);
player.play();
QTRY_VERIFY(player.position() >= 1000);
- if (surface.error() == QAbstractVideoSurface::UnsupportedFormatError)
- QSKIP("None of the pixel formats is supported by the backend");
- QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames)));
+ QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QStringLiteral("Expected >= 25, got %1").arg(surface.m_totalFrames)));
}
-TestVideoSurface::TestVideoSurface(bool storeFrames):
- m_totalFrames(0),
- m_storeFrames(storeFrames)
+void tst_QMediaPlayerBackend::audioVideoAvailable()
{
- // set default formats
- m_supported << QVideoFrame::Format_RGB32
- << QVideoFrame::Format_ARGB32
- << QVideoFrame::Format_ARGB32_Premultiplied
- << QVideoFrame::Format_RGB565
- << QVideoFrame::Format_RGB555;
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
+ QMediaPlayer player;
+ QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged);
+ QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged);
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.hasVideo());
+ QTRY_VERIFY(player.hasAudio());
+ QCOMPARE(hasVideoSpy.size(), 1);
+ QCOMPARE(hasAudioSpy.size(), 1);
+ player.setSource(QUrl());
+ QTRY_VERIFY(!player.hasVideo());
+ QTRY_VERIFY(!player.hasAudio());
+ QCOMPARE(hasVideoSpy.size(), 2);
+ QCOMPARE(hasAudioSpy.size(), 2);
}
-QList<QVideoFrame::PixelFormat> TestVideoSurface::supportedPixelFormats(
- QAbstractVideoBuffer::HandleType handleType) const
+void tst_QMediaPlayerBackend::audioVideoAvailable_updatedOnNewMedia()
{
- if (handleType == QAbstractVideoBuffer::NoHandle) {
- return m_supported;
+ CHECK_SELECTED_URL(m_localVideoFile);
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
+ QMediaPlayer player;
+ QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged);
+ QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged);
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.hasVideo());
+ QTRY_VERIFY(player.hasAudio());
+ QCOMPARE(hasVideoSpy.size(), 1);
+ QCOMPARE(hasAudioSpy.size(), 1);
+
+ hasVideoSpy.clear();
+ hasAudioSpy.clear();
+
+ player.setSource(*m_localWavFile);
+
+ auto expectedHasVideoSignals = SignalList{
+ { false },
+ };
+ QTRY_COMPARE(hasVideoSpy, expectedHasVideoSignals);
+
+ if (isGStreamerPlatform()) {
+ // GStreamer unsets hasAudio/hasVideo on new URIs
+ auto expectedHasAudioSignals = SignalList{
+ { false },
+ { true },
+ };
+ QTRY_COMPARE(hasAudioSpy, expectedHasAudioSignals);
} else {
- return QList<QVideoFrame::PixelFormat>();
+ QCOMPARE(hasAudioSpy.size(), 0);
+ }
+}
+
+void tst_QMediaPlayerBackend::isSeekable()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.isSeekable());
+}
+
+void tst_QMediaPlayerBackend::positionAfterSeek()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(false);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*m_localVideoFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ player.pause();
+ player.setPosition(500);
+ QTRY_VERIFY(player.position() == 500);
+ player.setPosition(700);
+ QVERIFY(player.position() != 0);
+ QTRY_VERIFY(player.position() == 700);
+ player.play();
+ QTRY_VERIFY(player.position() > 700);
+ player.setPosition(200);
+ QVERIFY(player.position() != 0);
+ QTRY_VERIFY(player.position() < 700);
+}
+
+void tst_QMediaPlayerBackend::pause_rendersVideoAtCorrectResolution_data()
+{
+ QTest::addColumn<MaybeUrl>("mediaFile");
+ QTest::addColumn<int>("width");
+ QTest::addColumn<int>("height");
+
+ QTest::addRow("mp4") << m_videoDimensionTestFile << 540 << 320;
+ QTest::addRow("av1") << m_av1File << 160 * 143 / 80 << 160;
+}
+
+void tst_QMediaPlayerBackend::pause_rendersVideoAtCorrectResolution()
+{
+ QFETCH(const MaybeUrl, mediaFile);
+ QFETCH(const int, width);
+ QFETCH(const int, height);
+ CHECK_SELECTED_URL(mediaFile);
+
+ // Arrange
+ TestVideoSink surface(true);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*mediaFile);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Act
+ player.pause();
+
+ if (isCI() && isFFMPEGPlatform())
+ QEXPECT_FAIL("av1", "QTBUG-119711: AV1 decoding requires HW support in the FFMPEG backend",
+ Abort);
+
+ QTRY_COMPARE(surface.m_totalFrames, 1);
+
+ // Assert
+ QCOMPARE(surface.m_frameList.last().width(), width);
+ QCOMPARE(surface.videoSize().width(), width);
+ QCOMPARE(surface.m_frameList.last().height(), height);
+ QCOMPARE(surface.videoSize().height(), height);
+}
+
+void tst_QMediaPlayerBackend::position()
+{
+ CHECK_SELECTED_URL(m_localVideoFile);
+
+ TestVideoSink surface(true);
+ QMediaPlayer player;
+ player.setVideoOutput(&surface);
+ QVERIFY(!player.isSeekable());
+ player.setSource(*m_localVideoFile);
+ QTRY_VERIFY(player.isSeekable());
+
+ player.play();
+ player.setPosition(1000);
+ QVERIFY(player.position() > 950);
+ QVERIFY(player.position() < 1050);
+ QTRY_VERIFY(player.position() > 1050);
+
+ player.pause();
+ player.setPosition(500);
+ QVERIFY(player.position() > 450);
+ QVERIFY(player.position() < 550);
+ QTest::qWait(200);
+ QVERIFY(player.position() > 450);
+ QVERIFY(player.position() < 550);
+}
+
+void tst_QMediaPlayerBackend::durationDetectionIssues_data()
+{
+ QTest::addColumn<QString>("mediaFile");
+ QTest::addColumn<qint64>("expectedDuration");
+ QTest::addColumn<int>("expectedVideoTrackCount");
+ QTest::addColumn<qint64>("expectedVideoTrackDuration");
+ QTest::addColumn<int>("expectedAudioTrackCount");
+ QTest::addColumn<QVariant>("expectedAudioTrackDuration");
+
+ // clang-format off
+
+ QTest::newRow("stream-duration-in-metadata")
+ << QString{ "qrc:/testdata/duration_issues.webm" }
+ << 400ll // Total media duration
+ << 1 // Number of video tracks in file
+ << 400ll // Video stream duration
+ << 0 // Number of audio tracks in file
+ << QVariant{}; // Audio stream duration (unused)
+
+ QTest::newRow("no-stream-duration-in-metadata")
+ << QString{ "qrc:/testdata/nokia-tune.mkv" }
+ << 7531ll // Total media duration
+ << 0 // Number of video tracks in file
+ << 0ll // Video stream duration (unused)
+ << 1 // Number of audio tracks in file
+ << QVariant{}; // Audio stream duration (not present on file)
+
+ // clang-format on
+}
+
+void tst_QMediaPlayerBackend::durationDetectionIssues()
+{
+ if (isGStreamerPlatform() && isCI())
+ QSKIP("QTBUG-124005: Fails with gstreamer on CI");
+
+ QFETCH(QString, mediaFile);
+ QFETCH(qint64, expectedDuration);
+ QFETCH(int, expectedVideoTrackCount);
+ QFETCH(qint64, expectedVideoTrackDuration);
+ QFETCH(int, expectedAudioTrackCount);
+ QFETCH(QVariant, expectedAudioTrackDuration);
+
+ // ffmpeg detects stream an incorrect stream duration, so we take
+ // the correct duration from the metadata
+
+ TestVideoSink surface(false);
+ QAudioOutput output;
+ QMediaPlayer player;
+
+ QSignalSpy durationSpy(&player, &QMediaPlayer::durationChanged);
+
+ player.setVideoOutput(&surface);
+ player.setAudioOutput(&output);
+ player.setSource(mediaFile);
+
+ QTRY_COMPARE_EQ(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Duration event received
+ QCOMPARE(durationSpy.size(), 1);
+ QCOMPARE(durationSpy.front().front(), expectedDuration);
+
+ // Duration property
+ QCOMPARE(player.duration(), expectedDuration);
+ QCOMPARE(player.metaData().value(QMediaMetaData::Duration), expectedDuration);
+
+ // Track duration properties
+ const auto videoTracks = player.videoTracks();
+ QCOMPARE(videoTracks.size(), expectedVideoTrackCount);
+
+ if (expectedVideoTrackCount != 0)
+ QCOMPARE(videoTracks.front().value(QMediaMetaData::Duration), expectedVideoTrackDuration);
+
+ const auto audioTracks = player.audioTracks();
+ QCOMPARE(audioTracks.size(), expectedAudioTrackCount);
+
+ if (expectedAudioTrackCount != 0)
+ QCOMPARE(audioTracks.front().value(QMediaMetaData::Duration), expectedAudioTrackDuration);
+}
+
+struct LoopIteration {
+ qint64 startPos;
+ qint64 endPos;
+ qint64 posCount;
+};
+// Creates a vector of LoopIterations, containing start- and end position
+// and the number of position changes per video loop iteration.
+static std::vector<LoopIteration> loopIterations(const QSignalSpy &positionSpy)
+{
+ std::vector<LoopIteration> result;
+ // Loops through all positions emitted by QMediaPlayer::positionChanged
+ for (auto &params : positionSpy) {
+ const auto pos = params.front().value<qint64>();
+
+ // Adds new LoopIteration struct to result if position is lower than previous position
+ if (result.empty() || pos < result.back().endPos) {
+ result.push_back(LoopIteration{pos, pos, 1});
+ }
+ // Updates end position of the current LoopIteration if position is higher than previous position
+ else {
+ result.back().posCount++;
+ result.back().endPos = pos;
+ }
+ }
+ return result;
+}
+
+void tst_QMediaPlayerBackend::finiteLoops()
+{
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ QCOMPARE(m_fixture->player.loops(), 1);
+ m_fixture->player.setLoops(3);
+ QCOMPARE(m_fixture->player.loops(), 3);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+ m_fixture->player.setPlaybackRate(5);
+ QCOMPARE(m_fixture->player.loops(), 3);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ // check pause doesn't affect looping
+ {
+ QTest::qWait(static_cast<int>(m_fixture->player.duration() * 3
+ * 0.6 /*relative pos*/ / m_fixture->player.playbackRate()));
+ m_fixture->player.pause();
+ m_fixture->player.play();
+ }
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ // Check for expected number of loop iterations and startPos, endPos and posCount per iteration
+ std::vector<LoopIteration> iterations = loopIterations(m_fixture->positionChanged);
+ QCOMPARE(iterations.size(), 3u);
+ QCOMPARE_GT(iterations[0].startPos, 0);
+ QCOMPARE(iterations[0].endPos, m_fixture->player.duration());
+ QCOMPARE(iterations[1].startPos, 0);
+ QCOMPARE(iterations[1].endPos, m_fixture->player.duration());
+ QCOMPARE(iterations[2].startPos, 0);
+ QCOMPARE(iterations[2].endPos, m_fixture->player.duration());
+ if (isFFMPEGPlatform()) {
+ QCOMPARE_GT(iterations[0].posCount, 10);
+ QCOMPARE_GT(iterations[1].posCount, 10);
+ QCOMPARE_GT(iterations[2].posCount, 10);
+ }
+
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+
+ // Check that loop counter is reset when playback is restarted.
+ {
+ m_fixture->positionChanged.clear();
+ m_fixture->player.play();
+ m_fixture->player.setPlaybackRate(10);
+ m_fixture->surface.waitForFrame();
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(loopIterations(m_fixture->positionChanged).size(), 3u);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
}
}
-bool TestVideoSurface::start(const QVideoSurfaceFormat &format)
+void tst_QMediaPlayerBackend::infiniteLoops()
{
- if (!isFormatSupported(format)) {
- setError(UnsupportedFormatError);
- return false;
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile2);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->player.setLoops(QMediaPlayer::Infinite);
+ QCOMPARE(m_fixture->player.loops(), QMediaPlayer::Infinite);
+
+ // select some small file
+ m_fixture->player.setSource(*m_localVideoFile2);
+ m_fixture->player.setPlaybackRate(20);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ for (int i = 0; i < 2; ++i) {
+ m_fixture->positionChanged.clear();
+
+ QTest::qWait(
+ std::max(static_cast<int>(m_fixture->player.duration()
+ / m_fixture->player.playbackRate() * 4),
+ 300 /*ensure some minimum waiting time to reduce threading flakiness*/));
+ QVERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferingMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia);
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+
+ const auto iterations = loopIterations(m_fixture->positionChanged);
+ QVERIFY(!iterations.empty());
+ QCOMPARE(iterations.front().endPos, m_fixture->player.duration());
}
- return QAbstractVideoSurface::start(format);
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ m_fixture->player.stop(); // QMediaPlayer::stop stops whether or not looping is infinite
+ QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ if (isGStreamerPlatform())
+ return; // QTBUG-124005: many StalledMedia/BufferingMedia/BufferedMedia signals are emitted
+ QCOMPARE(m_fixture->mediaStatusChanged,
+ SignalList({ { QMediaPlayer::LoadingMedia },
+ { QMediaPlayer::LoadedMedia },
+ { QMediaPlayer::BufferingMedia },
+ { QMediaPlayer::BufferedMedia },
+ { QMediaPlayer::LoadedMedia } }));
+}
+
+void tst_QMediaPlayerBackend::seekOnLoops()
+{
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->player.setLoops(3);
+ m_fixture->player.setPlaybackRate(2);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ // seek in the 1st loop
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ // wait for the 2nd loop and seek
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 8 / 9);
+
+ // wait for the 3rd loop and seek
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+
+ auto iterations = loopIterations(m_fixture->positionChanged);
+
+ QCOMPARE(iterations.size(), 3u);
+ QCOMPARE_GT(iterations[0].startPos, 0);
+ QCOMPARE(iterations[0].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[0].posCount, 2);
+ QCOMPARE(iterations[1].startPos, 0);
+ QCOMPARE(iterations[1].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[1].posCount, 2);
+ QCOMPARE(iterations[2].startPos, 0);
+ QCOMPARE(iterations[2].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[2].posCount, 2);
+
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
}
-void TestVideoSurface::stop()
+void tst_QMediaPlayerBackend::changeLoopsOnTheFly()
{
- QAbstractVideoSurface::stop();
+ QSKIP_GSTREAMER("QTBUG-123056(?): spuriously failures of the gstreamer backend");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->player.setLoops(4);
+ m_fixture->player.setPlaybackRate(5);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ // wait for the 2nd loop
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 8 / 9);
+
+ m_fixture->player.setLoops(1);
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
+
+ auto iterations = loopIterations(m_fixture->positionChanged);
+ QCOMPARE(iterations.size(), 2u);
+
+ QCOMPARE(iterations[1].startPos, 0);
+ QCOMPARE(iterations[1].endPos, m_fixture->player.duration());
+ QCOMPARE_GT(iterations[1].posCount, 2);
}
-bool TestVideoSurface::present(const QVideoFrame &frame)
+void tst_QMediaPlayerBackend::seekAfterLoopReset()
{
- if (m_storeFrames)
- m_frameList.push_back(frame);
- m_totalFrames++;
- return true;
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+#ifdef Q_OS_MACOS
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("The test accidently gets crashed on macOS CI, not reproduced locally. To be "
+ "investigated: QTBUG-111744");
+#endif
+
+ m_fixture->surface.setStoreFrames(false);
+
+ m_fixture->player.setLoops(QMediaPlayer::Infinite);
+ m_fixture->player.setPlaybackRate(2);
+
+ m_fixture->player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ m_fixture->player.play();
+ m_fixture->surface.waitForFrame();
+
+ // seek in the 1st loop
+ m_fixture->player.setPosition(m_fixture->player.duration() * 4 / 5);
+
+ // wait for the 2nd loop
+ m_fixture->surface.waitForFrame();
+ QTRY_VERIFY(m_fixture->player.position() < m_fixture->player.duration() / 2);
+
+ // reset loops and seek
+ m_fixture->player.setLoops(1);
+ m_fixture->player.setPosition(m_fixture->player.duration() * 8 / 9);
+
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState);
+ QCOMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::EndOfMedia);
}
+void tst_QMediaPlayerBackend::changeVideoOutputNoFramesLost()
+{
+ QSKIP_GSTREAMER("QTBUG-124005: gstreamer will lose frames, possibly due to buffering");
-void ProbeDataHandler::processFrame(const QVideoFrame &frame)
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ QVideoSink sinks[4];
+ std::atomic_int framesCount[4] = {
+ 0,
+ };
+ for (int i = 0; i < 4; ++i)
+ setVideoSinkAsyncFramesCounter(sinks[i], framesCount[i]);
+
+ QMediaPlayer player;
+
+ player.setPlaybackRate(10);
+
+ player.setVideoOutput(&sinks[0]);
+ player.setSource(*m_localVideoFile3ColorsWithSound);
+ player.play();
+ QTRY_VERIFY(!player.isPlaying());
+
+ player.setPlaybackRate(4);
+ player.setVideoOutput(&sinks[1]);
+ player.play();
+
+ QTRY_COMPARE_GE(framesCount[1], framesCount[0] / 4);
+ player.setVideoOutput(&sinks[2]);
+ const int savedFrameNumber1 = framesCount[1];
+
+ QTRY_COMPARE_GE(framesCount[2], (framesCount[0] - savedFrameNumber1) / 2);
+ player.setVideoOutput(&sinks[3]);
+ const int savedFrameNumber2 = framesCount[2];
+
+ QTRY_VERIFY(!player.isPlaying());
+
+ // check if no frames sent to old sinks
+ QCOMPARE(framesCount[1], savedFrameNumber1);
+ QCOMPARE(framesCount[2], savedFrameNumber2);
+
+ // no frames lost
+ QCOMPARE(framesCount[1] + framesCount[2] + framesCount[3], framesCount[0]);
+}
+
+void tst_QMediaPlayerBackend::cleanSinkAndNoMoreFramesAfterStop()
{
- m_frameList.append(frame);
+ QSKIP_GSTREAMER(
+ "QTBUG-124005: spurious failures on gstreamer, probably due to asynchronous play()");
+
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ QVideoSink sink;
+ std::atomic_int framesCount = 0;
+ setVideoSinkAsyncFramesCounter(sink, framesCount);
+ QMediaPlayer player;
+
+ player.setPlaybackRate(10);
+ player.setVideoOutput(&sink);
+
+ player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ // Run a few time to have more chances to detect race conditions
+ for (int i = 0; i < 8; ++i) {
+ player.play();
+ QTRY_VERIFY(framesCount > 0);
+ QVERIFY(sink.videoFrame().isValid());
+
+ player.stop();
+
+ if (isGStreamerPlatform())
+ // QTBUG-124005: stop() is asynchronous in gstreamer
+ QTRY_VERIFY(!sink.videoFrame().isValid());
+ else
+ QVERIFY(!sink.videoFrame().isValid());
+
+ QCOMPARE_NE(framesCount, 0);
+ framesCount = 0;
+
+ QTest::qWait(30);
+
+ if (isGStreamerPlatform())
+ continue; // QTBUG-124005: stop() is asynchronous in gstreamer
+
+ // check if nothing changed after short waiting
+ QCOMPARE(framesCount, 0);
+ }
+}
+
+void tst_QMediaPlayerBackend::lazyLoadVideo()
+{
+ QQmlEngine engine;
+ QQmlComponent component(&engine);
+ component.loadUrl(QUrl("qrc:/LazyLoad.qml"));
+ QScopedPointer<QObject> root(component.create());
+ QQuickItem *rootItem = qobject_cast<QQuickItem *>(root.get());
+ QVERIFY(rootItem);
+
+ QQuickView view;
+ rootItem->setParentItem(view.contentItem());
+ view.resize(600, 800);
+ view.show();
+
+ QQuickLoader *loader = qobject_cast<QQuickLoader *>(rootItem->findChild<QQuickItem *>("loader"));
+ QVERIFY(loader);
+ QCOMPARE(QQmlProperty::read(loader, "active").toBool(), false);
+ loader->setProperty("active", true);
+ QCOMPARE(QQmlProperty::read(loader, "active").toBool(), true);
+
+ QQuickItem *videoPlayer = qobject_cast<QQuickItem *>(loader->findChild<QQuickItem *>("videoPlayer"));
+ QVERIFY(videoPlayer);
+
+ QTRY_COMPARE_EQ(QQmlProperty::read(videoPlayer, "playbackState").value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState);
+ QCOMPARE(QQmlProperty::read(videoPlayer, "error").value<QMediaPlayer::Error>(), QMediaPlayer::NoError);
+
+ QVideoSink *videoSink = QQmlProperty::read(videoPlayer, "videoSink").value<QVideoSink *>();
+ QVERIFY(videoSink);
+
+ QSignalSpy spy(videoSink, &QVideoSink::videoFrameChanged);
+ QVERIFY(spy.wait());
+
+ QVideoFrame frame = spy.at(0).at(0).value<QVideoFrame>();
+ QVERIFY(frame.isValid());
+}
+
+void tst_QMediaPlayerBackend::videoSinkSignals()
+{
+ std::atomic<int> videoFrameCounter = 0;
+ std::atomic<int> videoSizeCounter = 0;
+
+ // TODO: come up with custom frames source,
+ // create the test target tst_QVideoSinkBackend,
+ // and move the test there
+
+ CHECK_SELECTED_URL(m_localVideoFile2);
+
+ QVideoSink sink;
+ QMediaPlayer player;
+ player.setVideoSink(&sink);
+
+ player.setSource(*m_localVideoFile2);
+
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
+
+ sink.platformVideoSink()->setNativeSize({}); // reset size to be able to check the size update
+
+ connect(&sink, &QVideoSink::videoFrameChanged, this, [&](const QVideoFrame &frame) {
+ QCOMPARE(sink.videoFrame(), frame);
+ QCOMPARE(sink.videoSize(), frame.size());
+ ++videoFrameCounter;
+ }, Qt::DirectConnection);
+
+ connect(&sink, &QVideoSink::videoSizeChanged, this, [&]() {
+ QCOMPARE(sink.videoSize(), sink.videoFrame().size());
+ if (sink.videoSize().isValid()) // filter end frame
+ ++videoSizeCounter;
+ }, Qt::DirectConnection);
+
+ player.play();
+
+ QTRY_COMPARE_GE(videoFrameCounter, 2);
+ QCOMPARE(videoSizeCounter, 1);
+}
+
+void tst_QMediaPlayerBackend::nonAsciiFileName()
+{
+ CHECK_SELECTED_URL(m_localWavFile);
+
+ auto temporaryFile =
+ copyResourceToTemporaryFile(":/testdata/test.wav", "äöüØøÆ中文.XXXXXX.wav");
+ QVERIFY(temporaryFile);
+
+ m_fixture->player.setSource(temporaryFile->fileName());
+ m_fixture->player.play();
+
+ QTRY_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia
+ || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia);
+
+ QCOMPARE(m_fixture->errorOccurred.size(), 0);
}
-void ProbeDataHandler::processBuffer(const QAudioBuffer &buffer)
+void tst_QMediaPlayerBackend::setMedia_setsVideoSinkSize_beforePlaying()
{
- m_bufferList.append(buffer);
+ CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound);
+
+ QVideoSink sink1;
+ QVideoSink sink2;
+ QMediaPlayer player;
+
+ QSignalSpy spy1(&sink1, &QVideoSink::videoSizeChanged);
+ QSignalSpy spy2(&sink2, &QVideoSink::videoSizeChanged);
+
+ player.setVideoOutput(&sink1);
+ QCOMPARE(sink1.videoSize(), QSize());
+
+ player.setSource(*m_localVideoFile3ColorsWithSound);
+
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia);
+
+ QCOMPARE(sink1.videoSize(), QSize(684, 384));
+
+ player.setVideoOutput(&sink2);
+ QCOMPARE(sink2.videoSize(), QSize(684, 384));
+
+ QCOMPARE(spy1.size(), 1);
+ QCOMPARE(spy2.size(), 1);
}
-void ProbeDataHandler::flushVideo()
+#if QT_CONFIG(process)
+std::unique_ptr<QProcess> tst_QMediaPlayerBackend::createRtpStreamProcess(QString fileName,
+ QString sdpUrl)
{
- isVideoFlushCalled = true;
+ Q_ASSERT(!m_vlcCommand.isEmpty());
+
+ auto process = std::make_unique<QProcess>();
+#if defined(Q_OS_WINDOWS)
+ fileName.replace('/', '\\');
+#endif
+
+ QStringList vlcParams = { "-vvv", fileName,
+ "--sout", QStringLiteral("#rtp{dst=localhost,sdp=%1}").arg(sdpUrl),
+ "--intf", "dummy" };
+
+ process->start(m_vlcCommand, vlcParams);
+ if (!process->waitForStarted())
+ return nullptr;
+
+ // rtp stream might be with started some delay after the vlc process starts.
+ // Ideally, we should wait for open connections, it requires some extra work + QNetwork
+ // dependency.
+ int timeout = 500;
+#ifdef Q_OS_MACOS
+ timeout = 2000;
+#endif
+ QTest::qWait(timeout);
+
+ return process;
}
+#endif //QT_CONFIG(process)
-void ProbeDataHandler::flushAudio()
+void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata_data()
{
+ QTest::addColumn<MaybeUrl>("fileURL");
+ QTest::addColumn<QRgb>("expectedColor");
+ QTest::addColumn<QtVideo::Rotation>("expectedRotationAngle");
+ QTest::addColumn<QSize>("videoSize");
+
+ // clang-format off
+ QTest::addRow("without rotation") << m_colorMatrixVideo
+ << QRgb(0xff0000)
+ << QtVideo::Rotation::None
+ << QSize(960, 540);
+
+ QTest::addRow("90 deg clockwise") << m_colorMatrix90degClockwiseVideo
+ << QRgb(0x0000FF)
+ << QtVideo::Rotation::Clockwise90
+ << QSize(540, 960);
+
+ QTest::addRow("180 deg clockwise") << m_colorMatrix180degClockwiseVideo
+ << QRgb(0xFFFF00)
+ << QtVideo::Rotation::Clockwise180
+ << QSize(960, 540);
+
+ QTest::addRow("270 deg clockwise") << m_colorMatrix270degClockwiseVideo
+ << QRgb(0x00FF00)
+ << QtVideo::Rotation::Clockwise270
+ << QSize(540, 960);
+ // clang-format on
+}
+void tst_QMediaPlayerBackend::play_playsRotatedVideoOutput_whenVideoFileHasOrientationMetadata()
+{
+ if (isGStreamerPlatform() && isCI())
+ QSKIP("QTBUG-124005: Fails with gstreamer on CI");
+
+ // This test uses 4 video files with a 2x2 color matrix consisting of
+ // red (upper left), blue (lower left), yellow (lower right) and green (upper right).
+ // The files are identical, except that three of them contain
+ // orientation (rotation) metadata specifying that they should be
+ // viewed with a 90, 180 and 270 degree clockwise rotation respectively.
+
+ // Fetch path and expected color of upper left area of each file
+ QFETCH(const MaybeUrl, fileURL);
+ QFETCH(const QRgb, expectedColor);
+ QFETCH(const QtVideo::Rotation, expectedRotationAngle);
+ QFETCH(const QSize, videoSize);
+
+ CHECK_SELECTED_URL(fileURL);
+
+ // Load video file
+ m_fixture->player.setSource(*fileURL);
+ QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia);
+
+ // Compare videoSize of the output video sink with the expected value before starting playing
+ QCOMPARE(m_fixture->surface.videoSize(), videoSize);
+
+ // Compare orientation metadata of QMediaPlayer with expected value
+ const auto metaData = m_fixture->player.metaData();
+ const auto playerOrientation = metaData.value(QMediaMetaData::Orientation).value<QtVideo::Rotation>();
+ QCOMPARE(playerOrientation, expectedRotationAngle);
+
+ // Compare orientation metadata of active video stream with expected value
+ const int activeVideoTrack = m_fixture->player.activeVideoTrack();
+ const auto videoTrackMetaData = m_fixture->player.videoTracks().at(activeVideoTrack);
+ const auto videoTrackOrientation = videoTrackMetaData.value(QMediaMetaData::Orientation).value<QtVideo::Rotation>();
+ QCOMPARE(videoTrackOrientation, expectedRotationAngle);
+
+ // Play video file, sample upper left area, compare with expected color
+ m_fixture->player.play();
+ QTRY_COMPARE(m_fixture->player.playbackState(), QMediaPlayer::PlayingState);
+ QVideoFrame videoFrame = m_fixture->surface.waitForFrame();
+ QVERIFY(videoFrame.isValid());
+ QCOMPARE(videoFrame.rotation(), expectedRotationAngle);
+ QImage image = videoFrame.toImage();
+ QVERIFY(!image.isNull());
+ QRgb upperLeftColor = image.pixel(5, 5);
+ QCOMPARE_LT(colorDifference(upperLeftColor, expectedColor), 0.004);
+
+ QSKIP_GSTREAMER("QTBUG-124005: surface.videoSize() not updated with rotation");
+
+ // Compare videoSize of the output video sink with the expected value after getting a frame
+ QCOMPARE(m_fixture->surface.videoSize(), videoSize);
}
QTEST_MAIN(tst_QMediaPlayerBackend)
diff --git a/tests/auto/integration/qmediaplayerformatsupport/CMakeLists.txt b/tests/auto/integration/qmediaplayerformatsupport/CMakeLists.txt
new file mode 100644
index 000000000..b4dd19f75
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/CMakeLists.txt
@@ -0,0 +1,30 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+file(GLOB_RECURSE test_data_glob
+ RELATIVE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ testdata/**
+)
+
+list(APPEND testdata_resource_files ${test_data_glob})
+
+qt_internal_add_test(tst_qmediaplayerformatsupport
+ SOURCES
+ tst_qmediaplayerformatsupport.cpp
+ ../shared/mediabackendutils.h
+ INCLUDE_DIRECTORIES
+ ../shared/
+ LIBRARIES
+ Qt::Core
+ Qt::MultimediaPrivate
+ TESTDATA
+ ${testdata_resource_files}
+)
+
+qt_internal_add_resource(tst_qmediaplayerformatsupport "testdata"
+ PREFIX
+ "/"
+ FILES
+ ${testdata_resource_files}
+)
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/README.md b/tests/auto/integration/qmediaplayerformatsupport/testdata/README.md
new file mode 100644
index 000000000..f52108bcc
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/README.md
@@ -0,0 +1,35 @@
+Sample video files are created using FFmpeg, for example:
+
+:: Supported container formats
+ffmpeg -ss 2 -i flipable.gif -t 1 -r 5 containers/supported/container.avi
+ffmpeg -ss 2 -i flipable.gif -t 1 -r 5 containers/supported/container.mkv
+ffmpeg -ss 2 -i flipable.gif -t 1 -r 5 containers/supported/container.mp4
+ffmpeg -ss 2 -i flipable.gif -t 1 -r 25 containers/supported/container.mpeg
+ffmpeg -ss 2 -i flipable.gif -t 1 -r 25 containers/supported/container.wmv
+
+:: Unsupported container formats
+ffmpeg -ss 2 -i flipable.gif -t 1 -r 5 containers/unsupported/container.webm
+
+:: Supported pixel formats h264
+ffmpeg -i flipable.gif -pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv420p.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv420p10 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv420p10.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv422p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv422p.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv422p10 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv422p10.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv444p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv444p.mp4
+ffmpeg -i flipable.gif -pix_fmt yuvj420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuvj420p.mp4
+ffmpeg -i flipable.gif -pix_fmt yuvj422p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuvj422p.mp4
+ffmpeg -i flipable.gif -pix_fmt yuvj444p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuvj444p.mp4
+ffmpeg -i flipable.gif -pix_fmt nv12 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_nv12.mp4
+ffmpeg -i flipable.gif -pix_fmt nv16 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_nv16.mp4
+ffmpeg -i flipable.gif -pix_fmt nv21 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_nv21.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv420p10le -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv420p10le.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv444p10 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv444p10.mp4
+ffmpeg -i flipable.gif -pix_fmt yuv422p10le -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_yuv422p10le.mp4
+ffmpeg -i flipable.gif -pix_fmt gray -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_gray.mp4
+ffmpeg -i flipable.gif -pix_fmt gray10le -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264 -r 5 pixel_formats/supported/h264_gray10le.mp4
+
+ffmpeg -i flipable.gif -pix_fmt bgr0 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264rgb -r 5 pixel_formats/supported/h264_bgr0.mp4
+ffmpeg -i flipable.gif -pix_fmt bgr24 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264rgb -r 5 pixel_formats/supported/h264_bgr24.mp4
+ffmpeg -i flipable.gif -pix_fmt rgb24 -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range tv -vcodec libx264rgb -r 5 pixel_formats/supported/h264_rgb24.mp4
+
+
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.avi b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.avi
new file mode 100644
index 000000000..a48028550
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.avi
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkv b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkv
new file mode 100644
index 000000000..2e362d7ca
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkv
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4
new file mode 100644
index 000000000..bff40278d
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpeg b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpeg
new file mode 100644
index 000000000..b94a47f0a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpeg
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmv b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmv
new file mode 100644
index 000000000..1a7577e3f
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmv
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webp b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webp
new file mode 100644
index 000000000..ed8b6f631
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webp
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gif b/tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gif
new file mode 100644
index 000000000..fd187906b
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gif
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr0.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr0.mp4
new file mode 100644
index 000000000..ff9eacb65
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr0.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr24.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr24.mp4
new file mode 100644
index 000000000..ff9eacb65
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr24.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray.mp4
new file mode 100644
index 000000000..c69357d2b
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray10le.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray10le.mp4
new file mode 100644
index 000000000..339912757
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray10le.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv12.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv12.mp4
new file mode 100644
index 000000000..20b51609a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv12.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv16.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv16.mp4
new file mode 100644
index 000000000..165819b22
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv16.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv21.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv21.mp4
new file mode 100644
index 000000000..20b51609a
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv21.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_rgb24.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_rgb24.mp4
new file mode 100644
index 000000000..ff9eacb65
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_rgb24.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p.mp4
new file mode 100644
index 000000000..53ef62b45
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10.mp4
new file mode 100644
index 000000000..bb45d344c
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10le.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10le.mp4
new file mode 100644
index 000000000..bb45d344c
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10le.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p.mp4
new file mode 100644
index 000000000..165819b22
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10.mp4
new file mode 100644
index 000000000..563fce6da
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10le.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10le.mp4
new file mode 100644
index 000000000..563fce6da
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10le.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p.mp4
new file mode 100644
index 000000000..953d014ec
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p10.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p10.mp4
new file mode 100644
index 000000000..c27683ed7
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p10.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj420p.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj420p.mp4
new file mode 100644
index 000000000..3906325e1
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj420p.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj422p.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj422p.mp4
new file mode 100644
index 000000000..6b2d43fd9
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj422p.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj444p.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj444p.mp4
new file mode 100644
index 000000000..75008f2aa
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj444p.mp4
Binary files differ
diff --git a/tests/auto/integration/qmediaplayerformatsupport/tst_qmediaplayerformatsupport.cpp b/tests/auto/integration/qmediaplayerformatsupport/tst_qmediaplayerformatsupport.cpp
new file mode 100644
index 000000000..8cabb28c8
--- /dev/null
+++ b/tests/auto/integration/qmediaplayerformatsupport/tst_qmediaplayerformatsupport.cpp
@@ -0,0 +1,124 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "mediabackendutils.h"
+#include <QtTest/QtTest>
+#include <QDebug>
+#include <QtMultimedia/qmediaplayer.h>
+#include <QtMultimedia/QVideoSink>
+
+using namespace Qt::StringLiterals;
+
+QT_USE_NAMESPACE
+
+struct Fixture
+{
+ Fixture() { player.setVideoOutput(&videoOutput); }
+
+ QVideoSink videoOutput;
+ QMediaPlayer player;
+ QSignalSpy errorOccurred{ &player, &QMediaPlayer::errorOccurred };
+ QSignalSpy playbackStateChanged{ &player, &QMediaPlayer::playbackStateChanged };
+
+ bool startedPlaying() const
+ {
+ return playbackStateChanged.contains(QList<QVariant>{ QMediaPlayer::PlayingState });
+ }
+};
+
+void addTestData(QLatin1StringView dir)
+{
+ QDirIterator it(dir);
+ while (it.hasNext()) {
+ QString v = it.next();
+ QTest::addRow("%s", v.toLatin1().data()) << QUrl{ "qrc" + v };
+ }
+}
+
+class tst_qmediaplayerformatsupport : public QObject
+{
+ Q_OBJECT
+
+public slots:
+ void initTestCase()
+ {
+ if (!isFFMPEGPlatform())
+ QSKIP("Test is only intended for FFmpeg backend");
+ }
+
+private slots:
+ void play_succeeds_withSupportedContainer_data()
+ {
+ QTest::addColumn<QUrl>("url");
+ addTestData(":testdata/containers/supported"_L1);
+ }
+
+ void play_succeeds_withSupportedContainer()
+ {
+ QFETCH(const QUrl, url);
+
+ Fixture f;
+ f.player.setSource(url);
+ f.player.play();
+
+ QTRY_VERIFY(f.startedPlaying());
+
+ // Log to understand failures in CI
+ for (const QList<QVariant> &err : f.errorOccurred)
+ qCritical() << "Unexpected failure detected:" << err[0] << "," << err[1];
+
+#ifdef Q_OS_ANDROID
+ QSKIP("QTBUG-125613 Limited format support on Android 14");
+#endif
+
+ QVERIFY(f.errorOccurred.empty());
+ }
+
+ void play_succeeds_withSupportedPixelFormats_data()
+ {
+ QTest::addColumn<QUrl>("url");
+ addTestData(":testdata/pixel_formats/supported"_L1);
+ }
+
+ void play_succeeds_withSupportedPixelFormats()
+ {
+ QFETCH(const QUrl, url);
+
+ Fixture f;
+ f.player.setSource(url);
+ f.player.play();
+
+ QTRY_VERIFY(f.startedPlaying());
+
+ // Log to understand failures in CI
+ for (const QList<QVariant> &err : f.errorOccurred)
+ qCritical() << "Unexpected failure detected:" << err[0] << "," << err[1];
+
+#ifdef Q_OS_ANDROID
+ QSKIP("QTBUG-125613 Limited format support on Android 14");
+#endif
+
+ QVERIFY(f.errorOccurred.empty());
+ }
+
+ void play_fails_withUnsupportedContainer_data()
+ {
+ QTest::addColumn<QUrl>("url");
+ addTestData(":testdata/containers/unsupported"_L1);
+ }
+
+ void play_fails_withUnsupportedContainer()
+ {
+ QFETCH(const QUrl, url);
+
+ Fixture f;
+ f.player.setSource(url);
+ f.player.play();
+
+ QTRY_COMPARE_NE(f.player.error(), QMediaPlayer::NoError);
+ }
+};
+
+QTEST_MAIN(tst_qmediaplayerformatsupport)
+
+#include "tst_qmediaplayerformatsupport.moc"
diff --git a/tests/auto/integration/qml/CMakeLists.txt b/tests/auto/integration/qml/CMakeLists.txt
new file mode 100644
index 000000000..1b7a1f686
--- /dev/null
+++ b/tests/auto/integration/qml/CMakeLists.txt
@@ -0,0 +1,23 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qml.pro.
+
+#####################################################################
+## tst_qml Test:
+#####################################################################
+
+qt_internal_add_test(tst_qml
+ GUI
+ QMLTEST
+ SOURCES
+ tst_qml.cpp
+ LIBRARIES
+ Qt::Gui
+)
+
+#### Keys ignored in scope 1:.:.:qml.pro:<TRUE>:
+# DEPLOYMENT = "importFiles"
+# TEMPLATE = "app"
+# importFiles.files = "soundeffect"
+# importFiles.path = "."
diff --git a/tests/auto/integration/qml/qml.pro b/tests/auto/integration/qml/qml.pro
deleted file mode 100644
index c0015d9bd..000000000
--- a/tests/auto/integration/qml/qml.pro
+++ /dev/null
@@ -1,10 +0,0 @@
-TEMPLATE=app
-TARGET=tst_qml
-CONFIG += qmltestcase
-SOURCES += tst_qml.cpp
-
-
-importFiles.files = soundeffect
-
-importFiles.path = .
-DEPLOYMENT += importFiles
diff --git a/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml b/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml
index 9100cd806..0b5867bce 100644
--- a/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml
+++ b/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml
@@ -1,33 +1,8 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-import QtQuick 2.0
-import QtMultimedia 5.0
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+import QtMultimedia
import QtTest 1.0
/*
diff --git a/tests/auto/integration/qml/tst_qml.cpp b/tests/auto/integration/qml/tst_qml.cpp
index 668285a31..2bdca1037 100644
--- a/tests/auto/integration/qml/tst_qml.cpp
+++ b/tests/auto/integration/qml/tst_qml.cpp
@@ -1,30 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtQuickTest/quicktest.h>
QUICK_TEST_MAIN(qml)
diff --git a/tests/auto/integration/qquickvideooutput/CMakeLists.txt b/tests/auto/integration/qquickvideooutput/CMakeLists.txt
new file mode 100644
index 000000000..909416140
--- /dev/null
+++ b/tests/auto/integration/qquickvideooutput/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qdeclarativevideooutput.pro.
+
+#####################################################################
+## tst_qdeclarativevideooutput Test:
+#####################################################################
+
+qt_internal_add_test(tst_qquickvideooutput
+ SOURCES
+ tst_qquickvideooutput.cpp
+ INCLUDE_DIRECTORIES
+ ../../../../src/imports/multimedia
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ Qt::MultimediaQuickPrivate
+ Qt::Qml
+ Qt::Quick
+)
+
+# Resources:
+set(qml_resource_files
+ "main.qml"
+)
+
+qt_internal_add_resource(tst_qquickvideooutput "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
+
diff --git a/tests/auto/integration/qdeclarativevideooutput/main.qml b/tests/auto/integration/qquickvideooutput/main.qml
index e456adf6c..6893a6e9f 100644
--- a/tests/auto/integration/qdeclarativevideooutput/main.qml
+++ b/tests/auto/integration/qquickvideooutput/main.qml
@@ -1,5 +1,5 @@
-import QtQuick 2.0
-import QtMultimedia 5.0
+import QtQuick
+import QtMultimedia
VideoOutput {
width: 150
diff --git a/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp b/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp
new file mode 100644
index 000000000..85a30bbb7
--- /dev/null
+++ b/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp
@@ -0,0 +1,392 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//TESTED_COMPONENT=plugins/declarative/multimedia
+
+#include <QtTest/QtTest>
+
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QQuickView>
+#include <QVideoSink>
+#include <QMediaPlayer>
+
+#include "private/qquickvideooutput_p.h"
+
+#include <qobject.h>
+#include <qvideoframeformat.h>
+#include <qvideoframe.h>
+
+void presentDummyFrame(QVideoSink *sink, const QSize &size)
+{
+ if (sink) {
+ QVideoFrameFormat format(size, QVideoFrameFormat::Format_ARGB8888_Premultiplied);
+ QVideoFrame frame(format);
+
+ sink->setVideoFrame(frame);
+
+ // Have to spin an event loop or two for the surfaceFormatChanged() signal
+ qApp->processEvents();
+ }
+}
+
+class tst_QQuickVideoOutput : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QQuickVideoOutput();
+
+ ~tst_QQuickVideoOutput() override
+ {
+ delete m_mappingOutput;
+ delete m_mappingComponent;
+ }
+
+public slots:
+ void initTestCase();
+
+private slots:
+ void fillMode();
+ void orientation();
+ void surfaceSource();
+ void paintSurface();
+ void sourceRect();
+
+ void contentRect();
+ void contentRect_data();
+
+private:
+ QQmlEngine m_engine;
+
+ // Variables used for the mapping test
+ QQmlComponent *m_mappingComponent = nullptr;
+ QQuickVideoOutput *m_mappingOutput = nullptr;
+
+ void updateOutputGeometry(QObject *output);
+};
+
+void tst_QQuickVideoOutput::initTestCase()
+{
+ // We initialize the mapping vars here
+ m_mappingComponent = new QQmlComponent(&m_engine);
+ m_mappingComponent->loadUrl(QUrl("qrc:/main.qml"));
+
+ auto *component = m_mappingComponent->create();
+ QVERIFY(component != nullptr);
+
+ m_mappingOutput = qobject_cast<QQuickVideoOutput *>(component);
+ QVERIFY(m_mappingOutput);
+
+ presentDummyFrame(m_mappingOutput->videoSink(), QSize(200,100));
+ updateOutputGeometry(m_mappingOutput);
+ // First make sure the component has processed the frame
+ QCOMPARE(m_mappingOutput->sourceRect(), QRectF(0, 0, 200,100));
+}
+
+tst_QQuickVideoOutput::tst_QQuickVideoOutput()
+{
+}
+
+void tst_QQuickVideoOutput::fillMode()
+{
+ QQmlComponent component(&m_engine);
+ component.loadUrl(QUrl("qrc:/main.qml"));
+
+ QObject *videoOutput = component.create();
+ QVERIFY(videoOutput != nullptr);
+
+ QSignalSpy propSpy(videoOutput, SIGNAL(fillModeChanged(QQuickVideoOutput::FillMode)));
+
+ // Default is preserveaspectfit
+ QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::PreserveAspectFit);
+ QCOMPARE(propSpy.size(), 0);
+
+ videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::PreserveAspectCrop)));
+ QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::PreserveAspectCrop);
+ QCOMPARE(propSpy.size(), 1);
+
+ videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch)));
+ QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::Stretch);
+ QCOMPARE(propSpy.size(), 2);
+
+ videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch)));
+ QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::Stretch);
+ QCOMPARE(propSpy.size(), 2);
+
+ delete videoOutput;
+}
+
+void tst_QQuickVideoOutput::orientation()
+{
+ QQmlComponent component(&m_engine);
+ component.loadUrl(QUrl("qrc:/main.qml"));
+
+ QObject *videoOutput = component.create();
+ QVERIFY(videoOutput != nullptr);
+
+ QSignalSpy propSpy(videoOutput, SIGNAL(orientationChanged()));
+
+ // Default orientation is 0
+ QCOMPARE(videoOutput->property("orientation").toInt(), 0);
+ QCOMPARE(propSpy.size(), 0);
+
+ videoOutput->setProperty("orientation", QVariant(90));
+ QCOMPARE(videoOutput->property("orientation").toInt(), 90);
+ QCOMPARE(propSpy.size(), 1);
+
+ videoOutput->setProperty("orientation", QVariant(180));
+ QCOMPARE(videoOutput->property("orientation").toInt(), 180);
+ QCOMPARE(propSpy.size(), 2);
+
+ videoOutput->setProperty("orientation", QVariant(270));
+ QCOMPARE(videoOutput->property("orientation").toInt(), 270);
+ QCOMPARE(propSpy.size(), 3);
+
+ videoOutput->setProperty("orientation", QVariant(360));
+ QCOMPARE(videoOutput->property("orientation").toInt(), 360);
+ QCOMPARE(propSpy.size(), 4);
+
+ // More than 360 should be fine
+ videoOutput->setProperty("orientation", QVariant(540));
+ QCOMPARE(videoOutput->property("orientation").toInt(), 540);
+ QCOMPARE(propSpy.size(), 5);
+
+ // Negative should be fine
+ videoOutput->setProperty("orientation", QVariant(-180));
+ QCOMPARE(videoOutput->property("orientation").toInt(), -180);
+ QCOMPARE(propSpy.size(), 6);
+
+ // Same value should not reemit
+ videoOutput->setProperty("orientation", QVariant(-180));
+ QCOMPARE(videoOutput->property("orientation").toInt(), -180);
+ QCOMPARE(propSpy.size(), 6);
+
+ // Non multiples of 90 should not work
+ videoOutput->setProperty("orientation", QVariant(-1));
+ QCOMPARE(videoOutput->property("orientation").toInt(), -180);
+ QCOMPARE(propSpy.size(), 6);
+
+ delete videoOutput;
+}
+
+void tst_QQuickVideoOutput::surfaceSource()
+{
+ QQmlComponent component(&m_engine);
+ component.loadUrl(QUrl("qrc:/main.qml"));
+
+ QObject *videoOutput = component.create();
+ QVERIFY(videoOutput != nullptr);
+
+ QMediaPlayer holder(this);
+
+ QCOMPARE(holder.videoOutput(), nullptr);
+
+ holder.setVideoOutput(videoOutput);
+
+ QVERIFY(holder.videoOutput() != nullptr);
+ QVERIFY(holder.videoSink() != nullptr);
+
+ delete videoOutput;
+
+ // This should clear the surface
+ QVERIFY(holder.videoOutput() == nullptr);
+ QVERIFY(holder.videoSink() == nullptr);
+
+ // Also, creating two sources, setting them in order, and destroying the first
+ // should not zero holder.videoSink()
+ videoOutput = component.create();
+ holder.setVideoOutput(videoOutput);
+
+ QObject *surface = holder.videoOutput();
+ QVERIFY(surface != nullptr);
+
+ QObject *videoOutput2 = component.create();
+ QVERIFY(videoOutput2);
+ holder.setVideoOutput(videoOutput2);
+ QVERIFY(holder.videoOutput() != nullptr);
+ QVERIFY(holder.videoOutput() != surface); // Surface should have changed
+ surface = holder.videoOutput();
+ QVERIFY(surface == videoOutput2);
+
+ // Now delete first one
+ delete videoOutput;
+ QVERIFY(holder.videoOutput() == surface); // Should not have changed surface
+
+ // Now create a second surface and assign it as the source
+ // The old surface holder should be zeroed
+ QMediaPlayer holder2(this);
+ holder2.setVideoOutput(videoOutput2);
+
+ QVERIFY(holder.videoOutput() == nullptr);
+ QVERIFY(holder2.videoOutput() != nullptr);
+
+ // Finally a combination - set the same source to two things, then assign a new source
+ // to the first output - should not reset the first source
+ videoOutput = component.create();
+ holder2.setVideoOutput(videoOutput);
+
+ // Both vo and vo2 were pointed to holder2 - setting vo2 should not clear holder2
+ QVERIFY(holder2.videoOutput() != nullptr);
+ QVERIFY(holder.videoOutput() == nullptr);
+ holder.setVideoOutput(videoOutput2);
+ QVERIFY(holder2.videoOutput() != nullptr);
+ QVERIFY(holder.videoOutput() != nullptr);
+
+ // They should also be independent
+ QVERIFY(holder.videoOutput() != holder2.videoOutput());
+
+ delete videoOutput;
+ delete videoOutput2;
+}
+
+static const uchar rgb32ImageData[] =
+{// B G R A
+ 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff,
+ 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff,
+ 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff,
+ 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff,
+ 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff,
+ 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff,
+ 0x00, 0x01, 0x02, 0xff, 0x03, 0x04, 0x05, 0xff,
+ 0x06, 0x07, 0x08, 0xff, 0x09, 0x0a, 0x0b, 0xff
+};
+
+void tst_QQuickVideoOutput::paintSurface()
+{
+ QQuickView window;
+ window.setSource(QUrl("qrc:/main.qml"));
+ window.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ auto videoOutput = qobject_cast<QQuickVideoOutput *>(window.rootObject());
+ QVERIFY(videoOutput);
+
+ auto surface = videoOutput->videoSink();
+ QVERIFY(surface);
+ videoOutput->setSize(QSize(2, 2));
+
+ QVideoFrame frame(QVideoFrameFormat(QSize(4, 4), QVideoFrameFormat::Format_ARGB8888));
+ frame.map(QtVideo::MapMode::ReadWrite);
+ QCOMPARE(frame.mappedBytes(0), 64);
+ memcpy(frame.bits(0), rgb32ImageData, 64);
+ frame.unmap();
+ surface->setVideoFrame(frame);
+}
+
+void tst_QQuickVideoOutput::sourceRect()
+{
+ QQmlComponent component(&m_engine);
+ component.loadUrl(QUrl("qrc:/main.qml"));
+
+ QObject *videoOutput = component.create();
+ QVERIFY(videoOutput != nullptr);
+
+ QMediaPlayer holder(this);
+
+ QSignalSpy propSpy(videoOutput, SIGNAL(sourceRectChanged()));
+
+ holder.setVideoOutput(videoOutput);
+
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF());
+
+ presentDummyFrame(holder.videoSink(), QSize(200,100));
+
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+ QCOMPARE(propSpy.size(), 1);
+
+ // Another frame shouldn't cause a source rect change
+ presentDummyFrame(holder.videoSink(), QSize(200,100));
+ QCOMPARE(propSpy.size(), 1);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ // Changing orientation and stretch modes should not affect this
+ videoOutput->setProperty("orientation", QVariant(90));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ videoOutput->setProperty("orientation", QVariant(180));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ videoOutput->setProperty("orientation", QVariant(270));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ videoOutput->setProperty("orientation", QVariant(-90));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::PreserveAspectCrop)));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch)));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch)));
+ updateOutputGeometry(videoOutput);
+ QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100));
+
+ delete videoOutput;
+}
+
+void tst_QQuickVideoOutput::updateOutputGeometry(QObject *output)
+{
+ // Since the object isn't visible, update() doesn't do anything
+ // so we manually force this
+ QMetaObject::invokeMethod(output, "_q_updateGeometry");
+}
+
+void tst_QQuickVideoOutput::contentRect()
+{
+ QFETCH(int, orientation);
+ QFETCH(QQuickVideoOutput::FillMode, fillMode);
+ QFETCH(QRectF, expected);
+
+ QVERIFY(m_mappingOutput);
+ m_mappingOutput->setProperty("orientation", QVariant(orientation));
+ m_mappingOutput->setProperty("fillMode", QVariant::fromValue(fillMode));
+
+ updateOutputGeometry(m_mappingOutput);
+
+ QRectF output = m_mappingOutput->property("contentRect").toRectF();
+ QCOMPARE(output, expected);
+}
+
+void tst_QQuickVideoOutput::contentRect_data()
+{
+ QTest::addColumn<int>("orientation");
+ QTest::addColumn<QQuickVideoOutput::FillMode>("fillMode");
+ QTest::addColumn<QRectF>("expected");
+
+ QQuickVideoOutput::FillMode stretch = QQuickVideoOutput::Stretch;
+ QQuickVideoOutput::FillMode fit = QQuickVideoOutput::PreserveAspectFit;
+ QQuickVideoOutput::FillMode crop = QQuickVideoOutput::PreserveAspectCrop;
+
+ // Stretch just keeps the full render rect regardless of orientation
+ QTest::newRow("s0") << 0 << stretch << QRectF(0,0,150,100);
+ QTest::newRow("s90") << 90 << stretch << QRectF(0,0,150,100);
+ QTest::newRow("s180") << 180 << stretch << QRectF(0,0,150,100);
+ QTest::newRow("s270") << 270 << stretch << QRectF(0,0,150,100);
+
+ // Fit depends on orientation
+ // Source is 200x100, fitting in 150x100 -> 150x75
+ // or 100x200 -> 50x100
+ QTest::newRow("f0") << 0 << fit << QRectF(0,12.5f,150,75);
+ QTest::newRow("f90") << 90 << fit << QRectF(50,0,50,100);
+ QTest::newRow("f180") << 180 << fit << QRectF(0,12.5,150,75);
+ QTest::newRow("f270") << 270 << fit << QRectF(50,0,50,100);
+
+ // Crop also depends on orientation, may go outside render rect
+ // 200x100 -> -25,0 200x100
+ // 100x200 -> 0,-100 150x300
+ QTest::newRow("c0") << 0 << crop << QRectF(-25,0,200,100);
+ QTest::newRow("c90") << 90 << crop << QRectF(0,-100,150,300);
+ QTest::newRow("c180") << 180 << crop << QRectF(-25,0,200,100);
+ QTest::newRow("c270") << 270 << crop << QRectF(0,-100,150,300);
+}
+
+QTEST_MAIN(tst_QQuickVideoOutput)
+
+#include "tst_qquickvideooutput.moc"
diff --git a/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt b/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt
new file mode 100644
index 000000000..e7b624c70
--- /dev/null
+++ b/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qdeclarativevideooutput_window.pro.
+
+#####################################################################
+## tst_qdeclarativevideooutput_window Test:
+#####################################################################
+
+qt_internal_add_test(tst_qquickvideooutput_window
+ SOURCES
+ tst_qquickvideooutput_window.cpp
+ INCLUDE_DIRECTORIES
+ ../../../../src/imports/multimedia
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ Qt::MultimediaQuickPrivate
+ Qt::Qml
+ Qt::Quick
+)
+
+# Resources:
+set(qml_resource_files
+ "main.qml"
+)
+
+qt_internal_add_resource(tst_qquickvideooutput_window "qml"
+ PREFIX
+ "/"
+ FILES
+ ${qml_resource_files}
+)
+
diff --git a/tests/auto/integration/qdeclarativevideooutput_window/main.qml b/tests/auto/integration/qquickvideooutput_window/main.qml
index 8866be147..40cecc762 100644
--- a/tests/auto/integration/qdeclarativevideooutput_window/main.qml
+++ b/tests/auto/integration/qquickvideooutput_window/main.qml
@@ -1,5 +1,5 @@
-import QtQuick 2.0
-import QtMultimedia 5.0
+import QtQuick
+import QtMultimedia
Item {
width: 200
diff --git a/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp b/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp
new file mode 100644
index 000000000..68f1771f9
--- /dev/null
+++ b/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp
@@ -0,0 +1,96 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2016 Research In Motion
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+//TESTED_COMPONENT=plugins/declarative/multimedia
+
+#include "private/qquickvideooutput_p.h"
+#include <QtCore/qobject.h>
+#include <QtTest/qtest.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml/qqmlcomponent.h>
+#include <QtQuick/qquickitem.h>
+#include <QtQuick/qquickview.h>
+#include <private/qplatformvideosink_p.h>
+#include <qmediaplayer.h>
+
+class QtTestVideoObject : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QtTestVideoObject()
+ : QObject(nullptr)
+ {
+ }
+};
+
+class tst_QQuickVideoOutputWindow : public QObject
+{
+ Q_OBJECT
+public:
+ tst_QQuickVideoOutputWindow()
+ : QObject(nullptr)
+ , m_sourceObject(&m_videoObject)
+ {
+ }
+
+ ~tst_QQuickVideoOutputWindow() override
+ = default;
+
+public slots:
+ void initTestCase();
+ void cleanupTestCase();
+
+private slots:
+ void aspectRatio();
+
+private:
+ QQmlEngine m_engine;
+ QQuickVideoOutput *m_videoItem;
+ QScopedPointer<QQuickItem> m_rootItem;
+ QtTestVideoObject m_videoObject;
+ QMediaPlayer m_sourceObject;
+ QQuickView m_view;
+ QVideoSink *m_sink;
+};
+
+void tst_QQuickVideoOutputWindow::initTestCase()
+{
+ QQmlComponent component(&m_engine);
+ component.loadUrl(QUrl("qrc:/main.qml"));
+
+ m_rootItem.reset(qobject_cast<QQuickItem *>(component.create()));
+ QVERIFY(m_rootItem != nullptr);
+ m_videoItem = qobject_cast<QQuickVideoOutput *>(m_rootItem->findChild<QQuickItem *>("videoOutput"));
+ QVERIFY(m_videoItem);
+ m_sink = m_videoItem->videoSink();
+ m_rootItem->setParentItem(m_view.contentItem());
+ m_sourceObject.setVideoOutput(m_videoItem);
+
+ m_view.resize(200, 200);
+ m_view.show();
+}
+
+void tst_QQuickVideoOutputWindow::cleanupTestCase()
+{
+ // Make sure that QQuickVideoOutput doesn't segfault when it is being destroyed after
+ // the service is already gone
+ m_view.setSource(QUrl());
+ m_rootItem.reset();
+}
+
+void tst_QQuickVideoOutputWindow::aspectRatio()
+{
+ m_videoItem->setProperty("fillMode", QQuickVideoOutput::Stretch);
+ QTRY_COMPARE(m_videoItem->fillMode(), QQuickVideoOutput::Stretch);
+
+ m_videoItem->setProperty("fillMode", QQuickVideoOutput::PreserveAspectFit);
+ QTRY_COMPARE(m_videoItem->fillMode(), QQuickVideoOutput::PreserveAspectFit);
+
+ m_videoItem->setProperty("fillMode", QQuickVideoOutput::PreserveAspectCrop);
+ QTRY_COMPARE(m_videoItem->fillMode(), QQuickVideoOutput::PreserveAspectCrop);
+}
+
+QTEST_MAIN(tst_QQuickVideoOutputWindow)
+
+#include "tst_qquickvideooutput_window.moc"
diff --git a/tests/auto/integration/qscreencapturebackend/BLACKLIST b/tests/auto/integration/qscreencapturebackend/BLACKLIST
new file mode 100644
index 000000000..bd6bb0e01
--- /dev/null
+++ b/tests/auto/integration/qscreencapturebackend/BLACKLIST
@@ -0,0 +1,11 @@
+macos ci
+windows ci
+
+#QTBUG-122577
+opensuse-15.5 ci
+
+#QTBUG-112827 on Android
+#QTBUG-111190, v4l2m2m issues
+[capture_capturesToFile_whenConnectedToMediaRecorder]
+linux ci
+android ci
diff --git a/tests/auto/integration/qscreencapturebackend/CMakeLists.txt b/tests/auto/integration/qscreencapturebackend/CMakeLists.txt
new file mode 100644
index 000000000..9b40642a0
--- /dev/null
+++ b/tests/auto/integration/qscreencapturebackend/CMakeLists.txt
@@ -0,0 +1,17 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qscreencapturebackend Test:
+#####################################################################
+
+qt_internal_add_test(tst_qscreencapturebackend
+ SOURCES
+ tst_qscreencapturebackend.cpp
+ LIBRARIES
+ Qt::Multimedia
+ Qt::Gui
+ Qt::Widgets
+)
+
+
diff --git a/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp b/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp
new file mode 100644
index 000000000..522d9bcdd
--- /dev/null
+++ b/tests/auto/integration/qscreencapturebackend/tst_qscreencapturebackend.cpp
@@ -0,0 +1,505 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+
+#include <qvideosink.h>
+#include <qvideoframe.h>
+#include <qmediacapturesession.h>
+#include <qpainter.h>
+#include <qscreencapture.h>
+#include <qsignalspy.h>
+#include <qmediarecorder.h>
+#include <qmediaplayer.h>
+
+#include <vector>
+
+QT_USE_NAMESPACE
+
+/*
+ This is the backend conformance test.
+
+ Since it relies on platform media framework it may be less stable.
+ Note, some of screen capture backend is not implemented or has bugs.
+ That's why some of the tests could get failed.
+ TODO: fix and platform implementations and make it stable.
+*/
+
+class QTestWidget : public QWidget
+{
+public:
+ QTestWidget(QColor firstColor, QColor secondColor)
+ : m_firstColor(firstColor), m_secondColor(secondColor)
+ {
+ }
+
+ static std::unique_ptr<QTestWidget> createAndShow(Qt::WindowFlags flags, const QRect &geometry,
+ QScreen *screen = nullptr,
+ QColor firstColor = QColor(0xFF, 0, 0),
+ QColor secondColor = QColor(0, 0, 0xFF))
+ {
+ auto widget = std::make_unique<QTestWidget>(firstColor, secondColor);
+
+ widget->setWindowTitle("Test QScreenCapture");
+ widget->setScreen(screen ? screen : QApplication::primaryScreen());
+ widget->setWindowFlags(flags);
+ widget->setGeometry(geometry);
+ widget->show();
+
+ return widget;
+ }
+
+ void setColors(QColor firstColor, QColor secondColor)
+ {
+ m_firstColor = firstColor;
+ m_secondColor = secondColor;
+ this->repaint();
+ }
+
+protected:
+ void paintEvent(QPaintEvent * /*event*/) override
+ {
+ QPainter p(this);
+ p.setPen(Qt::NoPen);
+
+ p.setBrush(m_firstColor);
+ auto rect = this->rect();
+ p.drawRect(rect);
+
+ if (m_firstColor != m_secondColor) {
+ rect.adjust(40, 50, -60, -70);
+ p.setBrush(m_secondColor);
+ p.drawRect(rect);
+ }
+ }
+
+private:
+ QColor m_firstColor;
+ QColor m_secondColor;
+};
+
+class TestVideoSink : public QVideoSink
+{
+ Q_OBJECT
+public:
+ TestVideoSink()
+ {
+ connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::videoFrameChangedSync);
+ }
+
+ void setStoreImagesEnabled(bool storeImages = true) {
+ if (storeImages)
+ connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::storeImage, Qt::UniqueConnection);
+ else
+ disconnect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::storeImage);
+ }
+
+ const std::vector<QImage> &images() const { return m_images; }
+
+ QVideoFrame waitForFrame()
+ {
+ QSignalSpy spy(this, &TestVideoSink::videoFrameChangedSync);
+ return spy.wait() ? spy.at(0).at(0).value<QVideoFrame>() : QVideoFrame{};
+ }
+
+signals:
+ void videoFrameChangedSync(QVideoFrame frame);
+
+private:
+ void storeImage(const QVideoFrame &frame) {
+ auto image = frame.toImage();
+ image.detach();
+ m_images.push_back(std::move(image));
+ }
+
+private:
+ std::vector<QImage> m_images;
+};
+
+class tst_QScreenCaptureBackend : public QObject
+{
+ Q_OBJECT
+
+ void removeWhileCapture(std::function<void(QScreenCapture &)> scModifier,
+ std::function<void()> deleter);
+
+ void capture(QTestWidget &widget, const QPoint &drawingOffset, const QSize &expectedSize,
+ std::function<void(QScreenCapture &)> scModifier);
+
+private slots:
+ void initTestCase();
+ void setActive_startsAndStopsCapture();
+ void setScreen_selectsScreen_whenCalledWithWidgetsScreen();
+ void constructor_selectsPrimaryScreenAsDefault();
+ void setScreen_selectsSecondaryScreen_whenCalledWithSecondaryScreen();
+
+ void capture_capturesToFile_whenConnectedToMediaRecorder();
+ void removeScreenWhileCapture(); // Keep the test last defined. TODO: find a way to restore
+ // application screens.
+};
+
+void tst_QScreenCaptureBackend::setActive_startsAndStopsCapture()
+{
+#ifdef Q_OS_ANDROID
+ // Should be removed after fixing QTBUG-112855
+ auto widget = QTestWidget::createAndShow(Qt::Window | Qt::FramelessWindowHint,
+ QRect{ 200, 100, 430, 351 });
+ QVERIFY(QTest::qWaitForWindowExposed(widget.get()));
+ QTest::qWait(100);
+#endif
+ TestVideoSink sink;
+ QScreenCapture sc;
+
+ QSignalSpy errorsSpy(&sc, &QScreenCapture::errorOccurred);
+ QSignalSpy activeStateSpy(&sc, &QScreenCapture::activeChanged);
+
+ QMediaCaptureSession session;
+
+ session.setScreenCapture(&sc);
+ session.setVideoSink(&sink);
+
+ QCOMPARE(activeStateSpy.size(), 0);
+ QVERIFY(!sc.isActive());
+
+ // set active true
+ {
+ sc.setActive(true);
+
+ QVERIFY(sc.isActive());
+ QCOMPARE(activeStateSpy.size(), 1);
+ QCOMPARE(activeStateSpy.front().front().toBool(), true);
+ QCOMPARE(errorsSpy.size(), 0);
+ }
+
+ // wait a bit
+ {
+ activeStateSpy.clear();
+ QTest::qWait(50);
+
+ QCOMPARE(activeStateSpy.size(), 0);
+ }
+
+ // set active false
+ {
+ sc.setActive(false);
+
+ sink.setStoreImagesEnabled(true);
+
+ QVERIFY(!sc.isActive());
+ QCOMPARE(sink.images().size(), 0u);
+ QCOMPARE(activeStateSpy.size(), 1);
+ QCOMPARE(activeStateSpy.front().front().toBool(), false);
+ QCOMPARE(errorsSpy.size(), 0);
+ }
+
+ // set active false again
+ {
+ activeStateSpy.clear();
+
+ sc.setActive(false);
+
+ QVERIFY(!sc.isActive());
+ QCOMPARE(activeStateSpy.size(), 0);
+ QCOMPARE(errorsSpy.size(), 0);
+ }
+}
+
+void tst_QScreenCaptureBackend::capture(QTestWidget &widget, const QPoint &drawingOffset,
+ const QSize &expectedSize,
+ std::function<void(QScreenCapture &)> scModifier)
+{
+ TestVideoSink sink;
+ QScreenCapture sc;
+
+ QSignalSpy errorsSpy(&sc, &QScreenCapture::errorOccurred);
+
+ if (scModifier)
+ scModifier(sc);
+
+ QMediaCaptureSession session;
+
+ session.setScreenCapture(&sc);
+ session.setVideoSink(&sink);
+
+ const auto pixelRatio = widget.devicePixelRatio();
+
+ sc.setActive(true);
+
+ QVERIFY(sc.isActive());
+
+ // In some cases, on Linux the window seems to be of a wrong color after appearance,
+ // the delay helps.
+ // TODO: remove the delay
+ QTest::qWait(300);
+
+ // Let's wait for the first frame to address a potential initialization delay.
+ // In practice, the delay varies between the platform and may randomly get increased.
+ {
+ const auto firstFrame = sink.waitForFrame();
+ QVERIFY(firstFrame.isValid());
+ }
+
+ sink.setStoreImagesEnabled();
+
+ const int delay = 200;
+
+ QTest::qWait(delay);
+ const auto expectedFramesCount =
+ delay / static_cast<int>(1000 / std::min(widget.screen()->refreshRate(), 60.));
+ const int framesCount = static_cast<int>(sink.images().size());
+ QCOMPARE_LE(framesCount, expectedFramesCount + 2);
+ QCOMPARE_GE(framesCount, 1);
+
+ for (const auto &image : sink.images()) {
+ auto pixelColor = [&drawingOffset, pixelRatio, &image](int x, int y) {
+ return image.pixelColor((QPoint(x, y) + drawingOffset) * pixelRatio).toRgb();
+ };
+ const int capturedWidth = qRound(image.size().width() / pixelRatio);
+ const int capturedHeight = qRound(image.size().height() / pixelRatio);
+ QCOMPARE(QSize(capturedWidth, capturedHeight), expectedSize);
+ QCOMPARE(pixelColor(0, 0), QColor(0xFF, 0, 0));
+
+ QCOMPARE(pixelColor(39, 50), QColor(0xFF, 0, 0));
+ QCOMPARE(pixelColor(40, 49), QColor(0xFF, 0, 0));
+
+ QCOMPARE(pixelColor(40, 50), QColor(0, 0, 0xFF));
+ }
+
+ QCOMPARE(errorsSpy.size(), 0);
+}
+
+void tst_QScreenCaptureBackend::removeWhileCapture(
+ std::function<void(QScreenCapture &)> scModifier, std::function<void()> deleter)
+{
+ QVideoSink sink;
+ QScreenCapture sc;
+
+ QSignalSpy errorsSpy(&sc, &QScreenCapture::errorOccurred);
+
+ QMediaCaptureSession session;
+
+ if (scModifier)
+ scModifier(sc);
+
+ session.setScreenCapture(&sc);
+ session.setVideoSink(&sink);
+
+ sc.setActive(true);
+
+ QTest::qWait(300);
+
+ QCOMPARE(errorsSpy.size(), 0);
+
+ if (deleter)
+ deleter();
+
+ QTest::qWait(100);
+
+ QSignalSpy framesSpy(&sink, &QVideoSink::videoFrameChanged);
+
+ QTest::qWait(100);
+
+ QCOMPARE(errorsSpy.size(), 1);
+ QCOMPARE(errorsSpy.front().front().value<QScreenCapture::Error>(),
+ QScreenCapture::CaptureFailed);
+ QVERIFY2(!errorsSpy.front().back().value<QString>().isEmpty(),
+ "Expected not empty error description");
+
+ QVERIFY2(framesSpy.empty(), "No frames expected after screen removal");
+}
+
+void tst_QScreenCaptureBackend::initTestCase()
+{
+#ifdef Q_OS_ANDROID
+ QSKIP("grabWindow() no longer supported on Android adding child windows support: QTBUG-118849");
+#endif
+#if defined(Q_OS_LINUX)
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci" &&
+ qEnvironmentVariable("XDG_SESSION_TYPE").toLower() != "x11")
+ QSKIP("Skip on wayland; to be fixed");
+#endif
+
+ if (!QApplication::primaryScreen())
+ QSKIP("No screens found");
+
+ QScreenCapture sc;
+ if (sc.error() == QScreenCapture::CapturingNotSupported)
+ QSKIP("Screen capturing not supported");
+}
+
+void tst_QScreenCaptureBackend::setScreen_selectsScreen_whenCalledWithWidgetsScreen()
+{
+ auto widget = QTestWidget::createAndShow(Qt::Window | Qt::FramelessWindowHint
+ | Qt::WindowStaysOnTopHint
+#ifdef Q_OS_ANDROID
+ | Qt::Popup
+#endif
+ ,
+ QRect{ 200, 100, 430, 351 });
+ QVERIFY(QTest::qWaitForWindowExposed(widget.get()));
+
+ capture(*widget, { 200, 100 }, widget->screen()->size(),
+ [&widget](QScreenCapture &sc) { sc.setScreen(widget->screen()); });
+}
+
+void tst_QScreenCaptureBackend::constructor_selectsPrimaryScreenAsDefault()
+{
+ auto widget = QTestWidget::createAndShow(Qt::Window | Qt::FramelessWindowHint
+ | Qt::WindowStaysOnTopHint
+#ifdef Q_OS_ANDROID
+ | Qt::Popup
+#endif
+ ,
+ QRect{ 200, 100, 430, 351 });
+ QVERIFY(QTest::qWaitForWindowExposed(widget.get()));
+
+ capture(*widget, { 200, 100 }, QApplication::primaryScreen()->size(), nullptr);
+}
+
+void tst_QScreenCaptureBackend::setScreen_selectsSecondaryScreen_whenCalledWithSecondaryScreen()
+{
+ const auto screens = QApplication::screens();
+ if (screens.size() < 2)
+ QSKIP("2 or more screens required");
+
+ auto topLeft = screens.back()->geometry().topLeft().x();
+
+ auto widgetOnSecondaryScreen = QTestWidget::createAndShow(
+ Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint,
+ QRect{ topLeft + 200, 100, 430, 351 }, screens.back());
+ QVERIFY(QTest::qWaitForWindowExposed(widgetOnSecondaryScreen.get()));
+
+ auto widgetOnPrimaryScreen = QTestWidget::createAndShow(
+ Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint,
+ QRect{ 200, 100, 430, 351 }, screens.front(), QColor(0, 0, 0), QColor(0, 0, 0));
+ QVERIFY(QTest::qWaitForWindowExposed(widgetOnPrimaryScreen.get()));
+ capture(*widgetOnSecondaryScreen, { 200, 100 }, screens.back()->size(),
+ [&screens](QScreenCapture &sc) { sc.setScreen(screens.back()); });
+}
+
+void tst_QScreenCaptureBackend::capture_capturesToFile_whenConnectedToMediaRecorder()
+{
+#ifdef Q_OS_LINUX
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116671: SKIP on linux CI to avoid crashes in ffmpeg. To be fixed.");
+#endif
+
+ // Create widget with blue color
+ auto widget = QTestWidget::createAndShow(Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint,
+ QRect{ 200, 100, 430, 351 });
+ widget->setColors(QColor(0, 0, 0xFF), QColor(0, 0, 0xFF));
+
+ QScreenCapture sc;
+ QSignalSpy errorsSpy(&sc, &QScreenCapture::errorOccurred);
+ QMediaCaptureSession session;
+ QMediaRecorder recorder;
+ session.setScreenCapture(&sc);
+ session.setRecorder(&recorder);
+ auto screen = QApplication::primaryScreen();
+ QSize screenSize = screen->geometry().size();
+ QSize videoResolution = QSize(1920, 1080);
+ recorder.setVideoResolution(videoResolution);
+ recorder.setQuality(QMediaRecorder::VeryHighQuality);
+
+ // Insert metadata
+ QMediaMetaData metaData;
+ metaData.insert(QMediaMetaData::Author, QStringLiteral("Author"));
+ metaData.insert(QMediaMetaData::Date, QDateTime::currentDateTime());
+ recorder.setMetaData(metaData);
+
+ sc.setActive(true);
+
+ QTest::qWait(1000); // wait a bit for SC threading activating
+
+ {
+ QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
+
+ recorder.record();
+
+ QTRY_VERIFY(!recorderStateChanged.empty());
+ QCOMPARE(recorder.recorderState(), QMediaRecorder::RecordingState);
+ }
+
+ QTest::qWait(1000);
+ widget->setColors(QColor(0, 0xFF, 0), QColor(0, 0xFF, 0)); // Change widget color
+ QTest::qWait(1000);
+
+ {
+ QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged);
+
+ recorder.stop();
+
+ QTRY_VERIFY(!recorderStateChanged.empty());
+ QCOMPARE(recorder.recorderState(), QMediaRecorder::StoppedState);
+ }
+
+ QString fileName = recorder.actualLocation().toLocalFile();
+ QVERIFY(!fileName.isEmpty());
+ QVERIFY(QFileInfo(fileName).size() > 0);
+
+ TestVideoSink sink;
+ QMediaPlayer player;
+ player.setSource(fileName);
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia);
+ QCOMPARE_EQ(player.metaData().value(QMediaMetaData::Resolution).toSize(),
+ QSize(videoResolution));
+ QCOMPARE_GT(player.duration(), 350);
+ QCOMPARE_LT(player.duration(), 3000);
+
+ // Convert video frames to QImages
+ player.setVideoSink(&sink);
+ sink.setStoreImagesEnabled();
+ player.setPlaybackRate(10);
+ player.play();
+ QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia);
+ const size_t framesCount = sink.images().size();
+
+ // Find pixel point at center of widget
+ int x = 415 * videoResolution.width() / screenSize.width();
+ int y = 275 * videoResolution.height() / screenSize.height();
+ auto point = QPoint(x, y);
+
+ // Verify color of first fourth of the video frames
+ for (size_t i = 0; i <= static_cast<size_t>(framesCount * 0.25); i++) {
+ QImage image = sink.images().at(i);
+ QVERIFY(!image.isNull());
+ QRgb rgb = image.pixel(point);
+// qDebug() << QStringLiteral("RGB: %1, %2, %3").arg(qRed(rgb)).arg(qGreen(rgb)).arg(qBlue(rgb));
+
+ // RGB values should be 0, 0, 255. Compensating for inaccurate video encoding.
+ QVERIFY(qRed(rgb) <= 60);
+ QVERIFY(qGreen(rgb) <= 60);
+ QVERIFY(qBlue(rgb) >= 200);
+ }
+
+ // Verify color of last fourth of the video frames
+ for (size_t i = static_cast<size_t>(framesCount * 0.75); i < framesCount - 1; i++) {
+ QImage image = sink.images().at(i);
+ QVERIFY(!image.isNull());
+ QRgb rgb = image.pixel(point);
+// qDebug() << QStringLiteral("RGB: %1, %2, %3").arg(qRed(rgb)).arg(qGreen(rgb)).arg(qBlue(rgb));
+
+ // RGB values should be 0, 255, 0. Compensating for inaccurate video encoding.
+ QVERIFY(qRed(rgb) <= 60);
+ QVERIFY(qGreen(rgb) >= 200);
+ QVERIFY(qBlue(rgb) <= 60);
+ }
+
+ QFile(fileName).remove();
+}
+
+void tst_QScreenCaptureBackend::removeScreenWhileCapture()
+{
+ QSKIP("TODO: find a reliable way to emulate it");
+
+ removeWhileCapture([](QScreenCapture &sc) { sc.setScreen(QApplication::primaryScreen()); },
+ []() {
+ // It's something that doesn't look safe but it performs required flow
+ // and allows to test the corener case.
+ delete QApplication::primaryScreen();
+ });
+}
+
+QTEST_MAIN(tst_QScreenCaptureBackend)
+
+#include "tst_qscreencapturebackend.moc"
diff --git a/tests/auto/integration/qsound/BLACKLIST b/tests/auto/integration/qsound/BLACKLIST
deleted file mode 100644
index ccb68f541..000000000
--- a/tests/auto/integration/qsound/BLACKLIST
+++ /dev/null
@@ -1,3 +0,0 @@
-[testLooping]
-opensuse-42.3
-
diff --git a/tests/auto/integration/qsound/qsound.pro b/tests/auto/integration/qsound/qsound.pro
deleted file mode 100644
index 1b552c60e..000000000
--- a/tests/auto/integration/qsound/qsound.pro
+++ /dev/null
@@ -1,13 +0,0 @@
-TARGET = tst_qsound
-
-QT += core multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-SOURCES += tst_qsound.cpp
-
-TESTDATA += test.wav
-
-RESOURCES += \
- resources.qrc
diff --git a/tests/auto/integration/qsound/resources.qrc b/tests/auto/integration/qsound/resources.qrc
deleted file mode 100644
index b54c65040..000000000
--- a/tests/auto/integration/qsound/resources.qrc
+++ /dev/null
@@ -1,5 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>test.wav</file>
- </qresource>
-</RCC>
diff --git a/tests/auto/integration/qsound/test.wav b/tests/auto/integration/qsound/test.wav
deleted file mode 100644
index e4088a973..000000000
--- a/tests/auto/integration/qsound/test.wav
+++ /dev/null
Binary files differ
diff --git a/tests/auto/integration/qsound/tst_qsound.cpp b/tests/auto/integration/qsound/tst_qsound.cpp
deleted file mode 100644
index dbf75f2e3..000000000
--- a/tests/auto/integration/qsound/tst_qsound.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-
-#include <QtTest/QtTest>
-#include <QtCore/QString>
-#include <QSound>
-#include <QSoundEffect>
-
-class tst_QSound : public QObject
-{
- Q_OBJECT
-
-public:
- tst_QSound( QObject* parent=0) : QObject(parent) {}
-
-private slots:
- void initTestCase();
- void cleanupTestCase();
- void testLooping();
- void testPlay();
- void testStop();
-
- void testPlayResource_data();
- void testPlayResource();
-
- void testStaticPlay();
-
-private:
- QSound* sound;
-};
-
-
-void tst_QSound::initTestCase()
-{
- sound = 0;
- // Only perform tests if audio device exists
- QStringList mimeTypes = QSoundEffect::supportedMimeTypes();
- if (mimeTypes.empty())
- QSKIP("No audio devices available");
-
- const QString testFileName = QStringLiteral("test.wav");
- const QString fullPath = QFINDTESTDATA(testFileName);
- QVERIFY2(!fullPath.isEmpty(), qPrintable(QStringLiteral("Unable to locate ") + testFileName));
- sound = new QSound(fullPath, this);
-
- QVERIFY(!sound->fileName().isEmpty());
- QCOMPARE(sound->loops(),1);
-}
-
-void tst_QSound::cleanupTestCase()
-{
- if (sound)
- {
- delete sound;
- sound = NULL;
- }
-}
-
-void tst_QSound::testLooping()
-{
- sound->setLoops(5);
- QCOMPARE(sound->loops(),5);
-
- sound->play();
- QVERIFY(!sound->isFinished());
-
- // test.wav is about 200ms, wait until it has finished playing 5 times
- QTRY_VERIFY(sound->isFinished());
- QCOMPARE(sound->loopsRemaining(),0);
-}
-
-void tst_QSound::testPlay()
-{
- sound->setLoops(1);
- sound->play();
- QVERIFY(!sound->isFinished());
- QTRY_VERIFY(sound->isFinished());
-}
-
-void tst_QSound::testStop()
-{
- sound->setLoops(10);
- sound->play();
- QVERIFY(!sound->isFinished());
- QTest::qWait(1000);
- sound->stop();
- QTRY_VERIFY(sound->isFinished());
-}
-
-void tst_QSound::testPlayResource_data()
-{
- QTest::addColumn<QString>("filePath");
-
- QTest::newRow("prefix :/") << ":/test.wav";
- QTest::newRow("prefix qrc:") << "qrc:test.wav";
- QTest::newRow("prefix qrc:///") << "qrc:///test.wav";
-}
-
-void tst_QSound::testPlayResource()
-{
- QFETCH(QString, filePath);
-
- QSound snd(filePath);
- snd.play();
- QVERIFY(!snd.isFinished());
- QTRY_VERIFY(snd.isFinished());
-}
-
-void tst_QSound::testStaticPlay()
-{
- // Check that you hear sound with static play also.
- const QString testFileName = QStringLiteral("test.wav");
- const QString fullPath = QFINDTESTDATA(testFileName);
- QVERIFY2(!fullPath.isEmpty(), qPrintable(QStringLiteral("Unable to locate ") + testFileName));
-
- QSound::play(fullPath);
-
- QTest::qWait(1000);
-}
-
-QTEST_MAIN(tst_QSound);
-#include "tst_qsound.moc"
diff --git a/tests/auto/integration/qsoundeffect/CMakeLists.txt b/tests/auto/integration/qsoundeffect/CMakeLists.txt
new file mode 100644
index 000000000..d403d9514
--- /dev/null
+++ b/tests/auto/integration/qsoundeffect/CMakeLists.txt
@@ -0,0 +1,20 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Generated from qsoundeffect.pro.
+
+#####################################################################
+## tst_qsoundeffect Test:
+#####################################################################
+
+qt_internal_add_test(tst_qsoundeffect
+ SOURCES
+ tst_qsoundeffect.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ TESTDATA
+ "test.wav"
+ "test_corrupted.wav"
+ "test_tone.wav"
+)
diff --git a/tests/auto/integration/qsoundeffect/qsoundeffect.pro b/tests/auto/integration/qsoundeffect/qsoundeffect.pro
deleted file mode 100644
index 868346a2e..000000000
--- a/tests/auto/integration/qsoundeffect/qsoundeffect.pro
+++ /dev/null
@@ -1,19 +0,0 @@
-TARGET = tst_qsoundeffect
-
-QT += core multimedia-private testlib
-
-# This is more of a system test
-CONFIG += testcase
-
-SOURCES += tst_qsoundeffect.cpp
-
-unix:!mac {
- !qtConfig(pulseaudio) {
- DEFINES += QT_MULTIMEDIA_QMEDIAPLAYER
- }
-}
-
-TESTDATA += test.wav
-
-RESOURCES += \
- resources.qrc
diff --git a/tests/auto/integration/qsoundeffect/resources.qrc b/tests/auto/integration/qsoundeffect/resources.qrc
deleted file mode 100644
index 24700560d..000000000
--- a/tests/auto/integration/qsoundeffect/resources.qrc
+++ /dev/null
@@ -1,8 +0,0 @@
-<RCC>
- <qresource prefix="/">
- <file>test.wav</file>
- <file>test_corrupted.wav</file>
- <file>test_tone.wav</file>
- <file>test24.wav</file>
- </qresource>
-</RCC>
diff --git a/tests/auto/integration/qsoundeffect/test24.wav b/tests/auto/integration/qsoundeffect/test24.wav
deleted file mode 100644
index 9575aaaee..000000000
--- a/tests/auto/integration/qsoundeffect/test24.wav
+++ /dev/null
Binary files differ
diff --git a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
index 7cbd57007..0d3d9f8b3 100644
--- a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
+++ b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
@@ -1,45 +1,18 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-//TESTED_COMPONENT=src/multimedia
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QtTest>
#include <QtCore/qlocale.h>
-#include <qaudiooutput.h>
-#include <qaudiodeviceinfo.h>
+#include <qaudiodevice.h>
#include <qaudio.h>
#include "qsoundeffect.h"
+#include "qmediadevices.h"
class tst_QSoundEffect : public QObject
{
Q_OBJECT
public:
- tst_QSoundEffect(QObject* parent=0) : QObject(parent) {}
+ tst_QSoundEffect(QObject* parent=nullptr) : QObject(parent) {}
public slots:
void init();
@@ -63,14 +36,12 @@ private slots:
void testSupportedMimeTypes_data();
void testSupportedMimeTypes();
void testCorruptFile();
- void testPlaying24Bits();
private:
QSoundEffect* sound;
QUrl url; // test.wav: pcm_s16le, 48000 Hz, stereo, s16
QUrl url2; // test_tone.wav: pcm_s16le, 44100 Hz, mono
QUrl urlCorrupted; // test_corrupted.wav: corrupted
- QUrl url24Bits; // test24.wav pcm_s24le, 44100 Hz, mono
};
void tst_QSoundEffect::init()
@@ -108,11 +79,6 @@ void tst_QSoundEffect::initTestCase()
QVERIFY2(!fullPath.isEmpty(), qPrintable(QStringLiteral("Unable to locate ") + testFileName));
urlCorrupted = QUrl::fromLocalFile(fullPath);
- testFileName = QStringLiteral("test24.wav");
- fullPath = QFINDTESTDATA(testFileName);
- QVERIFY2(!fullPath.isEmpty(), qPrintable(QStringLiteral("Unable to locate ") + testFileName));
- url24Bits = QUrl::fromLocalFile(fullPath);
-
sound = new QSoundEffect(this);
QVERIFY(sound->source().isEmpty());
@@ -123,18 +89,18 @@ void tst_QSoundEffect::initTestCase()
void tst_QSoundEffect::testSource()
{
- QSignalSpy readSignal(sound, SIGNAL(sourceChanged()));
+ QSignalSpy readSignal(sound, &QSoundEffect::sourceChanged);
sound->setSource(url);
sound->setVolume(0.1f);
QCOMPARE(sound->source(),url);
- QCOMPARE(readSignal.count(),1);
+ QCOMPARE(readSignal.size(),1);
QTestEventLoop::instance().enterLoop(1);
sound->play();
-
- QTest::qWait(3000);
+ QTRY_COMPARE(sound->isPlaying(), false);
+ QCOMPARE(sound->loopsRemaining(), 0);
}
void tst_QSoundEffect::testLooping()
@@ -142,24 +108,24 @@ void tst_QSoundEffect::testLooping()
sound->setSource(url);
QTRY_COMPARE(sound->status(), QSoundEffect::Ready);
- QSignalSpy readSignal_Count(sound, SIGNAL(loopCountChanged()));
- QSignalSpy readSignal_Remaining(sound, SIGNAL(loopsRemainingChanged()));
+ QSignalSpy readSignal_Count(sound, &QSoundEffect::loopCountChanged);
+ QSignalSpy readSignal_Remaining(sound, &QSoundEffect::loopsRemainingChanged);
- sound->setLoopCount(5);
+ sound->setLoopCount(3);
sound->setVolume(0.1f);
- QCOMPARE(sound->loopCount(), 5);
- QCOMPARE(readSignal_Count.count(), 1);
+ QCOMPARE(sound->loopCount(), 3);
+ QCOMPARE(readSignal_Count.size(), 1);
QCOMPARE(sound->loopsRemaining(), 0);
- QCOMPARE(readSignal_Remaining.count(), 0);
+ QCOMPARE(readSignal_Remaining.size(), 0);
sound->play();
- QVERIFY(readSignal_Remaining.count() > 0);
+ QVERIFY(readSignal_Remaining.size() > 0);
- // test.wav is about 200ms, wait until it has finished playing 5 times
+ // test.wav is about 200ms, wait until it has finished playing 3 times
QTestEventLoop::instance().enterLoop(3);
QTRY_COMPARE(sound->loopsRemaining(), 0);
- QVERIFY(readSignal_Remaining.count() >= 6);
+ QVERIFY(readSignal_Remaining.size() == 4);
QTRY_VERIFY(!sound->isPlaying());
// QTBUG-36643 (setting the loop count while playing should work)
@@ -167,31 +133,31 @@ void tst_QSoundEffect::testLooping()
readSignal_Count.clear();
readSignal_Remaining.clear();
- sound->setLoopCount(30);
- QCOMPARE(sound->loopCount(), 30);
- QCOMPARE(readSignal_Count.count(), 1);
+ sound->setLoopCount(10);
+ QCOMPARE(sound->loopCount(), 10);
+ QCOMPARE(readSignal_Count.size(), 1);
QCOMPARE(sound->loopsRemaining(), 0);
- QCOMPARE(readSignal_Remaining.count(), 0);
+ QCOMPARE(readSignal_Remaining.size(), 0);
sound->play();
- QVERIFY(readSignal_Remaining.count() > 0);
+ QVERIFY(readSignal_Remaining.size() > 0);
// wait for the sound to be played several times
- QTRY_VERIFY(sound->loopsRemaining() <= 20);
- QVERIFY(readSignal_Remaining.count() >= 10);
+ QTRY_VERIFY(sound->loopsRemaining() <= 7);
+ QVERIFY(readSignal_Remaining.size() >= 3);
readSignal_Count.clear();
readSignal_Remaining.clear();
// change the loop count while playing
- sound->setLoopCount(5);
- QCOMPARE(sound->loopCount(), 5);
- QCOMPARE(readSignal_Count.count(), 1);
- QCOMPARE(sound->loopsRemaining(), 5);
- QCOMPARE(readSignal_Remaining.count(), 1);
+ sound->setLoopCount(3);
+ QCOMPARE(sound->loopCount(), 3);
+ QCOMPARE(readSignal_Count.size(), 1);
+ QCOMPARE(sound->loopsRemaining(), 3);
+ QCOMPARE(readSignal_Remaining.size(), 1);
// wait for all the loops to be completed
QTRY_COMPARE(sound->loopsRemaining(), 0);
- QTRY_VERIFY(readSignal_Remaining.count() >= 6);
+ QTRY_VERIFY(readSignal_Remaining.size() == 4);
QTRY_VERIFY(!sound->isPlaying());
}
@@ -201,15 +167,15 @@ void tst_QSoundEffect::testLooping()
sound->setLoopCount(QSoundEffect::Infinite);
QCOMPARE(sound->loopCount(), int(QSoundEffect::Infinite));
- QCOMPARE(readSignal_Count.count(), 1);
+ QCOMPARE(readSignal_Count.size(), 1);
QCOMPARE(sound->loopsRemaining(), 0);
- QCOMPARE(readSignal_Remaining.count(), 0);
+ QCOMPARE(readSignal_Remaining.size(), 0);
sound->play();
QTRY_COMPARE(sound->loopsRemaining(), int(QSoundEffect::Infinite));
- QCOMPARE(readSignal_Remaining.count(), 1);
+ QCOMPARE(readSignal_Remaining.size(), 1);
- QTest::qWait(1500);
+ QTest::qWait(500);
QVERIFY(sound->isPlaying());
readSignal_Count.clear();
readSignal_Remaining.clear();
@@ -217,36 +183,34 @@ void tst_QSoundEffect::testLooping()
// Setting the loop count to 0 should play it one last time
sound->setLoopCount(0);
QCOMPARE(sound->loopCount(), 1);
- QCOMPARE(readSignal_Count.count(), 1);
+ QCOMPARE(readSignal_Count.size(), 1);
QCOMPARE(sound->loopsRemaining(), 1);
- QCOMPARE(readSignal_Remaining.count(), 1);
+ QCOMPARE(readSignal_Remaining.size(), 1);
QTRY_COMPARE(sound->loopsRemaining(), 0);
- QTRY_VERIFY(readSignal_Remaining.count() >= 2);
+ QTRY_VERIFY(readSignal_Remaining.size() >= 2);
QTRY_VERIFY(!sound->isPlaying());
}
}
void tst_QSoundEffect::testVolume()
{
- QSignalSpy readSignal(sound, SIGNAL(volumeChanged()));
+ QSignalSpy readSignal(sound, &QSoundEffect::volumeChanged);
sound->setVolume(0.5);
QCOMPARE(sound->volume(),0.5);
- QTest::qWait(20);
- QCOMPARE(readSignal.count(),1);
+ QTRY_COMPARE(readSignal.size(),1);
}
void tst_QSoundEffect::testMuting()
{
- QSignalSpy readSignal(sound, SIGNAL(mutedChanged()));
+ QSignalSpy readSignal(sound, &QSoundEffect::mutedChanged);
sound->setMuted(true);
QCOMPARE(sound->isMuted(),true);
- QTest::qWait(20);
- QCOMPARE(readSignal.count(),1);
+ QTRY_COMPARE(readSignal.size(),1);
}
void tst_QSoundEffect::testPlaying()
@@ -309,7 +273,7 @@ void tst_QSoundEffect::testDestroyWhilePlaying()
instance->setVolume(0.1f);
QTestEventLoop::instance().enterLoop(1);
instance->play();
- QTest::qWait(500);
+ QTest::qWait(100);
delete instance;
QTestEventLoop::instance().enterLoop(1);
}
@@ -321,7 +285,7 @@ void tst_QSoundEffect::testDestroyWhileRestartPlaying()
instance->setVolume(0.1f);
QTestEventLoop::instance().enterLoop(1);
instance->play();
- QTest::qWait(1000);
+ QTRY_COMPARE(instance->isPlaying(), false);
//restart playing
instance->play();
delete instance;
@@ -330,7 +294,7 @@ void tst_QSoundEffect::testDestroyWhileRestartPlaying()
void tst_QSoundEffect::testSetSourceWhileLoading()
{
- for (int i = 0; i < 10; i++) {
+ for (int i = 0; i < 2; i++) {
sound->setSource(url);
QVERIFY(sound->status() == QSoundEffect::Loading || sound->status() == QSoundEffect::Ready);
sound->setSource(url); // set same source again
@@ -358,7 +322,7 @@ void tst_QSoundEffect::testSetSourceWhileLoading()
void tst_QSoundEffect::testSetSourceWhilePlaying()
{
- for (int i = 0; i < 10; i++) {
+ for (int i = 0; i < 2; i++) {
sound->setSource(url);
QTRY_COMPARE(sound->status(), QSoundEffect::Ready);
sound->play();
@@ -392,9 +356,9 @@ void tst_QSoundEffect::testSupportedMimeTypes_data()
{
// Verify also passing of audio device info as parameter
QTest::addColumn<QSoundEffect*>("instance");
- QTest::newRow("without QAudioDeviceInfo") << sound;
- QAudioDeviceInfo deviceInfo(QAudioDeviceInfo::defaultOutputDevice());
- QTest::newRow("with QAudioDeviceInfo") << new QSoundEffect(deviceInfo, this);
+ QTest::newRow("without QAudioDevice") << sound;
+ QAudioDevice deviceInfo(QMediaDevices::defaultAudioOutput());
+ QTest::newRow("with QAudioDevice") << new QSoundEffect(deviceInfo, this);
}
void tst_QSoundEffect::testSupportedMimeTypes()
@@ -410,13 +374,19 @@ void tst_QSoundEffect::testSupportedMimeTypes()
void tst_QSoundEffect::testCorruptFile()
{
+ using namespace Qt::Literals;
+ auto expectedMessagePattern =
+ QRegularExpression(uR"(^QSoundEffect\(qaudio\): Error decoding source .*$)"_s);
+
for (int i = 0; i < 10; i++) {
- QSignalSpy statusSpy(sound, SIGNAL(statusChanged()));
+ QSignalSpy statusSpy(sound, &QSoundEffect::statusChanged);
+ QTest::ignoreMessage(QtMsgType::QtWarningMsg, expectedMessagePattern);
+
sound->setSource(urlCorrupted);
QVERIFY(!sound->isPlaying());
QVERIFY(sound->status() == QSoundEffect::Loading || sound->status() == QSoundEffect::Error);
QTRY_COMPARE(sound->status(), QSoundEffect::Error);
- QCOMPARE(statusSpy.count(), 2);
+ QCOMPARE(statusSpy.size(), 2);
sound->play();
QVERIFY(!sound->isPlaying());
@@ -427,26 +397,6 @@ void tst_QSoundEffect::testCorruptFile()
}
}
-void tst_QSoundEffect::testPlaying24Bits()
-{
- sound->setLoopCount(QSoundEffect::Infinite);
- sound->setSource(url24Bits);
- QTestEventLoop::instance().enterLoop(1);
- sound->play();
- QTestEventLoop::instance().enterLoop(1);
- QTRY_COMPARE(sound->isPlaying(), true);
- sound->stop();
-
- QSignalSpy readSignal(sound, SIGNAL(volumeChanged()));
- sound->setVolume(0.5);
- QCOMPARE(sound->volume(), 0.5);
- sound->play();
- QTestEventLoop::instance().enterLoop(1);
- QTRY_COMPARE(sound->isPlaying(), true);
- QCOMPARE(readSignal.count(), 1);
- sound->stop();
-}
-
QTEST_MAIN(tst_QSoundEffect)
#include "tst_qsoundeffect.moc"
diff --git a/tests/auto/integration/qvideoframebackend/CMakeLists.txt b/tests/auto/integration/qvideoframebackend/CMakeLists.txt
new file mode 100644
index 000000000..e4fa79201
--- /dev/null
+++ b/tests/auto/integration/qvideoframebackend/CMakeLists.txt
@@ -0,0 +1,26 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qvideoframebackend Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ testdata/*)
+list(APPEND testdata_resource_files ${test_data_glob})
+
+qt_internal_add_test(tst_qvideoframebackend
+ SOURCES
+ ../shared/mediafileselector.h
+ ../shared/testvideosink.h
+ tst_qvideoframebackend.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::MultimediaPrivate
+ BUILTIN_TESTDATA
+ TESTDATA ${testdata_resource_files}
+ INCLUDE_DIRECTORIES
+ ../shared/
+)
diff --git a/tests/auto/integration/qvideoframebackend/testdata/colors.mp4 b/tests/auto/integration/qvideoframebackend/testdata/colors.mp4
new file mode 100644
index 000000000..30ddda8b0
--- /dev/null
+++ b/tests/auto/integration/qvideoframebackend/testdata/colors.mp4
Binary files differ
diff --git a/tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4 b/tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4
new file mode 100644
index 000000000..6b67a3433
--- /dev/null
+++ b/tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4
Binary files differ
diff --git a/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp b/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp
new file mode 100644
index 000000000..590f57160
--- /dev/null
+++ b/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp
@@ -0,0 +1,258 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtTest/QtTest>
+#include <qmediaplayer.h>
+#include <qvideoframe.h>
+#include <qdebug.h>
+
+#include "mediafileselector.h"
+#include "testvideosink.h"
+#include "private/qvideotexturehelper_p.h"
+#include "private/qvideowindow_p.h"
+#include <thread>
+
+
+QT_USE_NAMESPACE
+
+class tst_QVideoFrameBackend : public QObject
+{
+ Q_OBJECT
+
+public slots:
+ void initTestCase();
+ void init() { }
+ void cleanup() { }
+
+private slots:
+ void testMediaFilesAreSupported();
+
+ void toImage_retainsThePreviousMappedState_data();
+ void toImage_retainsThePreviousMappedState();
+
+ void toImage_rendersUpdatedFrame_afterMappingInWriteModeAndModifying_data();
+ void toImage_rendersUpdatedFrame_afterMappingInWriteModeAndModifying();
+
+ void toImage_returnsImage_whenCalledFromSeparateThreadAndWhileRenderingToWindow();
+
+private:
+ QVideoFrame createDefaultFrame() const;
+
+ QVideoFrame createMediaPlayerFrame() const;
+
+ using FrameCreator = decltype(&tst_QVideoFrameBackend::createDefaultFrame);
+
+ template <typename F>
+ void addMediaPlayerFrameTestData(F &&f);
+
+private:
+ MaybeUrl m_oneRedFrameVideo = QUnexpect{};
+ MaybeUrl m_colorsVideo = QUnexpect{};
+ MediaFileSelector m_mediaSelector;
+};
+
+QVideoFrame tst_QVideoFrameBackend::createDefaultFrame() const
+{
+ return QVideoFrame(QVideoFrameFormat(QSize(10, 20), QVideoFrameFormat::Format_ARGB8888));
+}
+
+QVideoFrame tst_QVideoFrameBackend::createMediaPlayerFrame() const
+{
+ if (!m_oneRedFrameVideo)
+ return {};
+
+ TestVideoSink sink;
+ QMediaPlayer player;
+
+ player.setVideoOutput(&sink);
+ player.setSource(*m_oneRedFrameVideo);
+
+ player.play();
+
+ return sink.waitForFrame();
+}
+
+template <typename F>
+void tst_QVideoFrameBackend::addMediaPlayerFrameTestData(F &&f)
+{
+ if (!m_oneRedFrameVideo) {
+ qWarning() << "Skipping test data with mediaplayer as the source cannot be open."
+ "\nSee the test case 'testMediaFilesAreSupported' for details";
+ return;
+ }
+
+ f();
+}
+
+void tst_QVideoFrameBackend::initTestCase()
+{
+#ifdef Q_OS_ANDROID
+ qWarning() << "Skip media selection, QTBUG-118571";
+ return;
+#endif
+
+ m_oneRedFrameVideo = m_mediaSelector.select("qrc:/testdata/one_red_frame.mp4");
+ m_colorsVideo = m_mediaSelector.select("qrc:/testdata/colors.mp4");
+}
+
+void tst_QVideoFrameBackend::testMediaFilesAreSupported()
+{
+#ifdef Q_OS_ANDROID
+ QSKIP("Skip test cases with mediaPlayerFrame on Android CI, because of QTBUG-118571");
+#endif
+
+ QCOMPARE(m_mediaSelector.dumpErrors(), "");
+}
+
+void tst_QVideoFrameBackend::toImage_retainsThePreviousMappedState_data()
+{
+ QTest::addColumn<FrameCreator>("frameCreator");
+ QTest::addColumn<QtVideo::MapMode>("initialMapMode");
+
+ // clang-format off
+ QTest::addRow("defaulFrame.notMapped") << &tst_QVideoFrameBackend::createDefaultFrame
+ << QtVideo::MapMode::NotMapped;
+ QTest::addRow("defaulFrame.readOnly") << &tst_QVideoFrameBackend::createDefaultFrame
+ << QtVideo::MapMode::ReadOnly;
+
+ addMediaPlayerFrameTestData([]()
+ {
+ QTest::addRow("mediaPlayerFrame.notMapped")
+ << &tst_QVideoFrameBackend::createMediaPlayerFrame
+ << QtVideo::MapMode::NotMapped;
+ QTest::addRow("mediaPlayerFrame.readOnly")
+ << &tst_QVideoFrameBackend::createMediaPlayerFrame
+ << QtVideo::MapMode::ReadOnly;
+ });
+
+ // clang-format on
+}
+
+void tst_QVideoFrameBackend::toImage_retainsThePreviousMappedState()
+{
+ QFETCH(const FrameCreator, frameCreator);
+ QFETCH(const QtVideo::MapMode, initialMapMode);
+ const bool initiallyMapped = initialMapMode != QtVideo::MapMode::NotMapped;
+
+ QVideoFrame frame = std::invoke(frameCreator, this);
+ QVERIFY(frame.isValid());
+
+ frame.map(initialMapMode);
+ QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), initialMapMode);
+
+ QImage image = frame.toImage();
+ QVERIFY(!image.isNull());
+
+ QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), initialMapMode);
+ QCOMPARE(frame.isMapped(), initiallyMapped);
+}
+
+void tst_QVideoFrameBackend::toImage_rendersUpdatedFrame_afterMappingInWriteModeAndModifying_data()
+{
+ QTest::addColumn<FrameCreator>("frameCreator");
+ QTest::addColumn<QtVideo::MapMode>("mapMode");
+
+ // clang-format off
+ QTest::addRow("defaulFrame.writeOnly") << &tst_QVideoFrameBackend::createDefaultFrame
+ << QtVideo::MapMode::WriteOnly;
+ QTest::addRow("defaulFrame.readWrite") << &tst_QVideoFrameBackend::createDefaultFrame
+ << QtVideo::MapMode::ReadWrite;
+
+ addMediaPlayerFrameTestData([]()
+ {
+ QTest::addRow("mediaPlayerFrame.writeOnly")
+ << &tst_QVideoFrameBackend::createMediaPlayerFrame
+ << QtVideo::MapMode::WriteOnly;
+ QTest::addRow("mediaPlayerFrame.readWrite")
+ << &tst_QVideoFrameBackend::createMediaPlayerFrame
+ << QtVideo::MapMode::ReadWrite;
+ });
+ // clang-format on
+}
+
+void tst_QVideoFrameBackend::toImage_rendersUpdatedFrame_afterMappingInWriteModeAndModifying()
+{
+ QFETCH(const FrameCreator, frameCreator);
+ QFETCH(const QtVideo::MapMode, mapMode);
+
+ // Arrange
+
+ QVideoFrame frame = std::invoke(frameCreator, this);
+ QVERIFY(frame.isValid());
+
+ QImage originalImage = frame.toImage();
+ QVERIFY(!originalImage.isNull());
+
+ // Act: map the frame in write mode and change the top level pixel
+ frame.map(mapMode);
+ QVERIFY(frame.isWritable());
+
+ QCOMPARE_NE(frame.pixelFormat(), QVideoFrameFormat::Format_Invalid);
+
+ const QVideoTextureHelper::TextureDescription *textureDescription =
+ QVideoTextureHelper::textureDescription(frame.pixelFormat());
+ QVERIFY(textureDescription);
+
+ uchar *firstPlaneBits = frame.bits(0);
+ QVERIFY(firstPlaneBits);
+
+ for (int i = 0; i < textureDescription->strideFactor; ++i)
+ firstPlaneBits[i] = ~firstPlaneBits[i];
+
+ frame.unmap();
+
+ // get an image from modified frame
+ QImage modifiedImage = frame.toImage();
+
+ // Assert
+
+ QVERIFY(!frame.isMapped());
+ QCOMPARE_NE(originalImage.pixel(0, 0), modifiedImage.pixel(0, 0));
+ QCOMPARE(originalImage.pixel(1, 0), modifiedImage.pixel(1, 0));
+ QCOMPARE(originalImage.pixel(1, 1), modifiedImage.pixel(1, 1));
+}
+
+void tst_QVideoFrameBackend::toImage_returnsImage_whenCalledFromSeparateThreadAndWhileRenderingToWindow()
+{
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") {
+#ifdef Q_OS_MACOS
+ QSKIP("SKIP on macOS because of crash and error \"Failed to create QWindow::MetalSurface. Metal is not supported by any of the GPUs in this system.\"");
+#elif defined(Q_OS_ANDROID)
+ QSKIP("SKIP initTestCase on CI, because of QTBUG-118571");
+#endif
+ }
+ // Arrange
+ QVideoWindow window;
+ window.show();
+
+ QVERIFY(QTest::qWaitForWindowExposed(&window));
+
+ QMediaPlayer player;
+ player.setVideoOutput(&window);
+
+ const QVideoSink *sink = window.videoSink();
+ std::vector<QImage> images;
+
+ // act
+ connect(sink, &QVideoSink::videoFrameChanged, sink, [&](const QVideoFrame &frame) {
+
+ // Run toImage on separate thread to exercise special code path
+ QImage image;
+ auto t = std::thread([&] { image = frame.toImage(); });
+ t.join();
+
+ if (!image.isNull())
+ images.push_back(image);
+ });
+
+ // Arrange some more
+ player.setSource(*m_colorsVideo);
+ player.setLoops(10);
+ player.play();
+
+ // assert
+ QTRY_COMPARE_GE_WITH_TIMEOUT(images.size(), 10u, std::chrono::seconds(60) );
+}
+
+QTEST_MAIN(tst_QVideoFrameBackend)
+#include "tst_qvideoframebackend.moc"
diff --git a/tests/auto/integration/qwindowcapturebackend/BLACKLIST b/tests/auto/integration/qwindowcapturebackend/BLACKLIST
new file mode 100644
index 000000000..bc176cf98
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/BLACKLIST
@@ -0,0 +1,13 @@
+macos ci
+
+#QTBUG-112827 on Android
+#QTBUG-111190, v4l2m2m issues
+[recorder_encodesFrames_toValidMediaFile]
+linux ci
+android ci
+
+#QTBUG-112827 on Android
+#QTBUG-111190, v4l2m2m issues
+[recorder_encodesFrames_toValidMediaFile_whenWindowResizes]
+linux ci
+android ci
diff --git a/tests/auto/integration/qwindowcapturebackend/CMakeLists.txt b/tests/auto/integration/qwindowcapturebackend/CMakeLists.txt
new file mode 100644
index 000000000..8f633a1da
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/CMakeLists.txt
@@ -0,0 +1,20 @@
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+qt_internal_add_test(tst_qwindowcapturebackend
+ SOURCES
+ tst_qwindowcapturebackend.cpp
+ widget.h
+ widget.cpp
+ grabber.h
+ grabber.cpp
+ fixture.h
+ fixture.cpp
+ LIBRARIES
+ Qt::Multimedia
+ Qt::Gui
+ Qt::Widgets
+ Qt::MultimediaWidgets
+)
+
+
diff --git a/tests/auto/integration/qwindowcapturebackend/fixture.cpp b/tests/auto/integration/qwindowcapturebackend/fixture.cpp
new file mode 100644
index 000000000..ee130e294
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/fixture.cpp
@@ -0,0 +1,234 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "fixture.h"
+
+#include <qmediaplayer.h>
+#include <qvideowidget.h>
+#include <qsystemsemaphore.h>
+#include <quuid.h>
+
+DisableCursor::DisableCursor()
+{
+ QCursor cursor(Qt::BlankCursor);
+ QApplication::setOverrideCursor(cursor);
+}
+
+DisableCursor::~DisableCursor()
+{
+ QGuiApplication::restoreOverrideCursor();
+}
+
+WindowCaptureFixture::WindowCaptureFixture()
+{
+ m_session.setWindowCapture(&m_capture);
+ m_session.setVideoSink(&m_grabber);
+}
+
+QString WindowCaptureFixture::getResultsPath(const QString &fileName)
+{
+ const QString sep = QStringLiteral("--");
+
+ QString stem = QCoreApplication::applicationName();
+ if (const char *currentTest = QTest::currentTestFunction())
+ stem += sep + QString::fromLatin1(currentTest);
+
+ if (const char *currentTag = QTest::currentDataTag())
+ stem += sep + QString::fromLatin1(currentTag);
+
+ stem += sep + fileName;
+
+ const QDir resultsDir = qEnvironmentVariable("COIN_CTEST_RESULTSDIR", QDir::tempPath());
+
+ return resultsDir.filePath(stem);
+}
+
+bool WindowCaptureFixture::compareImages(QImage actual, const QImage &expected,
+ const QString &fileSuffix)
+{
+ // Convert to same format so that we can compare images
+ actual = actual.convertToFormat(expected.format());
+
+ if (actual == expected)
+ return true;
+
+ qWarning() << "Image comparison failed.";
+ qWarning() << "Actual image:";
+ qWarning() << actual;
+ qWarning() << "Expected image:";
+ qWarning() << expected;
+
+ const QString actualName = getResultsPath(QStringLiteral("actual%1.png").arg(fileSuffix));
+ if (!actual.save(actualName))
+ qWarning() << "Failed to save actual file to " << actualName;
+
+ const QString expectedName = getResultsPath(QStringLiteral("expected%1.png").arg(fileSuffix));
+ if (!expected.save(expectedName))
+ qWarning() << "Failed to save expected file to " << expectedName;
+
+ return false;
+}
+
+bool WindowCaptureWithWidgetFixture::start(QSize size)
+{
+ // In case of window capture failure, signal the grabber so we can stop
+ // waiting for frames that will never come.
+ connect(&m_capture, &QWindowCapture::errorOccurred, &m_grabber, &FrameGrabber::stop);
+
+ m_widget.setSize(size);
+
+ m_widget.show();
+
+ // Make sure window is in a state that allows it to be found by QWindowCapture.
+ // Not necessary on Windows, but seems to be necessary on some platforms.
+ if (!QTest::qWaitForWindowExposed(&m_widget, static_cast<int>(s_testTimeout.count()))) {
+ qWarning() << "Failed to display widget within timeout";
+ return false;
+ }
+
+ m_captureWindow = findCaptureWindow(m_widget.windowTitle());
+
+ if (!m_captureWindow.isValid())
+ return false;
+
+ m_capture.setWindow(m_captureWindow);
+ m_capture.setActive(true);
+
+ return true;
+}
+
+QVideoFrame WindowCaptureWithWidgetFixture::waitForFrame(qint64 noOlderThanTime)
+{
+ const std::vector<QVideoFrame> frames = m_grabber.waitAndTakeFrames(1u, noOlderThanTime);
+ if (frames.empty())
+ return QVideoFrame{};
+
+ return frames.back();
+}
+
+QCapturableWindow WindowCaptureWithWidgetFixture::findCaptureWindow(const QString &windowTitle)
+{
+ QList<QCapturableWindow> allWindows = QWindowCapture::capturableWindows();
+
+ const auto window = std::find_if(allWindows.begin(), allWindows.end(),
+ [windowTitle](const QCapturableWindow &win) {
+ return win.description() == windowTitle;
+ });
+
+ // Extra debug output to help understanding if test widget window could not be found
+ if (window == allWindows.end()) {
+ qDebug() << "Could not find window" << windowTitle << ". Existing capturable windows:";
+ std::for_each(allWindows.begin(), allWindows.end(), [](const QCapturableWindow &win) {
+ qDebug() << " " << win.description();
+ });
+ return QCapturableWindow{};
+ }
+
+ return *window;
+}
+
+void WindowCaptureWithWidgetAndRecorderFixture::start(QSize size, bool togglePattern)
+{
+ if (togglePattern) {
+ // Drive animation
+ connect(&m_grabber, &FrameGrabber::videoFrameChanged, &m_widget,
+ &TestWidget::togglePattern);
+ }
+
+ connect(&m_recorder, &QMediaRecorder::recorderStateChanged, this,
+ &WindowCaptureWithWidgetAndRecorderFixture::recorderStateChanged);
+
+ m_session.setRecorder(&m_recorder);
+ m_recorder.setQuality(QMediaRecorder::HighQuality);
+ m_recorder.setOutputLocation(QUrl::fromLocalFile(m_mediaFile));
+ m_recorder.setVideoResolution(size);
+
+ WindowCaptureWithWidgetFixture::start(size);
+
+ m_recorder.record();
+}
+
+bool WindowCaptureWithWidgetAndRecorderFixture::stop()
+{
+ m_recorder.stop();
+
+ const auto recorderStopped = [this] { return m_recorderState == QMediaRecorder::StoppedState; };
+
+ return QTest::qWaitFor(recorderStopped, s_testTimeout);
+}
+
+bool WindowCaptureWithWidgetAndRecorderFixture::testVideoFilePlayback(const QString &fileName)
+{
+ QVideoWidget widget;
+
+ QMediaPlayer player;
+
+ bool playing = true;
+ connect(&player, &QMediaPlayer::playbackStateChanged, this,
+ [&](QMediaPlayer::PlaybackState state) {
+ if (state == QMediaPlayer::StoppedState)
+ playing = false;
+ });
+
+ QMediaPlayer::Error error = QMediaPlayer::NoError;
+ connect(&player, &QMediaPlayer::errorOccurred, this,
+ [&](QMediaPlayer::Error e, const QString &errorString) {
+ error = e;
+ qWarning() << errorString;
+ });
+
+ player.setSource(QUrl{ fileName });
+ player.setVideoOutput(&widget);
+ widget.show();
+ player.play();
+
+ const bool completed = QTest::qWaitFor(
+ [&] { return !playing || error != QMediaPlayer::NoError; }, s_testTimeout);
+
+ return completed && error == QMediaPlayer::NoError;
+}
+
+void WindowCaptureWithWidgetAndRecorderFixture::recorderStateChanged(
+ QMediaRecorder::RecorderState state)
+{
+ m_recorderState = state;
+}
+
+bool WindowCaptureWithWidgetInOtherProcessFixture::start()
+{
+ // In case of window capture failure, signal the grabber so we can stop
+ // waiting for frames that will never come.
+ connect(&m_capture, &QWindowCapture::errorOccurred, &m_grabber, &FrameGrabber::stop);
+
+ // Create a new window title that is also used as a semaphore key with less than 30 characters
+ const QString windowTitle = QString::number(qHash(QUuid::createUuid().toString()));
+
+ QSystemSemaphore windowVisible{ QNativeIpcKey{ windowTitle } };
+
+ // Start another instance of the test executable and ask it to show a
+ // its test widget.
+ m_windowProcess.setArguments({ "--show", windowTitle });
+ m_windowProcess.setProgram(QApplication::applicationFilePath());
+ m_windowProcess.start();
+
+ // Make sure window is in a state that allows it to be found by QWindowCapture.
+ // We do this by waiting for the process to release the semaphore once its window is visible
+ windowVisible.acquire();
+
+ m_captureWindow = findCaptureWindow(windowTitle);
+
+ if (!m_captureWindow.isValid())
+ return false;
+
+ // Start capturing the out-of-process window
+ m_capture.setWindow(m_captureWindow);
+ m_capture.setActive(true);
+
+ // Show in-process widget used to create a reference image
+ m_widget.show();
+
+ return true;
+}
+
+
+#include "moc_fixture.cpp"
diff --git a/tests/auto/integration/qwindowcapturebackend/fixture.h b/tests/auto/integration/qwindowcapturebackend/fixture.h
new file mode 100644
index 000000000..2f72c3468
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/fixture.h
@@ -0,0 +1,147 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef WINDOW_CAPTURE_FIXTURE_H
+#define WINDOW_CAPTURE_FIXTURE_H
+
+#include "grabber.h"
+#include "widget.h"
+
+#include <chrono>
+#include <qmediacapturesession.h>
+#include <qmediarecorder.h>
+#include <qobject.h>
+#include <qsignalspy.h>
+#include <qtest.h>
+#include <qvideoframe.h>
+#include <qwindowcapture.h>
+#include <qprocess.h>
+
+QT_USE_NAMESPACE
+
+constexpr inline std::chrono::milliseconds s_testTimeout = std::chrono::seconds(60);
+
+/*!
+ Utility used to hide application cursor for image comparison tests.
+ On Windows, the mouse cursor is captured as part of the window capture.
+ This and can cause differences when comparing captured images with images
+ from QWindow::grab() which is used as a reference.
+*/
+struct DisableCursor final
+{
+ DisableCursor();
+ ~DisableCursor();
+
+ DisableCursor(const DisableCursor &) = delete;
+ DisableCursor &operator=(const DisableCursor &) = delete;
+};
+
+/*!
+ Fixture class that orchestrates setup/teardown of window capturing
+*/
+class WindowCaptureFixture : public QObject
+{
+ Q_OBJECT
+
+public:
+ WindowCaptureFixture();
+
+ /*!
+ Compare two images, ignoring format.
+ If images differ, diagnostic output is logged and images are saved to file.
+ */
+ static bool compareImages(QImage actual, const QImage &expected,
+ const QString &fileSuffix = "");
+
+ QMediaCaptureSession m_session;
+ QWindowCapture m_capture;
+ FrameGrabber m_grabber;
+
+ QSignalSpy m_errors{ &m_capture, &QWindowCapture::errorOccurred };
+ QSignalSpy m_activations{ &m_capture, &QWindowCapture::activeChanged };
+
+private:
+ /*!
+ Calculate a result path based upon a single filename.
+ On CI, the file will be located in COIN_CTEST_RESULTSDIR, and on developer
+ computers, the file will be located in TEMP.
+
+ The file name is on the form "testCase_testFunction_[dataTag_]fileName"
+ */
+ static QString getResultsPath(const QString &fileName);
+};
+
+/*!
+ Fixture class that extends window capture fixture with a capturable widget
+*/
+class WindowCaptureWithWidgetFixture : public WindowCaptureFixture
+{
+ Q_OBJECT
+
+public:
+ /*!
+ Starts capturing and returns true if successful.
+
+ Two phase initialization is used to be able to detect
+ failure to find widget window as a capturable window.
+ */
+ bool start(QSize size = { 60, 40 });
+
+ /*!
+ Waits until the a captured frame is received and returns it
+ */
+ QVideoFrame waitForFrame(qint64 noOlderThanTime = 0);
+
+ DisableCursor m_cursorDisabled; // Avoid mouse cursor causing image differences
+ TestWidget m_widget;
+ QCapturableWindow m_captureWindow;
+
+protected:
+ static QCapturableWindow findCaptureWindow(const QString &windowTitle);
+};
+
+class WindowCaptureWithWidgetInOtherProcessFixture : public WindowCaptureWithWidgetFixture
+{
+ Q_OBJECT
+
+public:
+ ~WindowCaptureWithWidgetInOtherProcessFixture() { m_windowProcess.close(); }
+
+ /*!
+ Create widget in separate process and start capturing its content
+ */
+ bool start();
+
+ QProcess m_windowProcess;
+};
+
+class WindowCaptureWithWidgetAndRecorderFixture : public WindowCaptureWithWidgetFixture
+{
+ Q_OBJECT
+
+public:
+ void start(QSize size = { 60, 40 }, bool togglePattern = true);
+
+ /*!
+ Stop recording.
+
+ Since recorder finalizes the file asynchronously, even after destructors are called,
+ we need to explicitly wait for the stopped state before ending the test. If we don't
+ do this, the media file can not be deleted by the QTemporaryDir at destruction.
+ */
+ bool stop();
+
+ bool testVideoFilePlayback(const QString& fileName);
+
+public slots:
+ void recorderStateChanged(QMediaRecorder::RecorderState state);
+
+public:
+ QTemporaryDir m_tempDir;
+ const QString m_mediaFile = m_tempDir.filePath("test.mp4");
+ QMediaRecorder m_recorder;
+ QMediaRecorder::RecorderState m_recorderState = QMediaRecorder::StoppedState;
+ QSignalSpy m_recorderErrors{ &m_recorder, &QMediaRecorder::errorOccurred };
+};
+
+#endif
diff --git a/tests/auto/integration/qwindowcapturebackend/grabber.cpp b/tests/auto/integration/qwindowcapturebackend/grabber.cpp
new file mode 100644
index 000000000..a7b72aeef
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/grabber.cpp
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "grabber.h"
+#include "fixture.h"
+
+#include <qtest.h>
+#include <qvideoframe.h>
+
+FrameGrabber::FrameGrabber()
+{
+ const auto copyFrame = [this](const QVideoFrame &frame) { m_frames.push_back(frame); };
+
+ connect(this, &QVideoSink::videoFrameChanged, this, copyFrame, Qt::DirectConnection);
+}
+
+const std::vector<QVideoFrame> &FrameGrabber::getFrames() const
+{
+ return m_frames;
+}
+
+std::vector<QVideoFrame> FrameGrabber::waitAndTakeFrames(size_t minCount, qint64 noOlderThanTime)
+{
+ m_frames.clear();
+
+ const auto enoughFramesOrStopped = [this, minCount, noOlderThanTime]() -> bool {
+ if (m_stopped)
+ return true; // Stop waiting
+
+ if (noOlderThanTime > 0) {
+ // Reject frames older than noOlderThanTime
+ const auto newEnd = std::remove_if(m_frames.begin(), m_frames.end(),
+ [noOlderThanTime](const QVideoFrame &frame) {
+ return frame.startTime() <= noOlderThanTime;
+ });
+ m_frames.erase(newEnd, m_frames.end());
+ }
+
+ return m_frames.size() >= minCount;
+ };
+
+ if (!QTest::qWaitFor(enoughFramesOrStopped, s_testTimeout))
+ return {};
+
+ if (m_stopped)
+ return {};
+
+ return std::exchange(m_frames, {});
+}
+
+bool FrameGrabber::isStopped() const
+{
+ return m_stopped;
+}
+
+void FrameGrabber::stop()
+{
+ qWarning() << "Stopping grabber";
+ m_stopped = true;
+}
+
+#include "moc_grabber.cpp"
diff --git a/tests/auto/integration/qwindowcapturebackend/grabber.h b/tests/auto/integration/qwindowcapturebackend/grabber.h
new file mode 100644
index 000000000..e997ff954
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/grabber.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef WINDOW_CAPTURE_GRABBER_H
+#define WINDOW_CAPTURE_GRABBER_H
+
+#include <qvideosink.h>
+#include <vector>
+
+QT_USE_NAMESPACE
+
+/*!
+ The FrameGrabber stores frames that arrive from the window capture,
+ and is used to inspect captured frames in the tests.
+*/
+class FrameGrabber : public QVideoSink
+{
+ Q_OBJECT
+
+public:
+ FrameGrabber();
+
+ const std::vector<QVideoFrame> &getFrames() const;
+
+ /*!
+ Wait for at least \a minCount frames that are no older than noOlderThanTime.
+
+ Returns empty if not enough frames arrived, or if grabber was stopped before global timeout
+ elapsed.
+ */
+ std::vector<QVideoFrame> waitAndTakeFrames(size_t minCount, qint64 noOlderThanTime = 0);
+
+ bool isStopped() const;
+
+public slots:
+ void stop();
+
+private:
+ std::vector<QVideoFrame> m_frames;
+ bool m_stopped = false;
+};
+
+#endif
diff --git a/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp b/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp
new file mode 100644
index 000000000..6809f81a8
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp
@@ -0,0 +1,278 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+// TESTED_COMPONENT=src/multimedia
+
+#include "fixture.h"
+#include "widget.h"
+
+#include <qmediarecorder.h>
+#include <qpainter.h>
+#include <qsignalspy.h>
+#include <qtest.h>
+#include <qwindowcapture.h>
+#include <qcommandlineparser.h>
+
+#include <chrono>
+#include <vector>
+
+using std::chrono::duration_cast;
+using std::chrono::high_resolution_clock;
+using std::chrono::microseconds;
+
+QT_USE_NAMESPACE
+
+class tst_QWindowCaptureBackend : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ static void initTestCase()
+ {
+#ifdef Q_OS_ANDROID
+ QSKIP("Feature does not work on Android");
+#endif
+#if defined(Q_OS_LINUX)
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci"
+ && qEnvironmentVariable("XDG_SESSION_TYPE").toLower() != "x11")
+ QSKIP("Skip on wayland; to be fixed");
+#elif defined(Q_OS_MACOS)
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116285: Skip on macOS CI because of permissions issues");
+#endif
+
+ const QWindowCapture capture;
+ if (capture.error() == QWindowCapture::CapturingNotSupported)
+ QSKIP("Screen capturing not supported");
+ }
+
+ void isActive_returnsFalse_whenNotStarted()
+ {
+ const WindowCaptureFixture fixture;
+ QVERIFY(!fixture.m_capture.isActive());
+ }
+
+ void setActive_failsAndEmitEerrorOccurred_whenNoWindowSelected()
+ {
+ WindowCaptureFixture fixture;
+
+ fixture.m_capture.setActive(true);
+
+ QVERIFY(!fixture.m_capture.isActive());
+ QVERIFY(!fixture.m_errors.empty());
+ }
+
+ void setActive_startsWindowCapture_whenCalledWithTrue()
+ {
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start());
+
+ // Ensure that we have received a frame
+ QVERIFY(fixture.waitForFrame().isValid());
+
+ QCOMPARE(fixture.m_activations.size(), 1);
+ QVERIFY(fixture.m_errors.empty());
+ }
+
+ void capturedImage_equals_imageFromGrab_data()
+ {
+ QTest::addColumn<QSize>("windowSize");
+ QTest::newRow("single-pixel-window") << QSize{1, 1};
+ QTest::newRow("small-window") << QSize{60, 40};
+ QTest::newRow("odd-width-window") << QSize{ 61, 40 };
+ QTest::newRow("odd-height-window") << QSize{ 60, 41 };
+ QTest::newRow("big-window") << QApplication::primaryScreen()->size();
+ }
+
+ void capturedImage_equals_imageFromGrab()
+ {
+ QFETCH(QSize, windowSize);
+
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start(windowSize));
+
+ const QImage expected = fixture.m_widget.grabImage();
+ const QImage actual = fixture.waitForFrame().toImage();
+
+ QVERIFY(fixture.compareImages(actual, expected));
+ }
+
+ void capturedImage_changes_whenWindowContentChanges()
+ {
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start());
+
+ const auto startTime = high_resolution_clock::now();
+
+ const QVideoFrame colorFrame = fixture.waitForFrame();
+ QVERIFY(colorFrame.isValid());
+
+ fixture.m_widget.setDisplayPattern(TestWidget::Grid);
+
+ // Ignore all frames that were grabbed since the colored frame,
+ // to ensure that we get a frame after we changed display pattern
+ const high_resolution_clock::duration delay = high_resolution_clock::now() - startTime;
+ const QVideoFrame gridFrame = fixture.waitForFrame(
+ colorFrame.endTime() + duration_cast<microseconds>(delay).count());
+
+ QVERIFY(gridFrame.isValid());
+
+ // Make sure that the gridFrame has a different content than the colorFrame
+ QCOMPARE(gridFrame.size(), colorFrame.size());
+ QCOMPARE_NE(gridFrame.toImage(), colorFrame.toImage());
+
+ const QImage actualGridImage = fixture.m_widget.grabImage();
+ QVERIFY(fixture.compareImages(gridFrame.toImage(), actualGridImage));
+ }
+
+ void sequenceOfCapturedImages_compareEqual_whenWindowContentIsUnchanged()
+ {
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start());
+
+ const std::vector<QVideoFrame> frames = fixture.m_grabber.waitAndTakeFrames(10);
+ QVERIFY(!frames.empty());
+
+ QImage firstFrame = frames.front().toImage();
+ QVERIFY(!firstFrame.isNull());
+
+ qsizetype index = 0;
+ for (const auto &frame : std::as_const(frames)){
+ QVERIFY(fixture.compareImages(frame.toImage(), firstFrame, QString::number(index)));
+ ++index;
+ }
+ }
+
+ void recorder_encodesFrames_toValidMediaFile_data()
+ {
+ QTest::addColumn<QSize>("windowSize");
+ //QTest::newRow("empty-window") << QSize{ 0, 0 }; TODO: Crash
+ //QTest::newRow("single-pixel-window") << QSize{ 1, 1 }; TODO: Crash
+ QTest::newRow("small-window") << QSize{ 60, 40 };
+ QTest::newRow("odd-width-window") << QSize{ 61, 40 };
+ QTest::newRow("odd-height-window") << QSize{ 60, 41 };
+ QTest::newRow("big-window") << QSize{ 800, 600 };
+ }
+
+ void recorder_encodesFrames_toValidMediaFile()
+ {
+#ifdef Q_OS_LINUX
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116671: SKIP on linux CI to avoid crashes in ffmpeg. To be fixed.");
+#endif
+ QFETCH(QSize, windowSize);
+
+ WindowCaptureWithWidgetAndRecorderFixture fixture;
+ fixture.start(windowSize);
+
+ // Wait on grabber to ensure that video recorder also get some frames
+ fixture.m_grabber.waitAndTakeFrames(60);
+
+ // Wait for recorder finalization
+ fixture.stop();
+
+ QVERIFY(fixture.m_recorderErrors.empty());
+ QVERIFY(QFile{ fixture.m_mediaFile }.exists());
+ QVERIFY(fixture.testVideoFilePlayback(fixture.m_mediaFile));
+ }
+
+ void recorder_encodesFrames_toValidMediaFile_whenWindowResizes_data()
+ {
+ QTest::addColumn<int>("increment");
+ QTest::newRow("shrink") << -1;
+ QTest::newRow("grow") << 1;
+ }
+
+ void recorder_encodesFrames_toValidMediaFile_whenWindowResizes()
+ {
+#ifdef Q_OS_LINUX
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116671: SKIP on linux CI to avoid crashes in ffmpeg. To be fixed.");
+#endif
+ QFETCH(int, increment);
+
+ QSize windowSize = { 200, 150 };
+ WindowCaptureWithWidgetAndRecorderFixture fixture;
+ fixture.start(windowSize, /*toggle pattern*/ false);
+
+ for (qsizetype i = 0; i < 20; ++i) {
+ windowSize.setWidth(windowSize.width() + increment);
+ windowSize.setHeight(windowSize.height() + increment);
+ fixture.m_widget.setSize(windowSize);
+
+ // Wait on grabber to ensure that video recorder also get some frames
+ fixture.m_grabber.waitAndTakeFrames(1);
+ }
+
+ // Wait for recorder finalization
+ fixture.stop();
+
+ QVERIFY(fixture.m_recorderErrors.empty());
+ QVERIFY(QFile{ fixture.m_mediaFile }.exists());
+ QVERIFY(fixture.testVideoFilePlayback(fixture.m_mediaFile));
+ }
+
+ void windowCapture_capturesWindowsInOtherProcesses()
+ {
+ WindowCaptureWithWidgetInOtherProcessFixture fixture;
+ QVERIFY(fixture.start());
+
+ // Get reference image from our in-process widget
+ const QImage expected = fixture.m_widget.grabImage();
+
+ // Get actual image grabbed from out-of-process widget
+ const QImage actual = fixture.waitForFrame().toImage();
+
+ QVERIFY(fixture.compareImages(actual, expected));
+ }
+
+ /*
+ This test is not a requirement per se, but we want all platforms
+ to behave the same. A reasonable alternative could have been to
+ treat closed window as a regular 'Stop' capture (not an error).
+ */
+ void windowCapture_stopsWithError_whenProcessCloses()
+ {
+ WindowCaptureWithWidgetInOtherProcessFixture fixture;
+ QVERIFY(fixture.start());
+
+ // Get capturing started
+ fixture.m_grabber.waitAndTakeFrames(3);
+
+ // Closing the process waits for it to exit
+ fixture.m_windowProcess.close();
+
+ const bool captureFailed =
+ QTest::qWaitFor([&] { return !fixture.m_errors.empty(); }, s_testTimeout);
+
+ QVERIFY(captureFailed);
+ }
+};
+
+int main(int argc, char *argv[])
+{
+ QCommandLineParser cmd;
+ const QCommandLineOption showTestWidget{ QStringList{ "show" },
+ "Creates a test widget with given title",
+ "windowTitle" };
+ cmd.addOption(showTestWidget);
+ cmd.parse({ argv, argv + argc });
+
+ if (cmd.isSet(showTestWidget)) {
+ QApplication app{ argc, argv };
+ const QString windowTitle = cmd.value(showTestWidget);
+ const bool result = showCaptureWindow(windowTitle);
+ return result ? 0 : 1;
+ }
+
+ // If no special arguments are set, enter the regular QTest main routine
+ TESTLIB_SELFCOVERAGE_START("tst_QWindowCaptureatioBackend")
+ QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<tst_QWindowCaptureBackend>();
+ QApplication app(argc, argv);
+ app.setAttribute(Qt::AA_Use96Dpi, true);
+ tst_QWindowCaptureBackend tc;
+ QTEST_SET_MAIN_SOURCE_PATH return QTest::qExec(&tc, argc, argv);
+
+}
+
+#include "tst_qwindowcapturebackend.moc"
diff --git a/tests/auto/integration/qwindowcapturebackend/widget.cpp b/tests/auto/integration/qwindowcapturebackend/widget.cpp
new file mode 100644
index 000000000..b17487149
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/widget.cpp
@@ -0,0 +1,125 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "widget.h"
+#include "fixture.h"
+
+#include <qapplication.h>
+#include <qsystemsemaphore.h>
+#include <qtest.h>
+
+
+TestWidget::TestWidget(const QString &uuid, QScreen *screen)
+{
+ // Give each window a unique title so that we can uniquely identify it
+ setWindowTitle(uuid);
+
+ setScreen(screen ? screen : QApplication::primaryScreen());
+
+ // Use frameless hint because on Windows UWP platform, the window titlebar is captured,
+ // but the reference image acquired by 'QWindow::grab()' does not include titlebar.
+ // This allows us to do pixel-perfect matching of captured content.
+ setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
+ setFixedSize(60, 40);
+}
+
+void TestWidget::setDisplayPattern(Pattern p)
+{
+ m_pattern = p;
+ repaint();
+}
+
+void TestWidget::setSize(QSize size)
+{
+ if (size == QApplication::primaryScreen()->size())
+ setWindowState(Qt::WindowMaximized);
+ else
+ setFixedSize(size);
+}
+
+QImage TestWidget::grabImage()
+{
+ return grab().toImage();
+}
+
+void TestWidget::togglePattern()
+{
+ Pattern p = m_pattern == ColoredSquares ? Grid : ColoredSquares;
+ setDisplayPattern(p);
+}
+
+void TestWidget::paintEvent(QPaintEvent *)
+{
+ QPainter p(this);
+ p.setPen(Qt::NoPen);
+ p.setBrush(Qt::black);
+ p.drawRect(rect());
+
+ if (m_pattern == ColoredSquares)
+ drawColoredSquares(p);
+ else
+ drawGrid(p);
+
+ p.end();
+}
+
+void TestWidget::drawColoredSquares(QPainter &p)
+{
+ const std::vector<std::vector<Qt::GlobalColor>> colors = { { Qt::red, Qt::green, Qt::blue },
+ { Qt::white, Qt::white, Qt::white },
+ { Qt::blue, Qt::green, Qt::red } };
+
+ const QSize squareSize = size() / 3;
+ QRect rect{ QPoint{ 0, 0 }, squareSize };
+
+ for (const auto &row : colors) {
+ for (const auto &color : row) {
+ p.setBrush(color);
+ p.drawRect(rect);
+ rect.moveLeft(rect.left() + rect.width());
+ }
+ rect.moveTo({ 0, rect.bottom() });
+ }
+}
+
+void TestWidget::drawGrid(QPainter &p) const
+{
+ const QSize winSize = size();
+
+ p.setPen(Qt::white);
+
+ QLine vertical{ QPoint{ 5, 0 }, QPoint{ 5, winSize.height() } };
+ while (vertical.x1() < winSize.width()) {
+ p.drawLine(vertical);
+ vertical.translate(10, 0);
+ }
+ QLine horizontal{ QPoint{ 0, 5 }, QPoint{ winSize.width(), 5 } };
+ while (horizontal.y1() < winSize.height()) {
+ p.drawLine(horizontal);
+ horizontal.translate(0, 10);
+ }
+}
+
+bool showCaptureWindow(const QString &windowTitle)
+{
+ const QNativeIpcKey key{ windowTitle };
+ QSystemSemaphore windowVisible(key);
+
+ TestWidget widget{ windowTitle };
+ widget.show();
+
+ // Wait for window to be visible and suitable for window capturing
+ const bool result = QTest::qWaitForWindowExposed(&widget, s_testTimeout.count());
+ if (!result)
+ qDebug() << "Failed to show window";
+
+ // Signal to host process that the window is visible
+ windowVisible.release();
+
+ // Keep window visible until a termination signal is received
+ QApplication::exec();
+
+ return result;
+}
+
+#include "moc_widget.cpp"
diff --git a/tests/auto/integration/qwindowcapturebackend/widget.h b/tests/auto/integration/qwindowcapturebackend/widget.h
new file mode 100644
index 000000000..56427a566
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/widget.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef WINDOW_CAPTURE_WIDGET_H
+#define WINDOW_CAPTURE_WIDGET_H
+
+#include <qwidget.h>
+#include <qscreen.h>
+#include <qpainter.h>
+#include <quuid.h>
+
+/*!
+ Window capable of drawing test patterns used for capture tests
+ */
+class TestWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ enum Pattern { ColoredSquares, Grid };
+
+ TestWidget(const QString &uuid = QUuid::createUuid().toString(), QScreen *screen = nullptr);
+
+ void setDisplayPattern(Pattern p);
+ void setSize(QSize size);
+ QImage grabImage();
+
+public slots:
+ void togglePattern();
+
+protected:
+ void paintEvent(QPaintEvent *) override;
+
+private:
+ void drawColoredSquares(QPainter &p);
+ void drawGrid(QPainter &p) const;
+
+ Pattern m_pattern = ColoredSquares;
+};
+
+bool showCaptureWindow(const QString &windowTitle);
+
+#endif
diff --git a/tests/auto/integration/shared/mediabackendutils.h b/tests/auto/integration/shared/mediabackendutils.h
new file mode 100644
index 000000000..142c01c01
--- /dev/null
+++ b/tests/auto/integration/shared/mediabackendutils.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef MEDIABACKENDUTILS_H
+#define MEDIABACKENDUTILS_H
+
+#include <QtTest/qtestcase.h>
+#include <private/qplatformmediaintegration_p.h>
+
+inline bool isGStreamerPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "gstreamer";
+}
+
+inline bool isDarwinPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "darwin";
+}
+
+inline bool isAndroidPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "android";
+}
+
+inline bool isFFMPEGPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "ffmpeg";
+}
+
+inline bool isWindowsPlatform()
+{
+ return QPlatformMediaIntegration::instance()->name() == "windows";
+}
+
+inline bool isCI()
+{
+ return qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci";
+}
+
+#define QSKIP_GSTREAMER(message) \
+ do { \
+ if (isGStreamerPlatform()) \
+ QSKIP(message); \
+ } while (0)
+
+#define QSKIP_IF_NOT_FFMPEG() \
+ do { \
+ if (!isFFMPEGPlatform()) \
+ QSKIP("Feature is only supported on FFmpeg"); \
+ } while (0)
+
+#define QSKIP_FFMPEG(message) \
+ do { \
+ if (isFFMPEGPlatform()) \
+ QSKIP(message); \
+ } while (0)
+
+#define QEXPECT_FAIL_GSTREAMER(dataIndex, comment, mode) \
+ do { \
+ if (isGStreamerPlatform()) \
+ QEXPECT_FAIL(dataIndex, comment, mode); \
+ } while (0)
+
+#endif // MEDIABACKENDUTILS_H
diff --git a/tests/auto/integration/shared/mediafileselector.h b/tests/auto/integration/shared/mediafileselector.h
index 984da6e2b..aa192f3e9 100644
--- a/tests/auto/integration/shared/mediafileselector.h
+++ b/tests/auto/integration/shared/mediafileselector.h
@@ -1,71 +1,179 @@
-/****************************************************************************
-**
-** Copyright (C) 2017 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** 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 General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** 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-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef MEDIAFILESELECTOR_H
#define MEDIAFILESELECTOR_H
-#include <QMediaContent>
-#include <QMediaPlayer>
+#include <QUrl>
+#include <qmediaplayer.h>
+#include <qaudiooutput.h>
+#include <qvideosink.h>
+#include <qsignalspy.h>
+#include <qfileinfo.h>
+#include <qtest.h>
+#include <private/qmultimediautils_p.h>
+
+#include <unordered_map>
QT_BEGIN_NAMESPACE
-namespace MediaFileSelector {
+using MaybeUrl = QMaybe<QUrl, QString>;
+
+#define CHECK_SELECTED_URL(maybeUrl) \
+ if (!maybeUrl) \
+ QSKIP((QLatin1String("\nUnable to select none of the media candidates:\n") + maybeUrl.error()) \
+ .toLocal8Bit() \
+ .data())
-static QMediaContent selectMediaFile(const QStringList& mediaCandidates)
+class MediaFileSelector
{
- QMediaPlayer player;
+public:
+ int failedSelectionsCount() const { return m_failedSelectionsCount; }
- QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error)));
+ QString dumpErrors() const
+ {
+ QStringList failedMedias;
+ for (const auto &mediaToError : m_mediaToErrors)
+ if (!mediaToError.second.isEmpty())
+ failedMedias.emplace_back(mediaToError.first);
- for (const QString &s : mediaCandidates) {
- QFileInfo mediaFile(s);
- if (!mediaFile.exists())
- continue;
- QMediaContent media = QMediaContent(QUrl::fromLocalFile(mediaFile.absoluteFilePath()));
- player.setMedia(media);
- player.play();
+ failedMedias.sort();
+ return dumpErrors(failedMedias);
+ }
- for (int i = 0; i < 2000 && player.mediaStatus() != QMediaPlayer::BufferedMedia && errorSpy.isEmpty(); i+=50) {
- QTest::qWait(50);
+ template <typename... Media>
+ MaybeUrl select(Media... media)
+ {
+ return select({ std::move(nativeFileName(media))... });
+ }
+
+ MaybeUrl select(const QStringList &candidates)
+ {
+ QUrl foundUrl;
+ for (const auto &media : candidates) {
+ auto emplaceRes = m_mediaToErrors.try_emplace(media, QString());
+ if (emplaceRes.second) {
+ auto maybeUrl = selectMediaFile(media);
+ if (!maybeUrl) {
+ Q_ASSERT(!maybeUrl.error().isEmpty());
+ emplaceRes.first->second = maybeUrl.error();
+ }
+ }
+
+ if (foundUrl.isEmpty() && emplaceRes.first->second.isEmpty())
+ foundUrl = media;
}
- if (player.mediaStatus() == QMediaPlayer::BufferedMedia && errorSpy.isEmpty()) {
- return media;
+ if (!foundUrl.isEmpty())
+ return foundUrl;
+
+ ++m_failedSelectionsCount;
+ return { QUnexpect{}, dumpErrors(candidates) };
+ }
+
+private:
+ QString dumpErrors(const QStringList &medias) const
+ {
+ using namespace Qt::StringLiterals;
+ QString result;
+
+ for (const auto &media : medias) {
+ auto it = m_mediaToErrors.find(media);
+ if (it != m_mediaToErrors.end() && !it->second.isEmpty())
+ result.append("\t"_L1)
+ .append(it->first)
+ .append(": "_L1)
+ .append(it->second)
+ .append("\n"_L1);
}
- errorSpy.clear();
+
+ return result;
+ }
+
+ static MaybeUrl selectMediaFile(QString media)
+ {
+ if (qEnvironmentVariableIsSet("QTEST_SKIP_MEDIA_VALIDATION"))
+ return QUrl(media);
+
+ using namespace Qt::StringLiterals;
+
+ QAudioOutput audioOutput;
+ QVideoSink videoOutput;
+ QMediaPlayer player;
+ player.setAudioOutput(&audioOutput);
+ player.setVideoOutput(&videoOutput);
+
+ player.setSource(media);
+ player.play();
+
+ const auto waitingFinished = QTest::qWaitFor([&]() {
+ if (player.error() != QMediaPlayer::NoError)
+ return true;
+
+ switch (player.mediaStatus()) {
+ case QMediaPlayer::BufferingMedia:
+ case QMediaPlayer::BufferedMedia:
+ case QMediaPlayer::EndOfMedia:
+ case QMediaPlayer::InvalidMedia:
+ return true;
+
+ default:
+ return false;
+ }
+ });
+
+ auto enumValueToString = [](auto enumValue) {
+ return QString(QMetaEnum::fromType<decltype(enumValue)>().valueToKey(enumValue));
+ };
+
+ if (!waitingFinished)
+ return { QUnexpect{},
+ "The media got stuck in the status "_L1
+ + enumValueToString(player.mediaStatus()) };
+
+ if (player.mediaStatus() == QMediaPlayer::InvalidMedia)
+ return { QUnexpect{},
+ "Unable to load the media. Error ["_L1 + enumValueToString(player.error())
+ + " "_L1 + player.errorString() + "]"_L1 };
+
+ if (player.error() != QMediaPlayer::NoError)
+ return { QUnexpect{},
+ "Unable to start playing the media, codecs issues. Error ["_L1
+ + enumValueToString(player.error()) + " "_L1 + player.errorString()
+ + "]"_L1 };
+
+ return QUrl(media);
}
- return QMediaContent();
-}
+ QString nativeFileName(const QString &media)
+ {
+#ifdef Q_OS_ANDROID
+ auto it = m_nativeFiles.find(media);
+ if (it != m_nativeFiles.end())
+ return it->second->fileName();
+
+ QFile file(media);
+ if (file.open(QIODevice::ReadOnly)) {
+ m_nativeFiles.insert({ media, std::unique_ptr<QTemporaryFile>(QTemporaryFile::createNativeFile(file))});
+ return m_nativeFiles[media]->fileName();
+ }
+ qWarning() << "Failed to create temporary file";
+#endif // Q_OS_ANDROID
+
+ return media;
+ }
-} // MediaFileSelector namespace
+private:
+#ifdef Q_OS_ANDROID
+ std::unordered_map<QString, std::unique_ptr<QTemporaryFile>> m_nativeFiles;
+#endif
+ std::unordered_map<QString, QString> m_mediaToErrors;
+ int m_failedSelectionsCount = 0;
+};
QT_END_NAMESPACE
+Q_DECLARE_METATYPE(MaybeUrl)
+
#endif
diff --git a/tests/auto/integration/shared/testvideosink.h b/tests/auto/integration/shared/testvideosink.h
new file mode 100644
index 000000000..b14c819c5
--- /dev/null
+++ b/tests/auto/integration/shared/testvideosink.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef TESTVIDEOSINK_H
+#define TESTVIDEOSINK_H
+
+#include <qvideosink.h>
+#include <qvideoframe.h>
+#include <qelapsedtimer.h>
+#include <qsignalspy.h>
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+/*
+ This is a simple video surface which records all presented frames.
+*/
+class TestVideoSink : public QVideoSink
+{
+ Q_OBJECT
+public:
+ explicit TestVideoSink(bool storeFrames = false) : m_storeFrames(storeFrames)
+ {
+ connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::addVideoFrame);
+ connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::videoFrameChangedSync);
+ }
+
+ QVideoFrame waitForFrame()
+ {
+ QSignalSpy spy(this, &TestVideoSink::videoFrameChangedSync);
+ return spy.wait() ? spy.at(0).at(0).value<QVideoFrame>() : QVideoFrame{};
+ }
+
+ void setStoreFrames(bool storeFrames = true) { m_storeFrames = storeFrames; }
+
+private Q_SLOTS:
+ void addVideoFrame(const QVideoFrame &frame)
+ {
+ if (!m_elapsedTimer.isValid())
+ m_elapsedTimer.start();
+ else
+ m_elapsedTimer.restart();
+
+ if (m_storeFrames)
+ m_frameList.append(frame);
+
+ if (frame.isValid())
+ m_frameTimes.emplace_back(std::chrono::microseconds(frame.startTime()));
+
+ ++m_totalFrames;
+ }
+
+signals:
+ void videoFrameChangedSync(const QVideoFrame &frame);
+
+public:
+ QList<QVideoFrame> m_frameList;
+ int m_totalFrames = 0; // used instead of the list when frames are not stored
+ QElapsedTimer m_elapsedTimer;
+ using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>;
+ std::vector<TimePoint> m_frameTimes;
+
+private:
+ bool m_storeFrames;
+};
+
+QT_END_NAMESPACE
+
+#endif // TESTVIDEOSINK_H