diff options
Diffstat (limited to 'tests/auto')
627 files changed, 14585 insertions, 3847 deletions
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 7a402f847..66b291222 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -1,7 +1,6 @@ -# Generated from auto.pro. +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause +add_subdirectory(cmake) add_subdirectory(unit) add_subdirectory(integration) -# special case begin -# add_subdirectory(cmake) -# special case end diff --git a/tests/auto/cmake/CMakeLists.txt b/tests/auto/cmake/CMakeLists.txt index 62b88dcc1..6e556ff93 100644 --- a/tests/auto/cmake/CMakeLists.txt +++ b/tests/auto/cmake/CMakeLists.txt @@ -1,24 +1,26 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16) - -project(qmake_cmake_files) +project(qtmultimedia_cmake_tests) enable_testing() -find_package(Qt5Core REQUIRED) +find_package(Qt6 REQUIRED COMPONENTS Core) +find_package(Qt6 OPTIONAL_COMPONENTS Widgets) -include("${_Qt5CTestMacros}") +include("${_Qt6CTestMacros}") set(qt_module_includes Multimedia QCamera ) -if (NOT NO_WIDGETS) +if(TARGET Qt6::Widgets) list(APPEND qt_module_includes MultimediaWidgets QVideoWidget ) endif() -test_module_includes( +_qt_internal_test_module_includes( ${qt_module_includes} ) diff --git a/tests/auto/integration/CMakeLists.txt b/tests/auto/integration/CMakeLists.txt index 3f27496cb..9be80db63 100644 --- a/tests/auto/integration/CMakeLists.txt +++ b/tests/auto/integration/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from integration.pro. # special case begin @@ -6,10 +9,17 @@ 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) 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/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 Binary files differnew file mode 100644 index 000000000..bd9a507c7 --- /dev/null +++ b/tests/auto/integration/multiapp/double-drop.wav 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/qaudiodecoderbackend/CMakeLists.txt b/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt index bef856a1e..d2206182f 100644 --- a/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt +++ b/tests/auto/integration/qaudiodecoderbackend/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudiodecoderbackend.pro. ##################################################################### @@ -12,21 +15,15 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qaudiodecoderbackend SOURCES - ../shared/mediafileselector.h tst_qaudiodecoderbackend.cpp + ../shared/mediafileselector.h + ../shared/mediabackendutils.h INCLUDE_DIRECTORIES + ../shared/ ../../../../src/multimedia/audio - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::Multimedia Qt::MultimediaPrivate TESTDATA ${test_data} ) - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_qaudiodecoderbackend CONDITION boot2qt - DEFINES - WAV_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 Binary files differnew file mode 100644 index 000000000..6b67a3433 --- /dev/null +++ b/tests/auto/integration/qaudiodecoderbackend/testdata/test-no-audio-track.mp4 diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp index c04d7ea2f..5a48b4457 100644 --- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp +++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp @@ -1,43 +1,30 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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" -#ifdef WAV_SUPPORT_NOT_FORCED -#include "../shared/mediafileselector.h" -#endif +#include "mediafileselector.h" +#include "mediabackendutils.h" + +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; -#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" -#define TEST_INVALID_SOURCE "invalid" +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 @@ -57,14 +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() @@ -76,25 +78,289 @@ void tst_QAudioDecoderBackend::initTestCase() QAudioDecoder d; if (!d.isSupported()) QSKIP("Audio decoder service is not available"); + + 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::NotSupportedError) @@ -106,40 +372,44 @@ void tst_QAudioDecoderBackend::fileTest() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.source(), QString("")); + QCOMPARE(d.source(), QStringLiteral("")); QVERIFY(d.audioFormat() == QAudioFormat()); // Test local file - QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME)); - QUrl url = QUrl::fromLocalFile(fileInfo.absoluteFilePath()); - d.setSource(url); + + d.setSource(*m_wavFile); QVERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); - QCOMPARE(d.source(), url); + 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 isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool))); - 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.isDecoding()); + 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().sampleRate(), testFileSampleRate); QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16); QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono @@ -152,37 +422,44 @@ 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()); - QCOMPARE(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_VERIFY(!d.isDecoding()); d.stop(); QTRY_VERIFY(!d.isDecoding()); - QTRY_COMPARE(durationSpy.count(), 2); + QTRY_COMPARE(durationSpy.size(), 2); QCOMPARE(d.duration(), qint64(-1)); QVERIFY(!d.bufferAvailable()); readySpy.clear(); @@ -192,6 +469,9 @@ void tst_QAudioDecoderBackend::fileTest() 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); @@ -211,13 +491,16 @@ void tst_QAudioDecoderBackend::fileTest() byteCount = 0; d.start(); - QTRY_VERIFY(d.isDecoding()); 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()); @@ -233,40 +516,35 @@ void tst_QAudioDecoderBackend::fileTest() sampleCount += buffer.sampleCount(); byteCount += buffer.byteCount(); - // Now drain the decoder - if (duration < 996000) { - 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()); - QCOMPARE(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 < 996000) { - 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()); QVERIFY(!d.isDecoding()); d.stop(); QTRY_VERIFY(!d.isDecoding()); - QTRY_COMPARE(durationSpy.count(), 2); + QTRY_COMPARE(durationSpy.size(), 2); QCOMPARE(d.duration(), qint64(-1)); QVERIFY(!d.bufferAvailable()); } @@ -283,24 +561,23 @@ void tst_QAudioDecoderBackend::unsupportedFileTest() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.source(), QString("")); + QCOMPARE(d.source(), QStringLiteral("")); QVERIFY(d.audioFormat() == QAudioFormat()); // Test local file - QFileInfo fileInfo(QFINDTESTDATA(TEST_UNSUPPORTED_FILE_NAME)); - QUrl url = QUrl::fromLocalFile(fileInfo.absoluteFilePath()); + QUrl url = testFileUrl(TEST_UNSUPPORTED_FILE_NAME); d.setSource(url); QVERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); 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 isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool))); - 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.isDecoding()); @@ -324,7 +601,7 @@ void tst_QAudioDecoderBackend::unsupportedFileTest() QVERIFY(finishedSpy.isEmpty()); QVERIFY(positionSpy.isEmpty()); // Either reject the file directly, or set the duration to 5secs on setUrl() and back to -1 on start() - QVERIFY(durationSpy.isEmpty() || durationSpy.count() == 2); + QVERIFY(durationSpy.isEmpty() || durationSpy.size() == 2); errorSpy.clear(); @@ -341,7 +618,7 @@ void tst_QAudioDecoderBackend::unsupportedFileTest() QVERIFY(isDecodingSpy.isEmpty()); QVERIFY(finishedSpy.isEmpty()); QVERIFY(positionSpy.isEmpty()); - QVERIFY(durationSpy.isEmpty() || durationSpy.count() == 2); + QVERIFY(durationSpy.isEmpty() || durationSpy.size() == 2); d.stop(); @@ -367,20 +644,19 @@ void tst_QAudioDecoderBackend::corruptedFileTest() QVERIFY(d.audioFormat() == QAudioFormat()); // Test local file - QFileInfo fileInfo(QFINDTESTDATA(TEST_CORRUPTED_FILE_NAME)); - QUrl url = QUrl::fromLocalFile(fileInfo.absoluteFilePath()); + QUrl url = testFileUrl(TEST_CORRUPTED_FILE_NAME); d.setSource(url); QVERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); 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 isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool))); - 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.isDecoding()); @@ -422,7 +698,6 @@ void tst_QAudioDecoderBackend::corruptedFileTest() QVERIFY(positionSpy.isEmpty()); QVERIFY(durationSpy.isEmpty()); - d.stop(); QTRY_VERIFY(!d.isDecoding()); QCOMPARE(d.duration(), qint64(-1)); @@ -449,13 +724,13 @@ void tst_QAudioDecoderBackend::invalidSource() QVERIFY(!d.bufferAvailable()); 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 isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool))); - 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.isDecoding()); @@ -522,8 +797,8 @@ void tst_QAudioDecoderBackend::invalidSource() 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::NotSupportedError) @@ -532,21 +807,19 @@ void tst_QAudioDecoderBackend::deviceTest() 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 isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool))); - 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.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.source(), 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); @@ -558,20 +831,22 @@ void tst_QAudioDecoderBackend::deviceTest() d.start(); - QTRY_VERIFY(d.isDecoding()); 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().sampleRate(), testFileSampleRate); QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16); QVERIFY(errorSpy.isEmpty()); @@ -580,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); } @@ -588,28 +863,34 @@ 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_VERIFY(!d.isDecoding()); d.stop(); 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(); @@ -618,6 +899,9 @@ void tst_QAudioDecoderBackend::deviceTest() 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); @@ -631,13 +915,17 @@ void tst_QAudioDecoderBackend::deviceTest() d.start(); QVERIFY(d.error() == QAudioDecoder::NoError); - QTRY_VERIFY(d.isDecoding()); 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()); @@ -652,10 +940,26 @@ void tst_QAudioDecoderBackend::deviceTest() d.stop(); 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/qaudiodevice/CMakeLists.txt b/tests/auto/integration/qaudiodevice/CMakeLists.txt index 059e62f87..93f0d49dd 100644 --- a/tests/auto/integration/qaudiodevice/CMakeLists.txt +++ b/tests/auto/integration/qaudiodevice/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudiodevice.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qaudiodevice SOURCES tst_qaudiodevice.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp index af48c4f3a..cd686bd08 100644 --- a/tests/auto/integration/qaudiodevice/tst_qaudiodevice.cpp +++ b/tests/auto/integration/qaudiodevice/tst_qaudiodevice.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 <QtTest/QtTest> @@ -35,8 +10,6 @@ #include <QList> #include <QMediaDevices> -//TESTED_COMPONENT=src/multimedia - class tst_QAudioDevice : public QObject { Q_OBJECT @@ -45,7 +18,6 @@ public: private slots: void initTestCase(); - void cleanupTestCase(); void checkAvailableDefaultInput(); void checkAvailableDefaultOutput(); void channels(); @@ -59,7 +31,7 @@ private slots: void equalityOperator(); private: - QAudioDevice* device; + std::unique_ptr<QAudioDevice> device; }; void tst_QAudioDevice::initTestCase() @@ -69,21 +41,18 @@ void tst_QAudioDevice::initTestCase() if (devices.size() == 0) { QSKIP("NOTE: no audio output device found, no tests will be performed"); } else { - device = new QAudioDevice(devices.at(0)); + device = std::make_unique<QAudioDevice>(devices.at(0)); } } -void tst_QAudioDevice::cleanupTestCase() -{ - delete device; -} - void tst_QAudioDevice::checkAvailableDefaultInput() { // Only perform tests if audio input device exists! QList<QAudioDevice> devices = QMediaDevices::audioInputs(); if (devices.size() > 0) { - QVERIFY(!QMediaDevices::defaultAudioInput().isNull()); + auto defaultInput = QMediaDevices::defaultAudioInput(); + QVERIFY(!defaultInput.isNull()); + QCOMPARE(std::count(devices.begin(), devices.end(), defaultInput), 1); } } @@ -92,7 +61,9 @@ void tst_QAudioDevice::checkAvailableDefaultOutput() // Only perform tests if audio input device exists! QList<QAudioDevice> devices = QMediaDevices::audioOutputs(); if (devices.size() > 0) { - QVERIFY(!QMediaDevices::defaultAudioOutput().isNull()); + auto defaultOutput = QMediaDevices::defaultAudioOutput(); + QVERIFY(!defaultOutput.isNull()); + QCOMPARE(std::count(devices.begin(), devices.end(), defaultOutput), 1); } } diff --git a/tests/auto/integration/qaudiosink/BLACKLIST b/tests/auto/integration/qaudiosink/BLACKLIST index 966b48af6..0b8789267 100644 --- a/tests/auto/integration/qaudiosink/BLACKLIST +++ b/tests/auto/integration/qaudiosink/BLACKLIST @@ -1 +1,11 @@ -linux ci +#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 index 26b1c50a2..902326dcc 100644 --- a/tests/auto/integration/qaudiosink/CMakeLists.txt +++ b/tests/auto/integration/qaudiosink/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + ##################################################################### ## tst_qaudiosink Test: ##################################################################### @@ -5,7 +8,7 @@ qt_internal_add_test(tst_qaudiosink SOURCES tst_qaudiosink.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp index d957cecc7..6fdfe8221 100644 --- a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp +++ b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QtCore/qlocale.h> @@ -75,6 +48,8 @@ private slots: void pushSuspendResume_data(){generate_audiofile_testrows();} void pushSuspendResume(); + void pushResetResume(); + void pushUnderrun_data(){generate_audiofile_testrows();} void pushUnderrun(); @@ -84,8 +59,16 @@ private slots: private: using FilePtr = QSharedPointer<QFile>; - QString formatToFileName(const QAudioFormat &format); + 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(); @@ -100,10 +83,10 @@ private: QString tst_QAudioSink::formatToFileName(const QAudioFormat &format) { - return QString("%1_%2_%3") - .arg(format.sampleRate()) - .arg(format.bytesPerSample()) - .arg(format.channelCount()); + 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) @@ -155,15 +138,69 @@ void tst_QAudioSink::createSineWaveData(const QAudioFormat &format, qint64 lengt 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.count(); i++) { - QTest::newRow(QString("Audio File %1").arg(i).toUtf8().constData()) + for (int i=0; i<audioFiles.size(); i++) { + QTest::newRow(QStringLiteral("Audio File %1").arg(i).toUtf8().constData()) << audioFiles.at(i) << testFormats.at(i); - } } @@ -205,6 +242,13 @@ void tst_QAudioSink::initTestCase() // 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 @@ -224,7 +268,7 @@ void tst_QAudioSink::initTestCase() QVERIFY(m_temporaryDir->isValid()); const QString temporaryAudioPath = m_temporaryDir->path() + slash; - for (const QAudioFormat &format : qAsConst(testFormats)) { + 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 @@ -250,11 +294,23 @@ void tst_QAudioSink::format() QAudioFormat actual = audioOutput.format(); QVERIFY2((requested.channelCount() == actual.channelCount()), - QString("channels: requested=%1, actual=%2").arg(requested.channelCount()).arg(actual.channelCount()).toUtf8().constData()); + QStringLiteral("channels: requested=%1, actual=%2") + .arg(requested.channelCount()) + .arg(actual.channelCount()) + .toUtf8() + .constData()); QVERIFY2((requested.sampleRate() == actual.sampleRate()), - QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toUtf8().constData()); + QStringLiteral("sampleRate: requested=%1, actual=%2") + .arg(requested.sampleRate()) + .arg(actual.sampleRate()) + .toUtf8() + .constData()); QVERIFY2((requested.sampleFormat() == actual.sampleFormat()), - QString("sampleFormat: requested=%1, actual=%2").arg((ushort)requested.sampleFormat()).arg((ushort)actual.sampleFormat()).toUtf8().constData()); + QStringLiteral("sampleFormat: requested=%1, actual=%2") + .arg((ushort)requested.sampleFormat()) + .arg((ushort)actual.sampleFormat()) + .toUtf8() + .constData()); QVERIFY(requested == actual); } @@ -314,12 +370,20 @@ void tst_QAudioSink::bufferSize() QFETCH(int, bufferSize); QAudioSink audioOutput(audioDevice.preferredFormat(), this); - QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() was not set to QAudio::NoError on creation(%1)").arg(bufferSize).toUtf8().constData()); + 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), - QString("bufferSize: requested=%1, actual=%2").arg(bufferSize).arg(audioOutput.bufferSize()).toUtf8().constData()); + QStringLiteral("bufferSize: requested=%1, actual=%2") + .arg(bufferSize) + .arg(audioOutput.bufferSize()) + .toUtf8() + .constData()); } void tst_QAudioSink::stopWhileStopped() @@ -334,11 +398,11 @@ void tst_QAudioSink::stopWhileStopped() 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))); + QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged); audioOutput.stop(); // Check that no state transition occurred - QVERIFY2((stateSignal.count() == 0), "stop() while stopped is emitting a signal and it shouldn't"); + 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()"); } @@ -354,11 +418,11 @@ void tst_QAudioSink::suspendWhileStopped() 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))); + QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged); audioOutput.suspend(); // Check that no state transition occurred - QVERIFY2((stateSignal.count() == 0), "stop() while suspended is emitting a signal and it shouldn't"); + 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()"); } @@ -374,11 +438,11 @@ void tst_QAudioSink::resumeWhileStopped() 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))); + QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged); audioOutput.resume(); // Check that no state transition occurred - QVERIFY2((stateSignal.count() == 0), "resume() while stopped is emitting a signal and it shouldn't"); + 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()"); } @@ -391,7 +455,7 @@ void tst_QAudioSink::pull() audioOutput.setVolume(0.1f); - QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -405,8 +469,11 @@ void tst_QAudioSink::pull() audioOutput.start(audioFile.data()); // Check that QAudioSink immediately transitions to ActiveState - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -417,8 +484,8 @@ void tst_QAudioSink::pull() // 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); + 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(); @@ -426,8 +493,11 @@ void tst_QAudioSink::pull() audioOutput.stop(); QTest::qWait(40); - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); @@ -444,7 +514,7 @@ void tst_QAudioSink::pullSuspendResume() audioOutput.setVolume(0.1f); - QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -457,21 +527,29 @@ void tst_QAudioSink::pullSuspendResume() audioOutput.start(audioFile.data()); // Check that QAudioSink immediately transitions to ActiveState - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); + 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.count() == 1), - QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead") - .arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -486,16 +564,19 @@ void tst_QAudioSink::pullSuspendResume() audioOutput.resume(); // Check that QAudioSink immediately transitions to ActiveState - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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.count() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), 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(); @@ -503,8 +584,11 @@ void tst_QAudioSink::pullSuspendResume() audioOutput.stop(); QTest::qWait(40); - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); @@ -513,68 +597,71 @@ void tst_QAudioSink::pullSuspendResume() audioFile->close(); } -class AudioPullSource : public QIODevice +void tst_QAudioSink::pullResumeFromUnderrun() { - Q_OBJECT -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; } + 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 bytesAvailable() const override { return available; } + bool atEnd() const override { return signalEnd && available == 0; } - qint64 available = 0; - bool signalEnd = false; -}; + qint64 available = 0; + bool signalEnd = false; + }; + + constexpr int chunkSize = 128; -void tst_QAudioSink::pullResumeFromUnderrun() -{ - AudioPullSource audioSource; QAudioFormat format; format.setChannelCount(1); format.setSampleFormat(QAudioFormat::UInt8); - format.setSampleRate(1024); - QAudioSink audioOutput(format, this); + format.setSampleRate(8000); - QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); + AudioPullSource audioSource; + QAudioSink audioOutput(format, this); + QSignalSpy stateSignal(&audioOutput, &QAudioSink::stateChanged); audioSource.open(QIODeviceBase::ReadOnly); - audioSource.available = 128; + audioSource.available = chunkSize; + QCOMPARE(audioOutput.state(), QAudio::StoppedState); audioOutput.start(&audioSource); - QTRY_VERIFY(stateSignal.count() == 1); - QVERIFY(audioOutput.state() == QAudio::ActiveState); - QVERIFY(audioOutput.error() == QAudio::NoError); + QTRY_COMPARE(stateSignal.size(), 1); + QCOMPARE(audioOutput.state(), QAudio::ActiveState); + QCOMPARE(audioOutput.error(), QAudio::NoError); stateSignal.clear(); - QTRY_VERIFY(stateSignal.count() == 1); - QVERIFY(audioOutput.state() == QAudio::IdleState); - QVERIFY(audioOutput.error() == QAudio::UnderrunError); + QTRY_COMPARE(stateSignal.size(), 1); + QCOMPARE(audioOutput.state(), QAudio::IdleState); + QCOMPARE(audioOutput.error(), QAudio::UnderrunError); stateSignal.clear(); QTest::qWait(300); - audioSource.available = 128; + audioSource.available = chunkSize; audioSource.signalEnd = true; // Resume pull emit audioSource.readyRead(); - QTRY_VERIFY(stateSignal.count() == 1); - QVERIFY(audioOutput.state() == QAudio::ActiveState); - QVERIFY(audioOutput.error() == QAudio::NoError); - stateSignal.clear(); + 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); - QTRY_VERIFY(stateSignal.count() == 1); - QVERIFY(audioOutput.state() == QAudio::IdleState); - QVERIFY(audioOutput.error() == QAudio::NoError); + QCOMPARE(audioOutput.error(), QAudio::NoError); + QCOMPARE(audioOutput.state(), QAudio::IdleState); - QTRY_COMPARE(audioOutput.processedUSecs(), 250000); + // 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() @@ -586,7 +673,7 @@ void tst_QAudioSink::push() audioOutput.setVolume(0.1f); - QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -600,8 +687,11 @@ void tst_QAudioSink::push() QIODevice* feed = audioOutput.start(); // Check that QAudioSink immediately transitions to IdleState - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -612,33 +702,29 @@ void tst_QAudioSink::push() QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()"); qint64 written = 0; - bool firstBuffer = true; - while (written < audioFile->size() - QWaveDecoder::headerLength()) { - - if (audioOutput.bytesFree() > 0) { - auto buffer = audioFile->read(audioOutput.bytesFree()); - written += feed->write(buffer); - - 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()).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"); - firstBuffer = false; - stateSignal.clear(); - } - } else - QTest::qWait(20); - } + 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.count() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), 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(); @@ -646,8 +732,11 @@ void tst_QAudioSink::push() audioOutput.stop(); QTest::qWait(40); - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); @@ -665,7 +754,7 @@ void tst_QAudioSink::pushSuspendResume() audioOutput.setVolume(0.1f); - QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -679,8 +768,11 @@ void tst_QAudioSink::pushSuspendResume() QIODevice* feed = audioOutput.start(); // Check that QAudioSink immediately transitions to IdleState - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -690,35 +782,35 @@ void tst_QAudioSink::pushSuspendResume() 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; + 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 - while (written < (audioFile->size() - QWaveDecoder::headerLength()) / 2) { - - if (audioOutput.bytesFree() > 0) { - auto buffer = audioFile->read(audioOutput.bytesFree()); - written += feed->write(buffer); - - 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()).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"); - firstBuffer = false; - } - } else - QTest::qWait(20); - } + pushDataToAudioSink(audioOutput, *audioFile, *feed, written, wavDataSize(*audioFile) / 2, + firstHalfChecker, true); + stateSignal.clear(); + const auto suspendedInState = audioOutput.state(); audioOutput.suspend(); - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead") - .arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -737,27 +829,30 @@ void tst_QAudioSink::pushSuspendResume() QTest::qWait(20); // Check that QAudioSink immediately transitions to IdleState - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); - QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after resume()"); + 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 - while (!audioFile->atEnd()) { - if (audioOutput.bytesFree() > 0) { - auto buffer = audioFile->read(audioOutput.bytesFree()); - written += feed->write(buffer); - QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after writing audio data"); - } else - QTest::qWait(20); - } + + 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.count() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), 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(); @@ -765,8 +860,11 @@ void tst_QAudioSink::pushSuspendResume() audioOutput.stop(); QTest::qWait(40); - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); @@ -775,6 +873,46 @@ void tst_QAudioSink::pushSuspendResume() 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); @@ -784,7 +922,7 @@ void tst_QAudioSink::pushUnderrun() audioOutput.setVolume(0.1f); - QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -798,8 +936,11 @@ void tst_QAudioSink::pushUnderrun() QIODevice* feed = audioOutput.start(); // Check that QAudioSink immediately transitions to IdleState - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -810,64 +951,61 @@ void tst_QAudioSink::pushUnderrun() 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() - QWaveDecoder::headerLength()) / 2) { - - if (audioOutput.bytesFree() > 0) { - auto buffer = audioFile->read(audioOutput.bytesFree()); - written += feed->write(buffer); - - 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()).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"); - firstBuffer = false; - } - } else - QTest::qWait(20); - } + + 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.count() == 1), - QString("didn't emit IdleState signal after suspend(), got %1 signals instead") - .arg(stateSignal.count()).toUtf8().constData()); + 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(); - firstBuffer = true; // Play rest of the clip - while (!audioFile->atEnd()) { - if (audioOutput.bytesFree() > 0) { - auto buffer = audioFile->read(audioOutput.bytesFree()); - written += feed->write(buffer); - 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()).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"); - firstBuffer = false; - } - } else - QTest::qWait(20); - } + 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.count() == 1), - QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -875,8 +1013,11 @@ void tst_QAudioSink::pushUnderrun() audioOutput.stop(); QTest::qWait(40); - QVERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); diff --git a/tests/auto/integration/qaudiosource/CMakeLists.txt b/tests/auto/integration/qaudiosource/CMakeLists.txt index e9026e963..0e572e804 100644 --- a/tests/auto/integration/qaudiosource/CMakeLists.txt +++ b/tests/auto/integration/qaudiosource/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + ##################################################################### ## tst_qaudiosource Test: ##################################################################### @@ -5,7 +8,7 @@ qt_internal_add_test(tst_qaudiosource SOURCES tst_qaudiosource.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp index d343fb8a8..ae100a08b 100644 --- a/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp +++ b/tests/auto/integration/qaudiosource/tst_qaudiosource.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QtCore/qlocale.h> @@ -40,8 +15,6 @@ #include <qwavedecoder.h> -//TESTED_COMPONENT=src/multimedia - #define RANGE_ERR 0.5 template<typename T> inline bool qTolerantCompare(T value, T expected) @@ -101,7 +74,7 @@ private: QScopedPointer<QByteArray> m_byteArray; QScopedPointer<QBuffer> m_buffer; - bool m_inCISystem; + bool m_inCISystem = false; }; void tst_QAudioSource::generate_audiofile_testrows() @@ -109,8 +82,8 @@ void tst_QAudioSource::generate_audiofile_testrows() QTest::addColumn<FilePtr>("audioFile"); QTest::addColumn<QAudioFormat>("audioFormat"); - for (int i=0; i<audioFiles.count(); i++) { - QTest::newRow(QString("%1").arg(i).toUtf8().constData()) + 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 @@ -121,16 +94,30 @@ void tst_QAudioSource::generate_audiofile_testrows() QString tst_QAudioSource::formatToFileName(const QAudioFormat &format) { - return QString("%1_%2_%3") - .arg(format.sampleRate()) - .arg(format.bytesPerSample()) - .arg(format.channelCount()); + return QStringLiteral("%1_%2_%3") + .arg(format.sampleRate()) + .arg(format.bytesPerSample()) + .arg(format.channelCount()); } void tst_QAudioSource::initTestCase() { - // Only perform tests if audio output device exists - const QList<QAudioDevice> devices = QMediaDevices::audioOutputs(); +#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"); @@ -184,11 +171,10 @@ void tst_QAudioSource::initTestCase() QVERIFY(m_temporaryDir->isValid()); const QString temporaryAudioPath = m_temporaryDir->path() + slash; - for (const QAudioFormat &format : qAsConst(testFormats)) { + for (const QAudioFormat &format : std::as_const(testFormats)) { const QString fileName = temporaryAudioPath + formatToFileName(format) + QStringLiteral(".wav"); audioFiles.append(FilePtr::create(fileName)); } - qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10); } void tst_QAudioSource::format() @@ -199,11 +185,23 @@ void tst_QAudioSource::format() QAudioFormat actual = audioInput.format(); QVERIFY2((requested.channelCount() == actual.channelCount()), - QString("channels: requested=%1, actual=%2").arg(requested.channelCount()).arg(actual.channelCount()).toUtf8().constData()); + QStringLiteral("channels: requested=%1, actual=%2") + .arg(requested.channelCount()) + .arg(actual.channelCount()) + .toUtf8() + .constData()); QVERIFY2((requested.sampleRate() == actual.sampleRate()), - QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toUtf8().constData()); + QStringLiteral("sampleRate: requested=%1, actual=%2") + .arg(requested.sampleRate()) + .arg(actual.sampleRate()) + .toUtf8() + .constData()); QVERIFY2((requested.sampleFormat() == actual.sampleFormat()), - QString("sampleFormat: requested=%1, actual=%2").arg((ushort)requested.sampleFormat()).arg((ushort)actual.sampleFormat()).toUtf8().constData()); + QStringLiteral("sampleFormat: requested=%1, actual=%2") + .arg((ushort)requested.sampleFormat()) + .arg((ushort)actual.sampleFormat()) + .toUtf8() + .constData()); QCOMPARE(actual, requested); } @@ -260,17 +258,26 @@ void tst_QAudioSource::bufferSize() 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()).toUtf8().constData()); + 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), - QString("bufferSize: requested=4096, actual=%2").arg(audioInput.bufferSize()).toUtf8().constData()); + 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), - QString("bufferSize: requested=8192, actual=%2").arg(audioInput.bufferSize()).toUtf8().constData()); + QStringLiteral("bufferSize: requested=8192, actual=%2") + .arg(audioInput.bufferSize()) + .toUtf8() + .constData()); } void tst_QAudioSource::stopWhileStopped() @@ -285,11 +292,11 @@ void tst_QAudioSource::stopWhileStopped() 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))); + QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged); audioInput.stop(); // Check that no state transition occurred - QVERIFY2((stateSignal.count() == 0), "stop() while stopped is emitting a signal and it shouldn't"); + 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()"); } @@ -305,11 +312,11 @@ void tst_QAudioSource::suspendWhileStopped() 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))); + QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged); audioInput.suspend(); // Check that no state transition occurred - QVERIFY2((stateSignal.count() == 0), "stop() while suspended is emitting a signal and it shouldn't"); + 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()"); } @@ -325,11 +332,11 @@ void tst_QAudioSource::resumeWhileStopped() 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))); + QSignalSpy stateSignal(&audioInput, &QAudioSource::stateChanged); audioInput.resume(); // Check that no state transition occurred - QVERIFY2((stateSignal.count() == 0), "resume() while stopped is emitting a signal and it shouldn't"); + 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()"); } @@ -340,7 +347,7 @@ void tst_QAudioSource::pull() QAudioSource audioInput(audioFormat, this); - QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -360,7 +367,7 @@ void tst_QAudioSource::pull() audioInput.start(audioFile.data()); // Check that QAudioSource immediately transitions to ActiveState or IdleState - QTRY_VERIFY2((stateSignal.count() > 0),"didn't emit signals on start()"); + 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()"); @@ -371,17 +378,25 @@ void tst_QAudioSource::pull() QTRY_VERIFY2((audioInput.processedUSecs() > 0), "elapsedUSecs() is still zero after start()"); // Allow some recording to happen - QTest::qWait(300); // .3 seconds should be plenty + QTest::qWait(3000); // 3 seconds should be plenty stateSignal.clear(); qint64 processedUs = audioInput.processedUSecs(); - QVERIFY2(qTolerantCompare(processedUs, 300000LL), - QString("processedUSecs() doesn't fall in acceptable range, should be 300000 (%1)").arg(processedUs).toUtf8().constData()); + 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.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); @@ -396,16 +411,12 @@ void tst_QAudioSource::pull() void tst_QAudioSource::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); QAudioSource audioInput(audioFormat, this); - QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -425,7 +436,7 @@ void tst_QAudioSource::pullSuspendResume() audioInput.start(audioFile.data()); // Check that QAudioSource immediately transitions to ActiveState or IdleState - QTRY_VERIFY2((stateSignal.count() > 0),"didn't emit signals on start()"); + 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()"); @@ -436,7 +447,7 @@ void tst_QAudioSource::pullSuspendResume() QTRY_VERIFY2((audioInput.processedUSecs() > 0), "elapsedUSecs() is still zero after start()"); // Allow some recording to happen - QTest::qWait(300); // .3 seconds should be plenty + QTest::qWait(3000); // 3 seconds should be plenty QVERIFY2((audioInput.state() == QAudio::ActiveState), "didn't transition to ActiveState after some recording"); @@ -446,8 +457,13 @@ void tst_QAudioSource::pullSuspendResume() audioInput.suspend(); - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -455,24 +471,35 @@ void tst_QAudioSource::pullSuspendResume() // Check that only 'elapsed', and not 'processed' increases while suspended qint64 elapsedUs = audioInput.elapsedUSecs(); qint64 processedUs = audioInput.processedUSecs(); - QVERIFY2(qTolerantCompare(processedUs, 300000LL), - QString("processedUSecs() doesn't fall in acceptable range, should be 300000 (%1)").arg(processedUs).toUtf8().constData()); + 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.count() == 1), - QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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()"); @@ -491,7 +518,7 @@ void tst_QAudioSource::push() QAudioSource audioInput(audioFormat, this); - QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -514,7 +541,7 @@ void tst_QAudioSource::push() QIODevice* feed = audioInput.start(); // Check that QAudioSource immediately transitions to IdleState - QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit IdleState signal on start()"); + 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()"); @@ -534,7 +561,7 @@ void tst_QAudioSource::push() totalBytesRead += buffer.size(); if (firstBuffer && buffer.size()) { // Check for transition to ActiveState when data is provided - QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit ActiveState signal on data"); + 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()"); @@ -547,12 +574,20 @@ void tst_QAudioSource::push() qint64 processedUs = audioInput.processedUSecs(); audioInput.stop(); - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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), - QString("processedUSecs() doesn't fall in acceptable range, should be 500000 (%1)").arg(processedUs).toUtf8().constData()); + 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"); @@ -574,7 +609,7 @@ void tst_QAudioSource::pushSuspendResume() audioInput.setBufferSize(audioFormat.bytesForDuration(100000)); - QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -594,7 +629,7 @@ void tst_QAudioSource::pushSuspendResume() QIODevice* feed = audioInput.start(); // Check that QAudioSource immediately transitions to IdleState - QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit IdleState signal on start()"); + 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()"); @@ -613,7 +648,7 @@ void tst_QAudioSource::pushSuspendResume() totalBytesRead += buffer.size(); if (firstBuffer && buffer.size()) { // Check for transition to ActiveState when data is provided - QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit ActiveState signal on data"); + 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()"); @@ -624,8 +659,13 @@ void tst_QAudioSource::pushSuspendResume() audioInput.suspend(); - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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(); @@ -644,7 +684,7 @@ void tst_QAudioSource::pushSuspendResume() audioInput.resume(); // Check that QAudioSource immediately transitions to Active or IdleState - QTRY_VERIFY2((stateSignal.count() > 0),"didn't emit signals on resume()"); + 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()"); @@ -665,12 +705,20 @@ void tst_QAudioSource::pushSuspendResume() processedUs = audioInput.processedUSecs(); audioInput.stop(); - QTRY_VERIFY2((stateSignal.count() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toUtf8().constData()); + 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), - QString("processedUSecs() doesn't fall in acceptable range, should be 2040000 (%1)").arg(processedUs).toUtf8().constData()); + 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()); @@ -687,7 +735,7 @@ void tst_QAudioSource::reset() { QAudioSource audioInput(audioFormat, this); - QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -696,7 +744,7 @@ void tst_QAudioSource::reset() QIODevice* device = audioInput.start(); // Check that QAudioSource immediately transitions to IdleState - QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit IdleState signal on start()"); + 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); @@ -707,7 +755,7 @@ void tst_QAudioSource::reset() stateSignal.clear(); audioInput.reset(); - QTRY_VERIFY2((stateSignal.count() == 1),"didn't emit StoppedState signal after 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()"); } @@ -717,7 +765,7 @@ void tst_QAudioSource::reset() QBuffer buffer; buffer.open(QIODevice::WriteOnly); - QSignalSpy stateSignal(&audioInput, SIGNAL(stateChanged(QAudio::State))); + 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()"); @@ -727,13 +775,13 @@ void tst_QAudioSource::reset() audioInput.start(&buffer); // Check that QAudioSource immediately transitions to ActiveState - QTRY_VERIFY2((stateSignal.count() >= 1),"didn't emit state changed signal on start()"); + 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.count() >= 1),"didn't emit StoppedState signal after 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()"); } diff --git a/tests/auto/integration/qcamerabackend/BLACKLIST b/tests/auto/integration/qcamerabackend/BLACKLIST index 0521c9fbd..e958c2103 100644 --- a/tests/auto/integration/qcamerabackend/BLACKLIST +++ b/tests/auto/integration/qcamerabackend/BLACKLIST @@ -1,3 +1,4 @@ +ci [testCameraCaptureMetadata] osx diff --git a/tests/auto/integration/qcamerabackend/CMakeLists.txt b/tests/auto/integration/qcamerabackend/CMakeLists.txt index c89456e8a..07a6a4cae 100644 --- a/tests/auto/integration/qcamerabackend/CMakeLists.txt +++ b/tests/auto/integration/qcamerabackend/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qcamerabackend.pro. ##################################################################### @@ -7,14 +10,17 @@ qt_internal_add_test(tst_qcamerabackend SOURCES tst_qcamerabackend.cpp - PUBLIC_LIBRARIES + ../shared/mediabackendutils.h + INCLUDE_DIRECTORIES + ../shared/ + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::Widgets ) # special case begin -if (APPLE) +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() diff --git a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp index 0edc9256e..63790dfd2 100644 --- a/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp +++ b/tests/auto/integration/qcamerabackend/tst_qcamerabackend.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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> @@ -34,9 +7,11 @@ #include <QtCore/qlocale.h> #include <QDebug> #include <QVideoSink> +#include <QMediaPlayer> #include <private/qplatformcamera_p.h> #include <private/qplatformimagecapture_p.h> +#include <private/qplatformmediaintegration_p.h> #include <qcamera.h> #include <qcameradevice.h> #include <qimagecapture.h> @@ -47,6 +22,12 @@ #include <qmediaplayer.h> #include <qaudiooutput.h> +#ifdef Q_OS_DARWIN +#include <QtCore/private/qcore_mac_p.h> +#endif + +#include <mediabackendutils.h> + QT_USE_NAMESPACE /* @@ -83,6 +64,8 @@ private slots: void testNativeMetadata(); + void multipleCameraSet(); + private: bool noCamera = false; }; @@ -116,6 +99,14 @@ public Q_SLOTS: 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; } @@ -222,8 +213,8 @@ void tst_QCameraBackend::testCameraActive() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error, const QString &))); - QSignalSpy activeChangedSignal(&camera, SIGNAL(activeChanged(bool))); + QSignalSpy errorSignal(&camera, &QCamera::errorOccurred); + QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged); QCOMPARE(camera.isActive(), false); @@ -233,7 +224,7 @@ void tst_QCameraBackend::testCameraActive() QCOMPARE(camera.error(), QCamera::NoError); camera.start(); - QCOMPARE(camera.isActive(), true); + QTRY_COMPARE(camera.isActive(), true); QTRY_COMPARE(activeChangedSignal.size(), 1); QCOMPARE(activeChangedSignal.last().first().value<bool>(), true); @@ -246,6 +237,14 @@ void tst_QCameraBackend::testCameraActive() void tst_QCameraBackend::testCameraStartParallel() { +#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"); @@ -266,8 +265,8 @@ void tst_QCameraBackend::testCameraStartParallel() QCOMPARE(camera2.isActive(), true); QCOMPARE(camera2.error(), QCamera::NoError); - QCOMPARE(errorSpy1.count(), 0); - QCOMPARE(errorSpy2.count(), 0); + QCOMPARE(errorSpy1.size(), 0); + QCOMPARE(errorSpy2.size(), 0); } void tst_QCameraBackend::testCameraFormat() @@ -278,15 +277,15 @@ void tst_QCameraBackend::testCameraFormat() if (videoFormats.isEmpty()) QSKIP("No Camera available, skipping test."); QCameraFormat cameraFormat = videoFormats.first(); - QSignalSpy spy(&camera, SIGNAL(cameraFormatChanged())); - QVERIFY(spy.count() == 0); + QSignalSpy spy(&camera, &QCamera::cameraFormatChanged); + QVERIFY(spy.size() == 0); QMediaCaptureSession session; session.setCamera(&camera); - QVERIFY(videoFormats.count()); + QVERIFY(videoFormats.size()); camera.setCameraFormat(cameraFormat); QCOMPARE(camera.cameraFormat(), cameraFormat); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); TestVideoFormat videoFormatTester(cameraFormat); session.setVideoOutput(&videoFormatTester); @@ -296,11 +295,11 @@ void tst_QCameraBackend::testCameraFormat() spy.clear(); camera.stop(); // Change camera format - if (videoFormats.count() > 1) { + if (videoFormats.size() > 1) { QCameraFormat secondFormat = videoFormats.at(1); camera.setCameraFormat(secondFormat); QCOMPARE(camera.cameraFormat(), secondFormat); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(camera.cameraFormat(), secondFormat); videoFormatTester.setCameraFormatToTest(secondFormat); camera.start(); @@ -315,7 +314,7 @@ void tst_QCameraBackend::testCameraFormat() spy.clear(); camera.stop(); camera.setCameraFormat({}); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); videoFormatTester.setCameraFormatToTest({}); camera.start(); // In case of a null format, the backend should have picked @@ -326,7 +325,7 @@ void tst_QCameraBackend::testCameraFormat() spy.clear(); // Shouldn't change anything as it's the same device camera.setCameraDevice(device); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); } void tst_QCameraBackend::testCameraCapture() @@ -342,9 +341,9 @@ void tst_QCameraBackend::testCameraCapture() QVERIFY(!imageCapture.isReadyForCapture()); - QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage))); - QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString))); - QSignalSpy errorSignal(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,const QString&))); + QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured); + QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); + QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred); imageCapture.captureToFile(); QTRY_COMPARE(errorSignal.size(), 1); @@ -365,7 +364,7 @@ void tst_QCameraBackend::testCameraCapture() 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); @@ -401,10 +400,10 @@ void tst_QCameraBackend::testCaptureToBuffer() 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(errorOccurred(int,QImageCapture::Error,const 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()); @@ -446,8 +445,14 @@ void tst_QCameraBackend::testCameraCaptureMetadata() camera.setFlashMode(QCamera::FlashOff); - QSignalSpy metadataSignal(&imageCapture, SIGNAL(imageMetadataAvailable(int,const QMediaMetaData&))); - QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString))); + 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, &QImageCapture::imageMetadataAvailable); + QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); camera.start(); @@ -458,7 +463,19 @@ void tst_QCameraBackend::testCameraCaptureMetadata() 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() @@ -470,22 +487,22 @@ void tst_QCameraBackend::testExposureCompensation() QCamera camera; session.setCamera(&camera); - QSignalSpy exposureCompensationSignal(&camera, SIGNAL(exposureCompensationChanged(float))); + QSignalSpy exposureCompensationSignal(&camera, &QCamera::exposureCompensationChanged); - //it should be possible to set exposure parameters in Unloaded state + // it should be possible to set exposure parameters in Unloaded state QCOMPARE(camera.exposureCompensation(), 0.); if (!(camera.supportedFeatures() & QCamera::Feature::ExposureCompensation)) return; camera.setExposureCompensation(1.0); QCOMPARE(camera.exposureCompensation(), 1.0); - QTRY_COMPARE(exposureCompensationSignal.count(), 1); + QTRY_COMPARE(exposureCompensationSignal.size(), 1); QCOMPARE(exposureCompensationSignal.last().first().toReal(), 1.0); //exposureCompensationChanged should not be emitted when value is not changed camera.setExposureCompensation(1.0); QTest::qWait(50); - QCOMPARE(exposureCompensationSignal.count(), 1); + QCOMPARE(exposureCompensationSignal.size(), 1); //exposure compensation should be preserved during start camera.start(); @@ -496,7 +513,7 @@ void tst_QCameraBackend::testExposureCompensation() exposureCompensationSignal.clear(); camera.setExposureCompensation(-1.0); QCOMPARE(camera.exposureCompensation(), -1.0); - QTRY_COMPARE(exposureCompensationSignal.count(), 1); + QTRY_COMPARE(exposureCompensationSignal.size(), 1); QCOMPARE(exposureCompensationSignal.last().first().toReal(), -1.0); } @@ -525,7 +542,7 @@ void tst_QCameraBackend::testExposureMode() camera.setExposureMode(QCamera::ExposureAuto); QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); camera.start(); - QVERIFY(camera.isActive()); + QTRY_VERIFY(camera.isActive()); QCOMPARE(camera.exposureMode(), QCamera::ExposureAuto); // Manual @@ -569,16 +586,16 @@ void tst_QCameraBackend::testVideoRecording() QMediaRecorder recorder; session.setRecorder(&recorder); - QSignalSpy errorSignal(camera.data(), SIGNAL(errorOccurred(QCamera::Error, const QString &))); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy recorderStateChanged(&recorder, SIGNAL(recorderStateChanged(RecorderState))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + QSignalSpy errorSignal(camera.data(), &QCamera::errorOccurred); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); + QSignalSpy recorderStateChanged(&recorder, &QMediaRecorder::recorderStateChanged); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); recorder.setVideoResolution(320, 240); // Insert metadata QMediaMetaData metaData; - metaData.insert(QMediaMetaData::Author, QString::fromUtf8("Author")); + metaData.insert(QMediaMetaData::Author, QStringLiteral("Author")); metaData.insert(QMediaMetaData::Date, QDateTime::currentDateTime()); recorder.setMetaData(metaData); @@ -591,27 +608,41 @@ void tst_QCameraBackend::testVideoRecording() QTRY_VERIFY(camera->isActive()); - for (int recordings = 0; recordings < 2; ++recordings) { - //record 200ms clip - recorder.record(); - durationChanged.clear(); - QTRY_VERIFY(durationChanged.count()); + recorder.record(); + if (!recorderErrorSignal.empty() || recorderErrorSignal.wait(550)) { + QEXPECT_FAIL_GSTREAMER("", "QTBUG-124148: GStreamer might return ResourceError", Continue); + + QCOMPARE(recorderErrorSignal.last().first().toInt(), QMediaRecorder::FormatError); + return; + } + + QTRY_VERIFY(durationChanged.size()); - QCOMPARE(recorder.metaData(), metaData); + QCOMPARE(recorder.metaData(), metaData); - recorderStateChanged.clear(); - recorder.stop(); - QTRY_VERIFY(recorderStateChanged.size() > 0); - QVERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); + recorderStateChanged.clear(); + recorder.stop(); + QTRY_VERIFY(recorderStateChanged.size() > 0); + QVERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); - QVERIFY(errorSignal.isEmpty()); - QVERIFY(recorderErrorSignal.isEmpty()); + QVERIFY(errorSignal.isEmpty()); + QVERIFY(recorderErrorSignal.isEmpty()); - QString fileName = recorder.actualLocation().toLocalFile(); - QVERIFY(!fileName.isEmpty()); - QVERIFY(QFileInfo(fileName).size() > 0); - QFile(fileName).remove(); - } + 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() @@ -627,10 +658,10 @@ void tst_QCameraBackend::testNativeMetadata() QMediaRecorder recorder; session.setRecorder(&recorder); - QSignalSpy errorSignal(&camera, SIGNAL(errorOccurred(QCamera::Error, const QString &))); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy recorderStateChanged(&recorder, SIGNAL(recorderStateChanged(RecorderState))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + 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()) { @@ -643,15 +674,15 @@ void tst_QCameraBackend::testNativeMetadata() // 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, QString::fromUtf8("Title")); + metaData.insert(QMediaMetaData::Title, QStringLiteral("Title")); metaData.insert(QMediaMetaData::Language, QVariant::fromValue(QLocale::German)); - metaData.insert(QMediaMetaData::Description, QString::fromUtf8("Description")); + metaData.insert(QMediaMetaData::Description, QStringLiteral("Description")); recorder.setMetaData(metaData); recorder.record(); - durationChanged.clear(); - QTRY_VERIFY(durationChanged.count()); + QTRY_VERIFY(durationChanged.size()); + QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecorderState::RecordingState); QCOMPARE(recorder.metaData(), metaData); @@ -659,9 +690,13 @@ void tst_QCameraBackend::testNativeMetadata() recorder.stop(); QTRY_VERIFY(recorderStateChanged.size() > 0); + QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecorderState::StoppedState); QVERIFY(errorSignal.isEmpty()); - QVERIFY(recorderErrorSignal.isEmpty()); + if (!isGStreamerPlatform()) { + // https://bugreports.qt.io/browse/QTBUG-124183 + QVERIFY(recorderErrorSignal.isEmpty()); + } QString fileName = recorder.actualLocation().toLocalFile(); QVERIFY(!fileName.isEmpty()); @@ -669,30 +704,60 @@ void tst_QCameraBackend::testNativeMetadata() // 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 - QMediaPlayer player; QAudioOutput output; + QMediaPlayer player; player.setAudioOutput(&output); - QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged())); + QSignalSpy metadataChangedSpy(&player, &QMediaPlayer::metaDataChanged); player.setSource(QUrl::fromLocalFile(fileName)); player.play(); - QTRY_VERIFY(metadataChangedSpy.count() > 0); + int metadataChangedRequiredCount = isGStreamerPlatform() ? 2 : 1; + + QTRY_VERIFY(metadataChangedSpy.size() >= metadataChangedRequiredCount); - QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(), metaData.value(QMediaMetaData::Title).toString()); + 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()); - metadataChangedSpy.clear(); + 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); + + 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) #include "tst_qcamerabackend.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 index 63ac2e99f..1aec26493 100644 --- a/tests/auto/integration/qmediacapturesession/CMakeLists.txt +++ b/tests/auto/integration/qmediacapturesession/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qcamerabackend.pro. ##################################################################### @@ -7,7 +10,10 @@ qt_internal_add_test(tst_qmediacapturesession SOURCES tst_qmediacapturesession.cpp - PUBLIC_LIBRARIES + ../shared/mediabackendutils.h + INCLUDE_DIRECTORIES + ../shared/ + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::MultimediaWidgets diff --git a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp index a5a3741b2..0a42851e5 100644 --- a/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp +++ b/tests/auto/integration/qmediacapturesession/tst_qmediacapturesession.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QtGui/QImageReader> @@ -34,6 +7,7 @@ #include <QDebug> #include <QVideoSink> #include <QVideoWidget> +#include <QSysInfo> #include <qcamera.h> #include <qcameradevice.h> @@ -47,11 +21,17 @@ #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 /* @@ -69,6 +49,12 @@ private slots: 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(); @@ -77,9 +63,16 @@ private slots: 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(); @@ -101,6 +94,8 @@ private slots: 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); @@ -112,8 +107,8 @@ void tst_QMediaCaptureSession::recordOk(QMediaCaptureSession &session) QMediaRecorder recorder; session.setRecorder(&recorder); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); recorder.record(); QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 2000); @@ -132,12 +127,12 @@ void tst_QMediaCaptureSession::recordOk(QMediaCaptureSession &session) void tst_QMediaCaptureSession::recordFail(QMediaCaptureSession &session) { QMediaRecorder recorder; - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); session.setRecorder(&recorder); recorder.record(); - QTRY_VERIFY_WITH_TIMEOUT(recorderErrorSignal.count() == 1, 2000); + QTRY_VERIFY_WITH_TIMEOUT(recorderErrorSignal.size() == 1, 2000); QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::StoppedState, 2000); } @@ -162,6 +157,132 @@ void tst_QMediaCaptureSession::stress_test_setup_and_teardown() } } +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; @@ -174,17 +295,18 @@ void tst_QMediaCaptureSession::record_video_without_preview() session.setRecorder(&recorder); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); session.setCamera(&camera); camera.setActive(true); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); + QTRY_COMPARE(camera.isActive(), true); recordOk(session); QVERIFY(!QTest::currentTestFailed()); session.setCamera(nullptr); - QTRY_COMPARE(cameraChanged.count(), 2); + QTRY_COMPARE(cameraChanged.size(), 2); // can't record without audio and video recordFail(session); @@ -198,29 +320,29 @@ void tst_QMediaCaptureSession::can_add_and_remove_AudioInput_with_and_without_Au QSKIP("No audio input available"); QMediaCaptureSession session; - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); - QSignalSpy audioOutputChanged(&session, SIGNAL(audioOutputChanged())); + QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged); + QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); session.setAudioInput(nullptr); - QTRY_COMPARE(audioInputChanged.count(), 2); + QTRY_COMPARE(audioInputChanged.size(), 2); QAudioOutput output; if (output.device().isNull()) return; session.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged.count(), 1); + QTRY_COMPARE(audioOutputChanged.size(), 1); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 3); + QTRY_COMPARE(audioInputChanged.size(), 3); session.setAudioOutput(nullptr); - QTRY_COMPARE(audioOutputChanged.count(), 2); + QTRY_COMPARE(audioOutputChanged.size(), 2); session.setAudioInput(nullptr); - QTRY_COMPARE(audioInputChanged.count(), 4); + QTRY_COMPARE(audioInputChanged.size(), 4); } void tst_QMediaCaptureSession::can_change_AudioDevices_on_attached_AudioInput() @@ -230,25 +352,25 @@ void tst_QMediaCaptureSession::can_change_AudioDevices_on_attached_AudioInput() QSKIP("Two audio inputs are not available"); QAudioInput input(audioInputs[0]); - QSignalSpy deviceChanged(&input, SIGNAL(deviceChanged())); + QSignalSpy deviceChanged(&input, &QAudioInput::deviceChanged); QMediaCaptureSession session; - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); + QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); recordOk(session); QVERIFY(!QTest::currentTestFailed()); input.setDevice(audioInputs[1]); - QTRY_COMPARE(deviceChanged.count(), 1); + QTRY_COMPARE(deviceChanged.size(), 1); recordOk(session); QVERIFY(!QTest::currentTestFailed()); input.setDevice(audioInputs[0]); - QTRY_COMPARE(deviceChanged.count(), 2); + QTRY_COMPARE(deviceChanged.size(), 2); recordOk(session); QVERIFY(!QTest::currentTestFailed()); @@ -265,27 +387,27 @@ void tst_QMediaCaptureSession::can_change_AudioInput_during_recording() session.setRecorder(&recorder); - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); recorder.record(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState); QVERIFY(durationChanged.wait(2000)); session.setAudioInput(nullptr); - QTRY_COMPARE(audioInputChanged.count(), 2); + QTRY_COMPARE(audioInputChanged.size(), 2); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 3); + QTRY_COMPARE(audioInputChanged.size(), 3); recorder.stop(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); QVERIFY(recorderErrorSignal.isEmpty()); session.setAudioInput(nullptr); - QTRY_COMPARE(audioInputChanged.count(), 4); + QTRY_COMPARE(audioInputChanged.size(), 4); QString fileName = recorder.actualLocation().toLocalFile(); QVERIFY(!fileName.isEmpty()); @@ -299,14 +421,14 @@ void tst_QMediaCaptureSession::disconnects_deleted_AudioInput() QSKIP("No audio input available"); QMediaCaptureSession session; - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); + QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged); { QAudioInput input; session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); } QVERIFY(session.audioInput() == nullptr); - QTRY_COMPARE(audioInputChanged.count(), 2); + QTRY_COMPARE(audioInputChanged.size(), 2); } void tst_QMediaCaptureSession::can_move_AudioInput_between_sessions() @@ -316,24 +438,24 @@ void tst_QMediaCaptureSession::can_move_AudioInput_between_sessions() QMediaCaptureSession session0; QMediaCaptureSession session1; - QSignalSpy audioInputChanged0(&session0, SIGNAL(audioInputChanged())); - QSignalSpy audioInputChanged1(&session1, SIGNAL(audioInputChanged())); + QSignalSpy audioInputChanged0(&session0, &QMediaCaptureSession::audioInputChanged); + QSignalSpy audioInputChanged1(&session1, &QMediaCaptureSession::audioInputChanged); QAudioInput input; { QMediaCaptureSession session2; - QSignalSpy audioInputChanged2(&session2, SIGNAL(audioInputChanged())); + QSignalSpy audioInputChanged2(&session2, &QMediaCaptureSession::audioInputChanged); session2.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged2.count(), 1); + QTRY_COMPARE(audioInputChanged2.size(), 1); } session0.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged0.count(), 1); + QTRY_COMPARE(audioInputChanged0.size(), 1); QVERIFY(session0.audioInput() != nullptr); session1.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged0.count(), 2); + QTRY_COMPARE(audioInputChanged0.size(), 2); QVERIFY(session0.audioInput() == nullptr); - QTRY_COMPARE(audioInputChanged1.count(), 1); + QTRY_COMPARE(audioInputChanged1.size(), 1); QVERIFY(session1.audioInput() != nullptr); } @@ -343,14 +465,14 @@ void tst_QMediaCaptureSession::disconnects_deleted_AudioOutput() QSKIP("No audio output available"); QMediaCaptureSession session; - QSignalSpy audioOutputChanged(&session, SIGNAL(audioOutputChanged())); + QSignalSpy audioOutputChanged(&session, &QMediaCaptureSession::audioOutputChanged); { QAudioOutput output; session.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged.count(), 1); + QTRY_COMPARE(audioOutputChanged.size(), 1); } QVERIFY(session.audioOutput() == nullptr); - QTRY_COMPARE(audioOutputChanged.count(), 2); + QTRY_COMPARE(audioOutputChanged.size(), 2); } void tst_QMediaCaptureSession::can_move_AudioOutput_between_sessions_and_player() @@ -358,46 +480,130 @@ 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, SIGNAL(audioOutputChanged())); - QSignalSpy audioOutputChanged1(&session1, SIGNAL(audioOutputChanged())); - QSignalSpy audioOutputChangedPlayer(&player, SIGNAL(audioOutputChanged())); + QSignalSpy audioOutputChanged0(&session0, &QMediaCaptureSession::audioOutputChanged); + QSignalSpy audioOutputChanged1(&session1, &QMediaCaptureSession::audioOutputChanged); + QSignalSpy audioOutputChangedPlayer(&player, &QMediaPlayer::audioOutputChanged); - QAudioOutput output; { QMediaCaptureSession session2; - QSignalSpy audioOutputChanged2(&session2, SIGNAL(audioOutputChanged())); + QSignalSpy audioOutputChanged2(&session2, &QMediaCaptureSession::audioOutputChanged); session2.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged2.count(), 1); + QTRY_COMPARE(audioOutputChanged2.size(), 1); } session0.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged0.count(), 1); + QTRY_COMPARE(audioOutputChanged0.size(), 1); QVERIFY(session0.audioOutput() != nullptr); session1.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged0.count(), 2); + QTRY_COMPARE(audioOutputChanged0.size(), 2); QVERIFY(session0.audioOutput() == nullptr); - QTRY_COMPARE(audioOutputChanged1.count(), 1); + QTRY_COMPARE(audioOutputChanged1.size(), 1); QVERIFY(session1.audioOutput() != nullptr); player.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged0.count(), 2); + QTRY_COMPARE(audioOutputChanged0.size(), 2); QVERIFY(session0.audioOutput() == nullptr); - QTRY_COMPARE(audioOutputChanged1.count(), 2); + QTRY_COMPARE(audioOutputChanged1.size(), 2); QVERIFY(session1.audioOutput() == nullptr); - QTRY_COMPARE(audioOutputChangedPlayer.count(), 1); + QTRY_COMPARE(audioOutputChangedPlayer.size(), 1); QVERIFY(player.audioOutput() != nullptr); session0.setAudioOutput(&output); - QTRY_COMPARE(audioOutputChanged0.count(), 3); + QTRY_COMPARE(audioOutputChanged0.size(), 3); QVERIFY(session0.audioOutput() != nullptr); - QTRY_COMPARE(audioOutputChangedPlayer.count(), 2); + 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() { @@ -411,17 +617,18 @@ void tst_QMediaCaptureSession::can_add_and_remove_Camera() session.setRecorder(&recorder); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); session.setCamera(&camera); camera.setActive(true); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); + QTRY_COMPARE(camera.isActive(), true); session.setCamera(nullptr); - QTRY_COMPARE(cameraChanged.count(), 2); + QTRY_COMPARE(cameraChanged.size(), 2); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 3); + QTRY_COMPARE(cameraChanged.size(), 3); recordOk(session); QVERIFY(!QTest::currentTestFailed()); @@ -431,31 +638,31 @@ void tst_QMediaCaptureSession::can_move_Camera_between_sessions() { QMediaCaptureSession session0; QMediaCaptureSession session1; - QSignalSpy cameraChanged0(&session0, SIGNAL(cameraChanged())); - QSignalSpy cameraChanged1(&session1, SIGNAL(cameraChanged())); + QSignalSpy cameraChanged0(&session0, &QMediaCaptureSession::cameraChanged); + QSignalSpy cameraChanged1(&session1, &QMediaCaptureSession::cameraChanged); { QCamera camera; { QMediaCaptureSession session2; - QSignalSpy cameraChanged2(&session2, SIGNAL(cameraChanged())); + QSignalSpy cameraChanged2(&session2, &QMediaCaptureSession::cameraChanged); session2.setCamera(&camera); - QTRY_COMPARE(cameraChanged2.count(), 1); + QTRY_COMPARE(cameraChanged2.size(), 1); } QVERIFY(camera.captureSession() == nullptr); session0.setCamera(&camera); - QTRY_COMPARE(cameraChanged0.count(), 1); + QTRY_COMPARE(cameraChanged0.size(), 1); QVERIFY(session0.camera() == &camera); QVERIFY(camera.captureSession() == &session0); session1.setCamera(&camera); - QTRY_COMPARE(cameraChanged0.count(), 2); + QTRY_COMPARE(cameraChanged0.size(), 2); QVERIFY(session0.camera() == nullptr); - QTRY_COMPARE(cameraChanged1.count(), 1); + QTRY_COMPARE(cameraChanged1.size(), 1); QVERIFY(session1.camera() == &camera); QVERIFY(camera.captureSession() == &session1); } - QTRY_COMPARE(cameraChanged1.count(), 2); + QTRY_COMPARE(cameraChanged1.size(), 2); QVERIFY(session1.camera() == nullptr); } @@ -471,21 +678,22 @@ void tst_QMediaCaptureSession::can_disconnect_Camera_when_recording() session.setRecorder(&recorder); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); + QSignalSpy recorderErrorSignal(&recorder, &QMediaRecorder::errorOccurred); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); camera.setActive(true); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); + QTRY_COMPARE(camera.isActive(), true); durationChanged.clear(); recorder.record(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState); - QTRY_VERIFY(durationChanged.count() > 0); + QTRY_VERIFY(durationChanged.size() > 0); session.setCamera(nullptr); - QTRY_COMPARE(cameraChanged.count(), 2); + QTRY_COMPARE(cameraChanged.size(), 2); recorder.stop(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); @@ -512,18 +720,20 @@ void tst_QMediaCaptureSession::can_add_and_remove_different_Cameras() session.setRecorder(&recorder); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); camera.setActive(true); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); + QTRY_COMPARE(camera.isActive(), true); session.setCamera(nullptr); - QTRY_COMPARE(cameraChanged.count(), 2); + QTRY_COMPARE(cameraChanged.size(), 2); session.setCamera(&camera2); camera2.setActive(true); - QTRY_COMPARE(cameraChanged.count(), 3); + QTRY_COMPARE(cameraChanged.size(), 3); + QTRY_COMPARE(camera2.isActive(), true); recordOk(session); QVERIFY(!QTest::currentTestFailed()); @@ -543,22 +753,25 @@ void tst_QMediaCaptureSession::can_change_CameraDevice_on_attached_Camera() session.setRecorder(&recorder); - QSignalSpy cameraDeviceChanged(&camera, SIGNAL(cameraDeviceChanged())); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); + QSignalSpy cameraDeviceChanged(&camera, &QCamera::cameraDeviceChanged); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 1); + 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.count(), 1); + QTRY_COMPARE(cameraDeviceChanged.size(), 1); + QTRY_COMPARE(camera.isActive(), true); recordOk(session); QVERIFY(!QTest::currentTestFailed()); @@ -577,26 +790,26 @@ void tst_QMediaCaptureSession::can_change_VideoOutput_with_and_without_camera() QMediaCaptureSession session; - QSignalSpy videoOutputChanged(&session, SIGNAL(videoOutputChanged())); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); + QSignalSpy videoOutputChanged(&session, &QMediaCaptureSession::videoOutputChanged); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); session.setVideoOutput(&videoOutput); - QTRY_COMPARE(videoOutputChanged.count(), 1); + QTRY_COMPARE(videoOutputChanged.size(), 1); session.setVideoOutput(nullptr); - QTRY_COMPARE(videoOutputChanged.count(), 2); + QTRY_COMPARE(videoOutputChanged.size(), 2); session.setVideoOutput(&videoOutput2); - QTRY_COMPARE(videoOutputChanged.count(), 3); + QTRY_COMPARE(videoOutputChanged.size(), 3); session.setCamera(nullptr); - QTRY_COMPARE(cameraChanged.count(), 2); + QTRY_COMPARE(cameraChanged.size(), 2); session.setVideoOutput(nullptr); - QTRY_COMPARE(videoOutputChanged.count(), 4); + QTRY_COMPARE(videoOutputChanged.size(), 4); } void tst_QMediaCaptureSession::can_change_VideoOutput_when_recording() @@ -613,27 +826,28 @@ void tst_QMediaCaptureSession::can_change_VideoOutput_when_recording() session.setRecorder(&recorder); - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); - QSignalSpy videoOutputChanged(&session, SIGNAL(videoOutputChanged())); + 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.count(), 1); + 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.count(), 1); + QTRY_COMPARE(videoOutputChanged.size(), 1); session.setVideoOutput(nullptr); - QTRY_COMPARE(videoOutputChanged.count(), 2); + QTRY_COMPARE(videoOutputChanged.size(), 2); session.setVideoOutput(&videoOutput); - QTRY_COMPARE(videoOutputChanged.count(), 3); + QTRY_COMPARE(videoOutputChanged.size(), 3); recorder.stop(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); @@ -655,20 +869,20 @@ void tst_QMediaCaptureSession::can_add_and_remove_recorders() QMediaRecorder recorder2; QMediaCaptureSession session; - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); - QSignalSpy recorderChanged(&session, SIGNAL(recorderChanged())); + QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged); + QSignalSpy recorderChanged(&session, &QMediaCaptureSession::recorderChanged); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); session.setRecorder(&recorder); - QTRY_COMPARE(recorderChanged.count(), 1); + QTRY_COMPARE(recorderChanged.size(), 1); session.setRecorder(&recorder2); - QTRY_COMPARE(recorderChanged.count(), 2); + QTRY_COMPARE(recorderChanged.size(), 2); session.setRecorder(&recorder); - QTRY_COMPARE(recorderChanged.count(), 3); + QTRY_COMPARE(recorderChanged.size(), 3); recordOk(session); QVERIFY(!QTest::currentTestFailed()); @@ -678,31 +892,31 @@ void tst_QMediaCaptureSession::can_move_Recorder_between_sessions() { QMediaCaptureSession session0; QMediaCaptureSession session1; - QSignalSpy recorderChanged0(&session0, SIGNAL(recorderChanged())); - QSignalSpy recorderChanged1(&session1, SIGNAL(recorderChanged())); + QSignalSpy recorderChanged0(&session0, &QMediaCaptureSession::recorderChanged); + QSignalSpy recorderChanged1(&session1, &QMediaCaptureSession::recorderChanged); { QMediaRecorder recorder; { QMediaCaptureSession session2; - QSignalSpy recorderChanged2(&session2, SIGNAL(recorderChanged())); + QSignalSpy recorderChanged2(&session2, &QMediaCaptureSession::recorderChanged); session2.setRecorder(&recorder); - QTRY_COMPARE(recorderChanged2.count(), 1); + QTRY_COMPARE(recorderChanged2.size(), 1); } QVERIFY(recorder.captureSession() == nullptr); session0.setRecorder(&recorder); - QTRY_COMPARE(recorderChanged0.count(), 1); + QTRY_COMPARE(recorderChanged0.size(), 1); QVERIFY(session0.recorder() == &recorder); QVERIFY(recorder.captureSession() == &session0); session1.setRecorder(&recorder); - QTRY_COMPARE(recorderChanged0.count(), 2); + QTRY_COMPARE(recorderChanged0.size(), 2); QVERIFY(session0.recorder() == nullptr); - QTRY_COMPARE(recorderChanged1.count(), 1); + QTRY_COMPARE(recorderChanged1.size(), 1); QVERIFY(session1.recorder() == &recorder); QVERIFY(recorder.captureSession() == &session1); } - QTRY_COMPARE(recorderChanged1.count(), 2); + QTRY_COMPARE(recorderChanged1.size(), 2); QVERIFY(session1.recorder() == nullptr); } @@ -721,10 +935,10 @@ void tst_QMediaCaptureSession::can_record_AudioInput_with_null_AudioDevice() QAudioInput input(nullDevice); QMediaCaptureSession session; - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); + QSignalSpy audioInputChanged(&session, &QMediaCaptureSession::audioInputChanged); session.setAudioInput(&input); - QTRY_COMPARE(audioInputChanged.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); recordOk(session); QVERIFY(!QTest::currentTestFailed()); @@ -739,12 +953,14 @@ void tst_QMediaCaptureSession::can_record_Camera_with_null_CameraDevice() QCamera camera(nullDevice); QMediaCaptureSession session; - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); + QSignalSpy cameraChanged(&session, &QMediaCaptureSession::cameraChanged); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); camera.setActive(true); + QTRY_COMPARE(camera.isActive(), true); + recordOk(session); QVERIFY(!QTest::currentTestFailed()); } @@ -758,23 +974,23 @@ void tst_QMediaCaptureSession::recording_stops_when_recorder_removed() QMediaRecorder recorder; QMediaCaptureSession session; - QSignalSpy audioInputChanged(&session, SIGNAL(audioInputChanged())); - QSignalSpy recorderChanged(&session, SIGNAL(recorderChanged())); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + 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.count(), 1); + QTRY_COMPARE(audioInputChanged.size(), 1); session.setRecorder(&recorder); - QTRY_COMPARE(recorderChanged.count(), 1); + QTRY_COMPARE(recorderChanged.size(), 1); recorder.record(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState); QVERIFY(durationChanged.wait(2000)); session.setRecorder(nullptr); - QTRY_COMPARE(recorderChanged.count(), 2); + QTRY_COMPARE(recorderChanged.size(), 2); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::StoppedState); QVERIFY(recorderErrorSignal.isEmpty()); @@ -795,75 +1011,78 @@ void tst_QMediaCaptureSession::can_add_and_remove_ImageCapture() QImageCapture capture; QMediaCaptureSession session; - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); - QSignalSpy imageCaptureChanged(&session, SIGNAL(imageCaptureChanged())); - QSignalSpy readyForCaptureChanged(&capture, SIGNAL(readyForCaptureChanged(bool))); + 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.count(), 1); + QTRY_COMPARE(imageCaptureChanged.size(), 1); QVERIFY(!capture.isAvailable()); QVERIFY(!capture.isReadyForCapture()); session.setCamera(&camera); - QTRY_COMPARE(cameraChanged.count(), 1); + QTRY_COMPARE(cameraChanged.size(), 1); QVERIFY(capture.isAvailable()); QVERIFY(!capture.isReadyForCapture()); camera.setActive(true); - QTRY_COMPARE(readyForCaptureChanged.count(), 1); + QTRY_COMPARE(camera.isActive(), true); + + QTRY_COMPARE(readyForCaptureChanged.size(), 1); QVERIFY(capture.isReadyForCapture()); session.setImageCapture(nullptr); - QTRY_COMPARE(imageCaptureChanged.count(), 2); - QTRY_COMPARE(readyForCaptureChanged.count(), 2); + QTRY_COMPARE(imageCaptureChanged.size(), 2); + QTRY_COMPARE(readyForCaptureChanged.size(), 2); QVERIFY(!capture.isAvailable()); QVERIFY(!capture.isReadyForCapture()); session.setImageCapture(&capture); - QTRY_COMPARE(imageCaptureChanged.count(), 3); - QTRY_COMPARE(readyForCaptureChanged.count(), 3); + 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, SIGNAL(imageCaptureChanged())); - QSignalSpy imageCaptureChanged1(&session1, SIGNAL(imageCaptureChanged())); + QSignalSpy imageCaptureChanged0(&session0, &QMediaCaptureSession::imageCaptureChanged); + QSignalSpy imageCaptureChanged1(&session1, &QMediaCaptureSession::imageCaptureChanged); { QImageCapture imageCapture; { QMediaCaptureSession session2; - QSignalSpy imageCaptureChanged2(&session2, SIGNAL(imageCaptureChanged())); + QSignalSpy imageCaptureChanged2(&session2, &QMediaCaptureSession::imageCaptureChanged); session2.setImageCapture(&imageCapture); - QTRY_COMPARE(imageCaptureChanged2.count(), 1); + QTRY_COMPARE(imageCaptureChanged2.size(), 1); } QVERIFY(imageCapture.captureSession() == nullptr); session0.setImageCapture(&imageCapture); - QTRY_COMPARE(imageCaptureChanged0.count(), 1); + QTRY_COMPARE(imageCaptureChanged0.size(), 1); QVERIFY(session0.imageCapture() == &imageCapture); QVERIFY(imageCapture.captureSession() == &session0); session1.setImageCapture(&imageCapture); - QTRY_COMPARE(imageCaptureChanged0.count(), 2); + QTRY_COMPARE(imageCaptureChanged0.size(), 2); QVERIFY(session0.imageCapture() == nullptr); - QTRY_COMPARE(imageCaptureChanged1.count(), 1); + QTRY_COMPARE(imageCaptureChanged1.size(), 1); QVERIFY(session1.imageCapture() == &imageCapture); QVERIFY(imageCapture.captureSession() == &session1); } - QTRY_COMPARE(imageCaptureChanged1.count(), 2); + QTRY_COMPARE(imageCaptureChanged1.size(), 2); QVERIFY(session1.imageCapture() == nullptr); } - void tst_QMediaCaptureSession::capture_is_not_available_when_Camera_is_null() { QCamera camera; @@ -874,26 +1093,27 @@ void tst_QMediaCaptureSession::capture_is_not_available_when_Camera_is_null() QImageCapture capture; QMediaCaptureSession session; - QSignalSpy cameraChanged(&session, SIGNAL(cameraChanged())); - QSignalSpy capturedSignal(&capture, SIGNAL(imageCaptured(int,QImage))); - QSignalSpy readyForCaptureChanged(&capture, SIGNAL(readyForCaptureChanged(bool))); + 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.count(), 1); + QTRY_COMPARE(readyForCaptureChanged.size(), 1); QVERIFY(capture.isReadyForCapture()); QVERIFY(capture.capture() >= 0); - QTRY_COMPARE(capturedSignal.count(), 1); + QTRY_COMPARE(capturedSignal.size(), 1); QVERIFY(capture.isReadyForCapture()); - int readyCount = readyForCaptureChanged.count(); + int readyCount = readyForCaptureChanged.size(); session.setCamera(nullptr); - QTRY_COMPARE(readyForCaptureChanged.count(), readyCount + 1); + QTRY_COMPARE(readyForCaptureChanged.size(), readyCount + 1); QVERIFY(!capture.isReadyForCapture()); QVERIFY(!capture.isAvailable()); QVERIFY(capture.capture() < 0); @@ -910,33 +1130,34 @@ void tst_QMediaCaptureSession::can_add_ImageCapture_and_capture_during_recording QMediaCaptureSession session; QMediaRecorder recorder; - QSignalSpy recorderChanged(&session, SIGNAL(recorderChanged())); - QSignalSpy recorderErrorSignal(&recorder, SIGNAL(errorOccurred(Error, const QString &))); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); - QSignalSpy imageCaptureChanged(&session, SIGNAL(imageCaptureChanged())); - QSignalSpy readyForCaptureChanged(&capture, SIGNAL(readyForCaptureChanged(bool))); - QSignalSpy capturedSignal(&capture, SIGNAL(imageCaptured(int,QImage))); + 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.count(), 1); + QTRY_COMPARE(recorderChanged.size(), 1); recorder.record(); QTRY_VERIFY(recorder.recorderState() == QMediaRecorder::RecordingState); QVERIFY(durationChanged.wait(2000)); session.setImageCapture(&capture); - QTRY_COMPARE(imageCaptureChanged.count(), 1); - QTRY_COMPARE(readyForCaptureChanged.count(), 1); + QTRY_COMPARE(imageCaptureChanged.size(), 1); + QTRY_COMPARE(readyForCaptureChanged.size(), 1); QVERIFY(capture.isReadyForCapture()); QVERIFY(capture.capture() >= 0); - QTRY_COMPARE(capturedSignal.count(), 1); + QTRY_COMPARE(capturedSignal.size(), 1); session.setImageCapture(nullptr); - QVERIFY(readyForCaptureChanged.count() >= 2); + QVERIFY(readyForCaptureChanged.size() >= 2); QVERIFY(!capture.isReadyForCapture()); recorder.stop(); @@ -966,28 +1187,26 @@ void tst_QMediaCaptureSession::testAudioMute() recorder.setOutputLocation(QStringLiteral("test")); QSignalSpy spy(&audioInput, &QAudioInput::mutedChanged); - QSignalSpy durationChanged(&recorder, SIGNAL(durationChanged(qint64))); + QSignalSpy durationChanged(&recorder, &QMediaRecorder::durationChanged); QMediaFormat format; - format.setAudioCodec(QMediaFormat::AudioCodec::MP3); + format.setAudioCodec(QMediaFormat::AudioCodec::Wave); recorder.setMediaFormat(format); - recorder.record(); audioInput.setMuted(true); - QCOMPARE(spy.count(), 1); + recorder.record(); + + QCOMPARE(spy.size(), 1); QCOMPARE(spy.last()[0], true); QTRY_VERIFY_WITH_TIMEOUT(recorder.recorderState() == QMediaRecorder::RecordingState, 2000); QVERIFY(durationChanged.wait(2000)); - audioInput.setMuted(false); - - QCOMPARE(spy.count(), 2); - QCOMPARE(spy.last()[0], false); - recorder.stop(); + QTRY_COMPARE(recorder.recorderState(), QMediaRecorder::StoppedState); + QString actualLocation = recorder.actualLocation().toLocalFile(); QVERIFY2(!actualLocation.isEmpty(), "Recorder did not save a file"); @@ -1016,6 +1235,33 @@ void tst_QMediaCaptureSession::testAudioMute() 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) 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..5d844a716 --- /dev/null +++ b/tests/auto/integration/qmediaframeinputsbackend/framegenerator.cpp @@ -0,0 +1,148 @@ +// 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::setPattern(ImagePattern pattern) +{ + m_pattern = pattern; +} + +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; +} + +static void fillColoredSquares(QImage& image) +{ + QList<QColor> colors = { Qt::red, Qt::green, Qt::blue, Qt::yellow }; + const int width = image.width(); + const int height = image.height(); + + for (int j = 0; j < height; ++j) { + for (int i = 0; i < width; ++i) { + const int colorX = i < width / 2 ? 0 : 1; + const int colorY = j < height / 2 ? 0 : 1; + const int colorIndex = colorX + 2 * colorY; + image.setPixel(i, j, colors[colorIndex].rgb()); + } + } +} + +QVideoFrame VideoGenerator::createFrame() +{ + QImage image(m_size, QImage::Format_ARGB32); + switch (m_pattern) { + case ImagePattern::SingleColor: + image.fill(colors[m_frameIndex % colors.size()]); + break; + case ImagePattern::ColoredSquares: + fillColoredSquares(image); + break; + } + + 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..dafb00681 --- /dev/null +++ b/tests/auto/integration/qmediaframeinputsbackend/framegenerator.h @@ -0,0 +1,82 @@ +// 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; + +enum class ImagePattern +{ + SingleColor, // Image filled with a single color + ColoredSquares // Colored squares, [red, green; blue, yellow] +}; + +class VideoGenerator : public QObject +{ + Q_OBJECT +public: + void setPattern(ImagePattern pattern); + 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 }; + ImagePattern m_pattern = ImagePattern::SingleColor; + 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..6c1141c67 --- /dev/null +++ b/tests/auto/integration/qmediaframeinputsbackend/mediainfo.h @@ -0,0 +1,96 @@ +// 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); + + std::vector<std::array<QColor, 4>> colors; + QObject::connect( + &sink, &TestVideoSink::videoFrameChangedSync, &sink, + [&](const QVideoFrame &frame) { // + if (frame.isValid()) + colors.push_back(sampleQuadrants(frame.toImage())); + }); + + 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(); + info.m_colors = colors; + return info; + } + + + + static std::array<QColor, 4> sampleQuadrants(const QImage &image) + { + const int width = image.width(); + const int height = image.height(); + return { + image.pixel(width / 4, height / 4), + image.pixel(3 * width / 4, height / 4), + image.pixel(width / 4, 3 * height / 4), + image.pixel(3 * width / 4, 3 * height / 4), + }; + + } + + 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<std::array<QColor, 4>> m_colors; // Colors in upper left, upper right, bottom left, and bottom right + + 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..54623c807 --- /dev/null +++ b/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.cpp @@ -0,0 +1,411 @@ +// 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); +} + +struct YUV +{ + double Y; + double U; + double V; +}; + +// Poor man's RGB to YUV conversion with BT.709 coefficients +// from https://en.wikipedia.org/wiki/Y%E2%80%B2UV +QVector3D RGBToYUV(const QColor &c) +{ + const float R = c.redF(); + const float G = c.greenF(); + const float B = c.blueF(); + QVector3D yuv; + yuv[0] = 0.2126f * R + 0.7152f * G + 0.0722f * B; + yuv[1] = -0.09991f * R - 0.33609f * G + 0.436f * B; + yuv[2] = 0.615f * R - 0.55861f * G - 0.05639f * B; + return yuv; +} + +// Considers two colors equal if their YUV components are +// pointing in the same direction and have similar luma (Y) +bool fuzzyCompare(const QColor &lhs, const QColor& rhs, float tol = 1e-2) +{ + const QVector3D lhsYuv = RGBToYUV(lhs); + const QVector3D rhsYuv = RGBToYUV(rhs); + const float relativeLumaDiff = + 0.5f * std::abs((lhsYuv[0] - rhsYuv[0]) / (lhsYuv[0] + rhsYuv[0])); + const float colorDiff = QVector3D::crossProduct(lhsYuv, rhsYuv).length(); + return colorDiff < tol && relativeLumaDiff < tol; +} + +void tst_QMediaFrameInputsBackend::mediaRecorderWritesVideo_withCorrectColors() +{ + CaptureSessionFixture f{ StreamType::Video, AutoStop::EmitEmpty }; + f.connectPullMode(); + f.m_videoGenerator.setPattern(ImagePattern::ColoredSquares); + f.m_videoGenerator.setFrameCount(3); + f.m_recorder.record(); + QVERIFY(f.waitForRecorderStopped(60s)); + + const auto info = MediaInfo::create(f.m_recorder.actualLocation()); + QCOMPARE_EQ(info->m_colors.size(), 3); + + std::array<QColor, 4> colors = info->m_colors.front(); + QVERIFY(fuzzyCompare(colors[0], Qt::red)); + QVERIFY(fuzzyCompare(colors[1], Qt::green)); + QVERIFY(fuzzyCompare(colors[2], Qt::blue)); + QVERIFY(fuzzyCompare(colors[3], Qt::yellow)); +} + +void tst_QMediaFrameInputsBackend::mediaRecorderWritesVideo_whenInputFrameShrinksOverTime() +{ + CaptureSessionFixture f{ StreamType::Video, AutoStop::EmitEmpty }; + f.m_recorder.record(); + f.readyToSendVideoFrame.wait(); + + constexpr int startSize = 38; + int frameCount = 0; + for (int i = 0; i < startSize; i += 2) { // TODO crash in sws_scale if subsequent frames are odd-sized QTBUG-126259 + ++frameCount; + const QSize size{ startSize - i, startSize - i }; + f.m_videoGenerator.setSize(size); + f.m_videoInput.sendVideoFrame(f.m_videoGenerator.createFrame()); + f.readyToSendVideoFrame.wait(); + } + + f.m_videoInput.sendVideoFrame({}); + + QVERIFY(f.waitForRecorderStopped(60s)); + auto info = MediaInfo::create(f.m_recorder.actualLocation()); + + QCOMPARE_EQ(info->m_frameCount, frameCount); + + // All frames should be resized to the size of the first frame + QCOMPARE_EQ(info->m_size, QSize(startSize, startSize)); +} + +void tst_QMediaFrameInputsBackend::mediaRecorderWritesVideo_whenInputFrameGrowsOverTime() +{ + CaptureSessionFixture f{ StreamType::Video, AutoStop::EmitEmpty }; + f.m_recorder.record(); + f.readyToSendVideoFrame.wait(); + + constexpr int startSize = 38; + constexpr int maxSize = 256; + int frameCount = 0; + + for (int i = 0; i < maxSize - startSize; i += 2) { // TODO crash in sws_scale if subsequent frames are odd-sized QTBUG-126259 + ++frameCount; + const QSize size{ startSize + i, startSize + i }; + f.m_videoGenerator.setPattern(ImagePattern::ColoredSquares); + f.m_videoGenerator.setSize(size); + f.m_videoInput.sendVideoFrame(f.m_videoGenerator.createFrame()); + f.readyToSendVideoFrame.wait(); + } + + f.m_videoInput.sendVideoFrame({}); + + QVERIFY(f.waitForRecorderStopped(60s)); + auto info = MediaInfo::create(f.m_recorder.actualLocation()); + + QCOMPARE_EQ(info->m_frameCount, frameCount); + + // All frames should be resized to the size of the first frame + QCOMPARE_EQ(info->m_size, QSize(startSize, startSize)); +} + +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..451e1aceb --- /dev/null +++ b/tests/auto/integration/qmediaframeinputsbackend/tst_qmediaframeinputsbackend.h @@ -0,0 +1,47 @@ +// 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_whenInputFrameShrinksOverTime(); + void mediaRecorderWritesVideo_whenInputFrameGrowsOverTime(); + + void mediaRecorderWritesVideo_withSingleFrame(); + + void mediaRecorderWritesVideo_withCorrectColors(); + + 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 e107df861..1f24e07a1 100644 --- a/tests/auto/integration/qmediaplayerbackend/BLACKLIST +++ b/tests/auto/integration/qmediaplayerbackend/BLACKLIST @@ -1,7 +1,6 @@ # Media player plugin not built at the moment on this platform opensuse-13.1 64bit - [playlist] redhatenterpriselinuxworkstation-6.6 diff --git a/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt index 3848c6099..3a9d25926 100644 --- a/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt +++ b/tests/auto/integration/qmediaplayerbackend/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmediaplayerbackend.pro. ##################################################################### @@ -13,24 +16,24 @@ 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 - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate - TESTDATA ${testdata_resource_files} -) - -qt_internal_add_resource(tst_qmediaplayerbackend "testdata" - PREFIX - "/" - FILES + Qt::MultimediaQuickPrivate + Qt::Qml + Qt::Quick + Qt::QuickPrivate + BUILTIN_TESTDATA + TESTDATA ${testdata_resource_files} -) - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_qmediaplayerbackend CONDITION boot2qt - DEFINES - SKIP_OGV_TEST + "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..883330513 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/fixture.h @@ -0,0 +1,95 @@ +// 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>>; + +struct TestSubtitleSink : QObject +{ + Q_OBJECT + +public Q_SLOTS: + void addSubtitle(QString string) + { + QMetaObject::invokeMethod(this, [this, string = std::move(string)]() mutable { + subtitles.append(std::move(string)); + }); + } + +public: + QStringList subtitles; +}; + +#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/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/15s.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv Binary files differnew file mode 100644 index 000000000..80ee0f923 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/15s.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4 Binary files differnew file mode 100644 index 000000000..96ae2e4e3 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/3colors_with_sound_1s.mp4 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 Binary files differnew file mode 100644 index 000000000..dfeec1546 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_jpg_thumbnail.mp4 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 Binary files differnew file mode 100644 index 000000000..147adf777 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/audio_video_with_png_thumbnail.mp4 diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm b/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm Binary files differnew file mode 100644 index 000000000..048d02e8a --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/busAv1.webm diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4 Binary files differnew file mode 100644 index 000000000..a3661b9d2 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix.mp4 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 Binary files differnew file mode 100644 index 000000000..9a60850d6 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_180_deg_clockwise.mp4 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 Binary files differnew file mode 100644 index 000000000..b3ecd486d --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_270_deg_clockwise.mp4 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 Binary files differnew file mode 100644 index 000000000..dc620d05f --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/color_matrix_90_deg_clockwise.mp4 diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm b/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm Binary files differnew file mode 100644 index 000000000..87b737949 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/duration_issues.webm 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 Binary files differnew file mode 100644 index 000000000..c5a508a1f --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/h264_avc1_yuv420p10le_tv_bt2020.mov diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv Binary files differnew file mode 100644 index 000000000..1962f00c1 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack-subtitle-start-at-zero.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv Binary files differnew file mode 100644 index 000000000..a3c2e9bb9 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/multitrack.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 Binary files differindex 2435f65b8..892c2a89e 100644 --- a/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 +++ b/tests/auto/integration/qmediaplayerbackend/testdata/nokia-tune.mp3 diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4 Binary files differnew file mode 100644 index 000000000..6b67a3433 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/one_red_frame.mp4 diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4 Binary files differnew file mode 100644 index 000000000..b0d9b3593 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/par_2_3.mp4 diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4 b/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4 Binary files differnew file mode 100644 index 000000000..55baed13e --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/par_3_2.mp4 diff --git a/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv b/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv Binary files differnew file mode 100644 index 000000000..2051e4df5 --- /dev/null +++ b/tests/auto/integration/qmediaplayerbackend/testdata/subtitletest.mkv diff --git a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp index 85d132368..b212b4b63 100644 --- a/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp +++ b/tests/auto/integration/qmediaplayerbackend/tst_qmediaplayerbackend.cpp @@ -1,47 +1,73 @@ -/**************************************************************************** -** -** 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 "qmediaplayer.h" +#include "mediaplayerstate.h" +#include "fake.h" +#include "fixture.h" +#include "server.h" #include <qmediametadata.h> #include <qaudiobuffer.h> +#include <qaudiodevice.h> #include <qvideosink.h> #include <qvideoframe.h> #include <qaudiooutput.h> - -#include "../shared/mediafileselector.h" -//TESTED_COMPONENT=src/multimedia - +#include <qmediadevices.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. @@ -53,19 +79,88 @@ 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_initializesExpectedDefaultState(); + void setSource_initializesExpectedDefaultState_data(); + 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_resetsState_whenCalledWithEmptyUrl_data(); + 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 pause_initializesExpectedDefaultState(); + void pause_initializesExpectedDefaultState_data(); + void pause_doesNotAdvancePosition(); + void pause_playback_resumesFromPausedPosition(); + + 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 play_succeedsFromSourceDevice(); + void play_succeedsFromSourceDevice_data(); + + void stop_entersStoppedState_whenPlayerWasPaused(); + void stop_entersStoppedState_whenPlayerWasPaused_data(); + void stop_setsPositionToZero_afterPlayingToEndOfMedia(); + + void playbackRate_returnsOne_byDefault(); + void setPlaybackRate_changesPlaybackRateAndEmitsSignal_data(); + void setPlaybackRate_changesPlaybackRateAndEmitsSignal(); + void setPlaybackRate_changesPlaybackDuration(); + void setPlaybackRate_changesPlaybackDuration_data(); + + 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(); @@ -73,99 +168,169 @@ private slots: void seekInStoppedState(); void subsequentPlayback(); 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 videoDimensions(); + 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(); + + void setVideoOutput_doesNotStopPlayback_data(); + void setVideoOutput_doesNotStopPlayback(); + void setAudioOutput_doesNotStopPlayback_data(); + void setAudioOutput_doesNotStopPlayback(); + void swapAudioDevice_doesNotStopPlayback_data(); + void swapAudioDevice_doesNotStopPlayback(); + + void play_readsSubtitle(); + void multiTrack_validateMetadata(); + void play_readsSubtitle_fromMultiTrack(); + void play_readsSubtitle_fromMultiTrack_data(); + + void setActiveSubtitleTrack_switchesSubtitles(); + void setActiveSubtitleTrack_switchesSubtitles_data(); + + void setActiveVideoTrack_switchesVideoTrack(); + + void disablingAllTracks_doesNotStopPlayback(); + void disablingAllTracks_beforeTracksChanged_doesNotStopPlayback(); private: - QUrl selectVideoFile(const QStringList& mediaCandidates); - bool isWavSupported(); - - //one second local wav file - QUrl localWavFile; - QUrl localWavFile2; - QUrl localVideoFile; - QUrl localVideoFile2; - QUrl videoDimensionTestFile; - QUrl localCompressedSoundFile; - QUrl localFileWithMetadata; - - bool m_inCISystem; + QUrl selectVideoFile(const QStringList &mediaCandidates); + + 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{}; + MaybeUrl m_15sVideo = QUnexpect{}; + MaybeUrl m_subtitleVideo = QUnexpect{}; + MaybeUrl m_multitrackVideo = QUnexpect{}; + MaybeUrl m_multitrackSubtitleStartsAtZeroVideo = 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 TestVideoSink : public QVideoSink +static bool commandExists(const QString &command) { - Q_OBJECT -public: - explicit TestVideoSink(bool storeFrames = true) - : m_storeFrames(storeFrames) - { - connect(this, &QVideoSink::videoFrameChanged, this, &TestVideoSink::addVideoFrame); - } -public Q_SLOTS: - void addVideoFrame(const QVideoFrame &frame) { - if (m_storeFrames) - m_frameList.append(frame); - ++m_totalFrames; - } +#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); + }); +} -public: - QList<QVideoFrame> m_frameList; - int m_totalFrames = 0; // used instead of the list when frames are not stored +static std::unique_ptr<QTemporaryFile> copyResourceToTemporaryFile(QString resource, + QString filePattern) +{ + QFile resourceFile(resource); + if (!resourceFile.open(QIODeviceBase::ReadOnly)) + return nullptr; -private: - bool m_storeFrames; -}; + auto temporaryFile = std::make_unique<QTemporaryFile>(filePattern); + if (!temporaryFile->open()) + return nullptr; -void tst_QMediaPlayerBackend::init() -{ + QByteArray bytes = resourceFile.readAll(); + QDataStream stream(temporaryFile.get()); + stream.writeRawData(bytes.data(), bytes.length()); + + temporaryFile->close(); + + return temporaryFile; } -QUrl tst_QMediaPlayerBackend::selectVideoFile(const QStringList& mediaCandidates) +void tst_QMediaPlayerBackend::detectVlcCommand() { - // select supported video format - QMediaPlayer player; - TestVideoSink *surface = new TestVideoSink; - player.setVideoOutput(surface); + m_vlcCommand = qEnvironmentVariable("QT_VLC_COMMAND"); - QSignalSpy errorSpy(&player, SIGNAL(error(QMediaPlayer::Error))); + if (!m_vlcCommand.isEmpty()) + return; - for (const QString &s : mediaCandidates) { - QFileInfo videoFile(s); - if (!videoFile.exists()) - continue; - QUrl media = QUrl(QUrl::fromLocalFile(videoFile.absoluteFilePath())); - player.setSource(media); - player.pause(); +#if defined(Q_OS_WINDOWS) + m_vlcCommand = "vlc.exe"; +#else + m_vlcCommand = "vlc"; +#endif + if (commandExists(m_vlcCommand)) + return; - for (int i = 0; i < 2000 && surface->m_frameList.isEmpty() && errorSpy.isEmpty(); i+=50) { - QTest::qWait(50); - } + m_vlcCommand.clear(); - if (!surface->m_frameList.isEmpty() && errorSpy.isEmpty()) { - return media; - } - errorSpy.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 - return QUrl(); + if (!QFile::exists(m_vlcCommand)) + m_vlcCommand.clear(); } -bool tst_QMediaPlayerBackend::isWavSupported() +bool tst_QMediaPlayerBackend::canCreateRtpStream() const { - return !localWavFile.isEmpty(); + return !m_vlcCommand.isEmpty(); } void tst_QMediaPlayerBackend::initTestCase() @@ -174,412 +339,1731 @@ void tst_QMediaPlayerBackend::initTestCase() if (!player.isAvailable()) QSKIP("Media player service is not available"); - localWavFile = MediaFileSelector::selectMediaFile(QStringList() << "qrc:/testdata/test.wav"); - localWavFile2 = MediaFileSelector::selectMediaFile(QStringList() << "qrc:/testdata/_test.wav"); + qRegisterMetaType<MaybeUrl>(); + + m_localWavFile = m_mediaSelector.select("qrc:/testdata/test.wav"); + m_localWavFile2 = m_mediaSelector.select("qrc:/testdata/_test.wav"); + + m_localVideoFile = + m_mediaSelector.select("qrc:/testdata/colors.mp4", "qrc:/testdata/colors.ogv"); + + 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"); - QStringList mediaCandidates; - mediaCandidates << "qrc:/testdata/colors.mp4"; - mediaCandidates << "qrc:/testdata/colors.ogv"; - localVideoFile = MediaFileSelector::selectMediaFile(mediaCandidates); + m_videoDimensionTestFile = m_mediaSelector.select("qrc:/testdata/BigBuckBunny.mp4"); - mediaCandidates.clear(); - mediaCandidates << "qrc:/testdata/BigBuckBunny.mp4"; - mediaCandidates << "qrc:/testdata/busMpeg4.mp4"; - localVideoFile2 = MediaFileSelector::selectMediaFile(mediaCandidates); + m_localCompressedSoundFile = + m_mediaSelector.select("qrc:/testdata/nokia-tune.mp3", "qrc:/testdata/nokia-tune.mkv"); - mediaCandidates.clear(); - mediaCandidates << "qrc:/testdata/BigBuckBunny.mp4"; - videoDimensionTestFile = MediaFileSelector::selectMediaFile(mediaCandidates); + m_localFileWithMetadata = m_mediaSelector.select("qrc:/testdata/nokia-tune.mp3"); - mediaCandidates.clear(); - mediaCandidates << "qrc:/testdata/nokia-tune.mp3"; - mediaCandidates << "qrc:/testdata/nokia-tune.mkv"; - localCompressedSoundFile = MediaFileSelector::selectMediaFile(mediaCandidates); + m_oneRedFrameVideo = m_mediaSelector.select("qrc:/testdata/one_red_frame.mp4"); - localFileWithMetadata = - MediaFileSelector::selectMediaFile(QStringList() << "qrc:/testdata/nokia-tune.mp3"); + 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"); - qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10); + 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"); + m_15sVideo = m_mediaSelector.select("qrc:/testdata/15s.mkv"); + m_subtitleVideo = m_mediaSelector.select("qrc:/testdata/subtitletest.mkv"); + m_multitrackVideo = m_mediaSelector.select("qrc:/testdata/multitrack.mkv"); + m_multitrackSubtitleStartsAtZeroVideo = + m_mediaSelector.select("qrc:/testdata/multitrack-subtitle-start-at-zero.mkv"); + + detectVlcCommand(); } -void tst_QMediaPlayerBackend::cleanup() +void tst_QMediaPlayerBackend::testMediaFilesAreSupported() { + 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::construction() +void tst_QMediaPlayerBackend::destructor_cancelsPreviousSetSource_whenServerDoesNotRespond() { - QMediaPlayer player; - QTRY_VERIFY(player.isAvailable()); +#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::loadMedia() +void tst_QMediaPlayerBackend::destructor_emitsOnlyQObjectDestroyedSignal_whenPlayerIsRunning() { - if (!isWavSupported()) - QSKIP("Sound format is not supported"); + 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; - QAudioOutput output; - player.setAudioOutput(&output); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); + if (hasAudioOutput) + player.setAudioOutput(&audioOutput); - QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); - QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); - QSignalSpy mediaSpy(&player, SIGNAL(sourceChanged(QUrl))); + if (hasVideoOutput) + player.setVideoOutput(&videoOutput); - player.setSource(localWavFile); + if (hasVideoSink) + player.setVideoSink(videoOutput.videoSink()); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + expectedState.audioOutput = hasAudioOutput ? &audioOutput : nullptr; + expectedState.videoOutput = (hasVideoOutput && !hasVideoSink) ? &videoOutput : nullptr; + expectedState.videoSink = (hasVideoSink || hasVideoOutput) ? videoOutput.videoSink() : nullptr; - QVERIFY(player.mediaStatus() != QMediaPlayer::NoMedia); - QVERIFY(player.mediaStatus() != QMediaPlayer::InvalidMedia); - QVERIFY(player.source() == localWavFile); + const MediaPlayerState actualState{ player }; + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); +} - QCOMPARE(stateSpy.count(), 0); - QVERIFY(statusSpy.count() > 0); - QCOMPARE(mediaSpy.count(), 1); - QCOMPARE(mediaSpy.last()[0].value<QUrl>(), localWavFile); +void tst_QMediaPlayerBackend::setSource_emitsSourceChanged_whenCalledWithInvalidFile() +{ + m_fixture->player.setSource({ "Some not existing media" }); + QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + 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); - QVERIFY(player.hasAudio()); - QVERIFY(!player.hasVideo()); + QCOMPARE_EQ(m_fixture->errorOccurred[0][0], QMediaPlayer::ResourceError); } -void tst_QMediaPlayerBackend::unloadMedia() +void tst_QMediaPlayerBackend::setSource_emitsMediaStatusChange_whenCalledWithInvalidFile() { - if (!isWavSupported()) - QSKIP("Sound format is not supported"); + m_fixture->player.setSource({ "Some not existing media" }); + QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError); - QMediaPlayer player; - QAudioOutput output; - player.setAudioOutput(&output); + QCOMPARE_EQ(m_fixture->mediaStatusChanged, + SignalList({ { QMediaPlayer::LoadingMedia }, { QMediaPlayer::InvalidMedia } })); +} - QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); - QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); - QSignalSpy mediaSpy(&player, SIGNAL(sourceChanged(QUrl))); - QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); - QSignalSpy durationSpy(&player, SIGNAL(durationChanged(qint64))); +void tst_QMediaPlayerBackend::setSource_doesNotEmitPlaybackStateChange_whenCalledWithInvalidFile() +{ + m_fixture->player.setSource({ "Some not existing media" }); + QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError); - player.setSource(localWavFile); + QVERIFY(m_fixture->playbackStateChanged.empty()); +} - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); +void tst_QMediaPlayerBackend::setSource_setsSourceMediaStatusAndError_whenCalledWithInvalidFile() +{ + const QUrl invalidFile{ "Some not existing media" }; + + 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_initializesExpectedDefaultState() +{ + QFETCH(MaybeUrl, url); + CHECK_SELECTED_URL(url); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*url); + + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + expectedState.source = *url; + expectedState.mediaStatus = QMediaPlayer::LoadingMedia; + + if (isGStreamerPlatform()) { + // gstreamer initializes the tracks + expectedState.audioTracks = std::nullopt; + expectedState.videoTracks = std::nullopt; + expectedState.activeAudioTrack = std::nullopt; + expectedState.activeVideoTrack = std::nullopt; + expectedState.hasAudio = std::nullopt; + expectedState.hasVideo = std::nullopt; + + expectedState.isSeekable = true; + } + + const MediaPlayerState actualState{ m_fixture->player }; + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); +} + +void tst_QMediaPlayerBackend::setSource_initializesExpectedDefaultState_data() +{ + QTest::addColumn<MaybeUrl>("url"); + + QTest::addRow("with wave file") << m_localWavFile; + QTest::addRow("with video file") << m_localVideoFile; + QTest::addRow("with av1 file") << m_av1File; + QTest::addRow("with compressed sound file") << m_localCompressedSoundFile; +} + +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); - QVERIFY(player.position() == 0); -#ifdef Q_OS_QNX - // QNX mm-renderer only updates the duration when 'play' is triggered - QVERIFY(player.duration() == 0); #else - QVERIFY(player.duration() > 0); + QSKIP("Test requires network feature"); #endif +} - player.play(); +void tst_QMediaPlayerBackend::setSource_changesSourceAndMediaStatus_whenCalledWithValidFile() +{ + CHECK_SELECTED_URL(m_localVideoFile); - QTRY_VERIFY(player.position() > 0); - QVERIFY(player.duration() > 0); + m_fixture->player.setSource(*m_localVideoFile); - stateSpy.clear(); - statusSpy.clear(); - mediaSpy.clear(); - positionSpy.clear(); - durationSpy.clear(); + QCOMPARE_EQ(m_fixture->mediaStatusChanged, SignalList({ { QMediaPlayer::LoadingMedia } })); - player.setSource(QUrl()); + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + expectedState.source = *m_localVideoFile; + expectedState.mediaStatus = QMediaPlayer::LoadingMedia; - QVERIFY(player.position() <= 0); - QVERIFY(player.duration() <= 0); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); - QCOMPARE(player.source(), QUrl()); + if (isGStreamerPlatform()) // gstreamer synchronously identifies file streams as seekable + expectedState.isSeekable = true; + + MediaPlayerState actualState{ m_fixture->player }; - QVERIFY(!statusSpy.isEmpty()); - QVERIFY(!mediaSpy.isEmpty()); + QSKIP_GSTREAMER("QTBUG-124005: spurious failures"); + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); } -void tst_QMediaPlayerBackend::loadMediaInLoadingState() +void tst_QMediaPlayerBackend::setSource_updatesExpectedAttributes_whenMediaHasLoaded() { - if (!isWavSupported()) - QSKIP("Sound format is not supported"); + 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::setSource_stopsAndEntersErrorState_whenPlayerWasPlaying() +{ + 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::setSource_loadsAudioTrack_whenCalledWithValidWavFile() +{ + CHECK_SELECTED_URL(m_localWavFile); + + m_fixture->player.setSource(*m_localWavFile); + + QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); + + QVERIFY(m_fixture->player.mediaStatus() != QMediaPlayer::NoMedia); + QVERIFY(m_fixture->player.mediaStatus() != QMediaPlayer::InvalidMedia); + QVERIFY(m_fixture->player.source() == *m_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); + + QTRY_COMPARE(m_fixture->player.mediaStatus(), QMediaPlayer::LoadedMedia); + + QVERIFY(m_fixture->player.hasAudio()); + QVERIFY(!m_fixture->player.hasVideo()); +} + +void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl() +{ + QFETCH(MaybeUrl, url); + CHECK_SELECTED_URL(url); + + QMediaPlayer &player = m_fixture->player; + + // Load valid media and start playing + player.setSource(*url); - QMediaPlayer player; - QAudioOutput output; - player.setAudioOutput(&output); - player.setSource(localWavFile2); - QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); - // Sets new media while old has not been finished. - player.setSource(localWavFile); - QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + + QCOMPARE(player.position(), 0); + + if (isQNXPlatform()) + // QNX mm-renderer updates the duration when 'play' is triggered + QCOMPARE(player.duration(), 0); + else + QCOMPARE_GT(player.duration(), 0); + player.play(); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); - player.setSource(localWavFile2); - QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadingMedia); + QTRY_COMPARE_GT(player.position(), 0); + if (isGStreamerPlatform()) + QTRY_COMPARE_GT(player.duration(), 0); // duration update is asynchronous + else + QCOMPARE_GT(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()); + + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + if (isGStreamerPlatform()) // QTBUG-124005: no buffer progress update + expectedState.bufferProgress = std::nullopt; + const MediaPlayerState actualState{ player }; + + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); } -void tst_QMediaPlayerBackend::playPauseStop() +void tst_QMediaPlayerBackend::setSource_resetsState_whenCalledWithEmptyUrl_data() { - if (!isWavSupported()) - QSKIP("Sound format is not supported"); + QTest::addColumn<MaybeUrl>("url"); - QMediaPlayer player; - QAudioOutput output; - player.setAudioOutput(&output); + QTest::addRow("with wave file") << m_localWavFile; + QTest::addRow("with video file") << m_localVideoFile; +} - QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); - QSignalSpy statusSpy(&player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); - QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); - QSignalSpy errorSpy(&player, SIGNAL(errorOccurred(QMediaPlayer::Error, const QString&))); +void tst_QMediaPlayerBackend::setSource_loadsNewMedia_whenPreviousMediaWasFullyLoaded() +{ + 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); +} - // Check play() without a media - player.play(); +void tst_QMediaPlayerBackend::setSource_loadsCorrectTracks_whenLoadingMediaInSequence() +{ + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); + CHECK_SELECTED_URL(m_localWavFile2); - QCOMPARE(player.playbackState(), 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); + // 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()); - // Check pause() without a media - player.pause(); + m_fixture->clearSpies(); - QCOMPARE(player.playbackState(), 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); + // Load an audio file, and verify that only audio track is loaded + m_fixture->player.setSource(*m_localWavFile2); - // The rest is with a valid media + QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia); - player.setSource(localWavFile); + 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()); - QCOMPARE(player.position(), qint64(0)); + m_fixture->player.play(); - player.play(); + // Load video only file, and verify that only video track is loaded + m_fixture->player.setSource(*m_localVideoFile2); - QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); + QTRY_COMPARE_EQ(m_fixture->player.mediaStatus(), QMediaPlayer::MediaStatus::LoadedMedia); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QCOMPARE(m_fixture->player.playbackState(), QMediaPlayer::StoppedState); + QVERIFY(m_fixture->player.hasVideo()); + QVERIFY(!m_fixture->player.hasAudio()); + QCOMPARE(m_fixture->errorOccurred.size(), 0); +} - QCOMPARE(stateSpy.count(), 1); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState); - QTRY_VERIFY(statusSpy.count() > 0 && - statusSpy.last()[0].value<QMediaPlayer::MediaStatus>() == QMediaPlayer::BufferedMedia); +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()); +} - QTRY_VERIFY(player.position() > 100); - QVERIFY(player.duration() > 0); - QTRY_VERIFY(positionSpy.count() > 0); - QTRY_VERIFY(positionSpy.last()[0].value<qint64>() > 0); +void tst_QMediaPlayerBackend::setSource_entersStoppedState_whenPlayerWasPlaying() +{ + 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); + QTRY_COMPARE(m_fixture->mediaStatusChanged, + SignalList({ + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::LoadingMedia }, + { 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); +} - stateSpy.clear(); - statusSpy.clear(); - positionSpy.clear(); +void tst_QMediaPlayerBackend::setSource_emitsError_whenSdpFileIsLoaded() +{ +#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 - qint64 positionBeforePause = player.position(); - player.pause(); + if (!isFFMPEGPlatform()) + QSKIP("This test is only for FFmpeg backend"); - QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + // Create stream + if (!canCreateRtpStream()) + QSKIP("Rtp stream cannot be created"); + + // Make sure the default whitelist is used + qunsetenv("QT_FFMPEG_PROTOCOL_WHITELIST"); + + 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"); + + auto processCloser = qScopeGuard([&process, &sdpUrl]() { + // End stream + process->close(); + + // Remove .sdp file created by VLC + QFile(sdpUrl.toLocalFile()).remove(); + }); + + m_fixture->player.setSource(sdpUrl); + QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError); +#endif // QT_CONFIG(process) +} + +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; + QTest::addRow("video with subtitle") << m_subtitleVideo << 1 << 1 << 1; + QTest::addRow("video with multiple streams") << m_multitrackVideo << 2 << 2 << 2; +} + +void tst_QMediaPlayerBackend::setSource_updatesTrackProperties() +{ + QFETCH(MaybeUrl, url); + QFETCH(int, numberOfVideoTracks); + QFETCH(int, numberOfAudioTracks); + QFETCH(int, numberOfSubtitleTracks); + + QMediaPlayer &player = m_fixture->player; + + CHECK_SELECTED_URL(url); + + player.setSource(*url); - QCOMPARE(stateSpy.count(), 1); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PausedState); + QTRY_COMPARE(player.videoTracks().size(), numberOfVideoTracks); + QTRY_COMPARE(player.audioTracks().size(), numberOfAudioTracks); + QTRY_COMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks); +} + +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; + QTest::addRow("video with subtitle") << m_subtitleVideo << 1 << 1 << 1; + QTest::addRow("video with multiple streams") << m_multitrackVideo << 2 << 2 << 2; +} + +void tst_QMediaPlayerBackend::setSource_emitsTracksChanged() +{ + QFETCH(MaybeUrl, url); + QFETCH(int, numberOfVideoTracks); + QFETCH(int, numberOfAudioTracks); + QFETCH(int, numberOfSubtitleTracks); + + QMediaPlayer &player = m_fixture->player; + + CHECK_SELECTED_URL(url); + + QSignalSpy tracksChanged(&player, &QMediaPlayer::tracksChanged); + player.setSource(*url); + + QVERIFY(tracksChanged.wait()); + + QCOMPARE(player.videoTracks().size(), numberOfVideoTracks); + QCOMPARE(player.audioTracks().size(), numberOfAudioTracks); + QCOMPARE(player.subtitleTracks().size(), numberOfSubtitleTracks); +} + +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); +} + +void tst_QMediaPlayerBackend:: + setSourceAndPlay_setCorrectVideoSize_whenVideoHasNonStandardPixelAspectRatio() +{ +#ifdef Q_OS_ANDROID + QSKIP("SKIP initTestCase on CI, because of QTBUG-126428"); +#endif + if (isGStreamerPlatform() && isCI()) + QSKIP("QTBUG-124005: Fails with gstreamer on CI"); + + QFETCH(MaybeUrl, url); + QFETCH(QSize, expectedVideoSize); + + CHECK_SELECTED_URL(url); + + 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(m_fixture->surface.videoSize(), expectedVideoSize); + + m_fixture->player.play(); + + 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)); + +#ifdef Q_OS_ANDROID + QSKIP("frame.toImage will return null image because of QTBUG-108446"); +#endif + + 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); +} + +void tst_QMediaPlayerBackend::pause_doesNotChangePlayerState_whenInvalidFileLoaded() +{ + m_fixture->player.setSource({ "Some not existing media" }); + QTRY_COMPARE_EQ(m_fixture->player.error(), QMediaPlayer::ResourceError); + + const MediaPlayerState expectedState{ m_fixture->player }; + + m_fixture->player.pause(); + + const MediaPlayerState actualState{ m_fixture->player }; + + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); +} + +void tst_QMediaPlayerBackend::pause_doesNothing_whenMediaIsNotLoaded() +{ + m_fixture->player.pause(); + + 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::pause_entersPauseState_whenPlayerWasPlaying() +{ + CHECK_SELECTED_URL(m_localWavFile); + + // 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_VERIFY(qAbs(player.position() - positionBeforePause) < 150); + QTRY_COMPARE_LT(qAbs(m_fixture->player.position() - positionBeforePause), 200); +} - stateSpy.clear(); - statusSpy.clear(); +void tst_QMediaPlayerBackend::pause_initializesExpectedDefaultState() +{ + QFETCH(MaybeUrl, url); + QFETCH(bool, hasVideo); + QFETCH(bool, hasAudio); + CHECK_SELECTED_URL(url); - player.stop(); + if (isFFMPEGPlatform() && url->path().contains("Av1")) + QSKIP("QTBUG-119711: ffmpeg's binaries on CI do not support av1"); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); + QMediaPlayer &player = m_fixture->player; + player.setSource(*url); + player.pause(); - QCOMPARE(stateSpy.count(), 1); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); - //it's allowed to emit statusChanged() signal async - QTRY_COMPARE(statusSpy.count(), 1); - QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::PausedState); - //ensure the position is reset to 0 at stop and positionChanged(0) is emitted - QTRY_COMPARE(player.position(), qint64(0)); - QCOMPARE(positionSpy.last()[0].value<qint64>(), qint64(0)); - QVERIFY(player.duration() > 0); + MediaPlayerState expectedState = MediaPlayerState::defaultState(); + expectedState.source = *url; + expectedState.playbackState = QMediaPlayer::PausedState; + expectedState.isSeekable = true; - stateSpy.clear(); - statusSpy.clear(); - positionSpy.clear(); + expectedState.mediaStatus = std::nullopt; + expectedState.duration = std::nullopt; + expectedState.bufferProgress = std::nullopt; - player.play(); + expectedState.audioTracks = std::nullopt; + expectedState.videoTracks = std::nullopt; + expectedState.metaData = std::nullopt; - QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); - QCOMPARE(stateSpy.count(), 1); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), 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); + if (hasVideo) { + expectedState.activeVideoTrack = 0; + expectedState.hasVideo = std::nullopt; + } - player.stop(); - stateSpy.clear(); - statusSpy.clear(); - positionSpy.clear(); + if (hasAudio) { + expectedState.activeAudioTrack = 0; + expectedState.hasAudio = std::nullopt; + } - player.setSource(localWavFile2); + const MediaPlayerState actualState{ player }; + COMPARE_MEDIA_PLAYER_STATE_EQ(actualState, expectedState); - QTRY_VERIFY(statusSpy.count() > 0); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); - QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.count(), 0); + QVERIFY(actualState.mediaStatus == QMediaPlayer::BufferingMedia + || actualState.mediaStatus == QMediaPlayer::BufferedMedia); + + if (hasVideo) + QCOMPARE(actualState.videoTracks->size(), 1); + if (hasAudio) + QCOMPARE(actualState.audioTracks->size(), 1); + + QEXPECT_FAIL_GSTREAMER("", "GStreamer doesn't update bufferProgress while paused", Continue); + + QTRY_COMPARE_GT(actualState.bufferProgress, 0); +} + +void tst_QMediaPlayerBackend::pause_initializesExpectedDefaultState_data() +{ + QTest::addColumn<MaybeUrl>("url"); + QTest::addColumn<bool>("hasVideo"); + QTest::addColumn<bool>("hasAudio"); + + QTest::addRow("with wave file") << m_localWavFile << false << true; + QTest::addRow("with video file") << m_localVideoFile << true << true; + QTest::addRow("with av1 file") << m_av1File << true << false; + QTest::addRow("with compressed sound file") << m_localCompressedSoundFile << false << true; +} + +void tst_QMediaPlayerBackend::pause_doesNotAdvancePosition() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_localVideoFile); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*m_localVideoFile); + + player.pause(); + + QTest::qWait(1s); + + QTRY_COMPARE_EQ(player.position(), 0); +} + +void tst_QMediaPlayerBackend::pause_playback_resumesFromPausedPosition() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_localVideoFile); + + QMediaPlayer &player = m_fixture->player; + player.setSource(*m_localVideoFile); player.play(); - QTRY_VERIFY(player.position() > 100); + QTRY_COMPARE_GT(player.position(), 100); - player.setSource(localWavFile); + player.pause(); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); - QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::LoadedMedia); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); - QCOMPARE(player.position(), 0); - QCOMPARE(positionSpy.last()[0].value<qint64>(), 0); + qint64 pausePos = player.position(); + QTest::qWait(1s); - stateSpy.clear(); - statusSpy.clear(); - positionSpy.clear(); + QCOMPARE_EQ(player.position(), pausePos); player.play(); - QTRY_VERIFY(player.position() > 100); + // Make sure the media player does not make up for the lost time + m_fixture->positionChanged.wait(); + m_fixture->positionChanged.wait(); - player.setSource(QUrl()); + QCOMPARE_LT(player.position(), pausePos + 500); +} - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::NoMedia); - QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::NoMedia); - QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); - QCOMPARE(player.position(), 0); - QCOMPARE(positionSpy.last()[0].value<qint64>(), 0); - QCOMPARE(player.duration(), 0); +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::processEOS() +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_VERIFY(m_fixture->player.mediaStatus() == QMediaPlayer::BufferedMedia + || m_fixture->player.mediaStatus() == QMediaPlayer::EndOfMedia); + + QCOMPARE(m_fixture->playbackStateChanged, SignalList({ { QMediaPlayer::PlayingState } })); + + auto expectedMediaStatus = SignalList{ + { QMediaPlayer::LoadingMedia }, + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + }; + + QTRY_COMPARE_EQ(m_fixture->mediaStatusChanged.first(4), expectedMediaStatus); + + 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 { + QTRY_COMPARE_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); + + QList allowedSignalSequences = { + SignalList{ + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::NoMedia }, + }, + SignalList{ + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::BufferingMedia }, + { QMediaPlayer::BufferedMedia }, + { QMediaPlayer::EndOfMedia }, // EndOfMedia can be reached before setSource({}) + { QMediaPlayer::LoadedMedia }, + { QMediaPlayer::NoMedia }, + }, + }; + + QTRY_VERIFY(allowedSignalSequences.contains(m_fixture->mediaStatusChanged)); + + 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; - QAudioOutput output; - player.setAudioOutput(&output); - QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); - 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.setSource(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); + + const auto colors = { qRgb(0, 0, 0xFF), qRgb(0xFF, 0, 0), qRgb(0, 0xFE, 0) }; + const auto colorInterval = 5000; + + for (auto pos : { colorInterval / 2, colorInterval + 100 }) { + qDebug() << "Waiting for position:" << pos; + + QTRY_COMPARE_GT(player.position(), pos); + + auto frame1 = surface.waitForFrame(); + QVERIFY(frame1.isValid()); + QCOMPARE(frame1.size(), QSize(213, 120)); + + 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(); - QVERIFY(statusSpy.count() > 0); - QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::EndOfMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.count(), 2); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); + QCOMPARE(errorSpy.size(), 0); +#endif //QT_CONFIG(process) +} - //at EOS the position stays at the end of file - QCOMPARE(player.position(), player.duration()); - QTRY_VERIFY(positionSpy.count() > 0); - QTRY_COMPARE(positionSpy.last()[0].value<qint64>(), player.duration()); +void tst_QMediaPlayerBackend::play_waitsForLastFrameEnd_whenPlayingVideoWithLongFrames() +{ +#ifdef Q_OS_ANDROID + QSKIP("SKIP initTestCase on CI, because of QTBUG-126428"); +#endif + if (isCI() && isGStreamerPlatform()) + QSKIP_GSTREAMER("QTBUG-124005: spurious failures with gstreamer"); - stateSpy.clear(); - statusSpy.clear(); - positionSpy.clear(); + 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, 850); + 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::play_succeedsFromSourceDevice() +{ + QFETCH(const MaybeUrl, mediaUrl); + QFETCH(bool, streamOutlivesPlayer); + + CHECK_SELECTED_URL(mediaUrl); + + auto *stream = new QFile(u":"_s + mediaUrl->path()); + + QVERIFY(stream->open(QFile::ReadOnly)); + + QMediaPlayer &player = m_fixture->player; + + player.setSourceDevice(stream); player.play(); + QTRY_COMPARE_GT(player.position(), 100); - //position is reset to start - QTRY_VERIFY(player.position() < 100); - QTRY_VERIFY(positionSpy.count() > 0); - QCOMPARE(positionSpy.first()[0].value<qint64>(), 0); + if (streamOutlivesPlayer) + stream->setParent(&player); + else + delete stream; +} - QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); +void tst_QMediaPlayerBackend::play_succeedsFromSourceDevice_data() +{ + QTest::addColumn<MaybeUrl>("mediaUrl"); + QTest::addColumn<bool>("streamOutlivesPlayer"); + + QTest::addRow("audio file") << m_localWavFile << true; + QTest::addRow("video file") << m_localVideoFile << true; + + // QMediaPlayer crashes when we delete the stream during playback + constexpr bool validateStreamDestructionDuringPlayback = false; + if constexpr (validateStreamDestructionDuringPlayback) { + QTest::addRow("audio file, stream destroyed during playback") << m_localWavFile << false; + QTest::addRow("video file, stream destroyed during playback") << m_localVideoFile << false; + } +} + +void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused() +{ + QFETCH(const MaybeUrl, mediaUrl); + + CHECK_SELECTED_URL(mediaUrl); + QMediaPlayer &player = m_fixture->player; + + // Arrange + player.setSource(*mediaUrl); + player.play(); + QTRY_COMPARE_GT(player.position(), 100); + player.pause(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + m_fixture->clearSpies(); - QCOMPARE(stateSpy.count(), 1); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::PlayingState); - QVERIFY(statusSpy.count() > 0); - QCOMPARE(statusSpy.last()[0].value<QMediaPlayer::MediaStatus>(), QMediaPlayer::BufferedMedia); + if (!isGStreamerPlatform()) // Gstreamer may see EOS already + QCOMPARE_GT(player.position(), 100); - positionSpy.clear(); - QTRY_VERIFY(player.position() > 100); - QTRY_VERIFY(positionSpy.count() > 0); - QVERIFY(positionSpy.last()[0].value<qint64>() > 100); - 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); + // Act + player.stop(); + + // Assert QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.count(), 2); - QCOMPARE(stateSpy.last()[0].value<QMediaPlayer::PlaybackState>(), QMediaPlayer::StoppedState); + QTRY_COMPARE(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() && *mediaUrl == *m_localWavFile) { + // QTBUG-124517: for some media types gstreamer does not emit buffer progress messages + } else { + QCOMPARE(m_fixture->bufferProgressChanged, SignalList({ { 0.f } })); + } + + QTRY_COMPARE(m_fixture->player.position(), qint64(0)); + + QTRY_VERIFY(!m_fixture->positionChanged.empty()); + QCOMPARE(m_fixture->positionChanged.last()[0].value<qint64>(), qint64(0)); + QVERIFY(player.duration() > 0); +} + +void tst_QMediaPlayerBackend::stop_entersStoppedState_whenPlayerWasPaused_data() +{ + QTest::addColumn<MaybeUrl>("mediaUrl"); + + QTest::addRow("audio file") << m_localWavFile; + QTest::addRow("video file") << m_localVideoFile; +} + +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::setPlaybackRate_changesPlaybackDuration() +{ + using namespace std::chrono; + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + + // speeding up a 15s file by 3 should result in a duration of 5s + // auto minDuration = 3s; + // auto maxDuration = 7s; + // auto playbackRate = 3.0; + + // speeding up a 15s file by 5 should result in a duration of 3s + auto minDuration = 2s; + auto maxDuration = 4s; + auto playbackRate = 5.0; + + QFETCH(const QLatin1String, testMode); + + QMediaPlayer &player = m_fixture->player; + + if (testMode == "SetRateBeforeSetSource"_L1) + player.setPlaybackRate(playbackRate); + + player.setSource(*m_15sVideo); + + QTRY_COMPARE_EQ(player.mediaStatus(), QMediaPlayer::LoadedMedia); + + auto begin = steady_clock::now(); + + if (testMode == "SetRateBeforePlay"_L1) { + QSKIP_GSTREAMER("FIXME: SetRateBeforeSetSource is currently broken"); + player.setPlaybackRate(playbackRate); + } + + player.play(); + + if (testMode == "SetRateAfterPlay"_L1) + player.setPlaybackRate(playbackRate); + + if (testMode == "SetRateAfterPlaybackStarted"_L1) { + QTRY_COMPARE_GT(player.position(), 50); + player.setPlaybackRate(playbackRate); + } + + QCOMPARE(player.playbackRate(), playbackRate); + + QTRY_COMPARE_EQ_WITH_TIMEOUT(player.playbackState(), QMediaPlayer::StoppedState, 20s); + + auto end = steady_clock::now(); + auto duration = end - begin; + + if (false) + qDebug() << round<milliseconds>(duration); + + QCOMPARE_LT(duration, maxDuration); + QCOMPARE_GT(duration, minDuration); +} - //position stays at the end of file - QCOMPARE(player.position(), player.duration()); - QTRY_VERIFY(positionSpy.count() > 0); - QTRY_COMPARE(positionSpy.last()[0].value<qint64>(), player.duration()); +void tst_QMediaPlayerBackend::setPlaybackRate_changesPlaybackDuration_data() +{ + QTest::addColumn<QLatin1String>("testMode"); + + QTest::addRow("SetRateBeforeSetSource") << "SetRateBeforeSetSource"_L1; + QTest::addRow("SetRateBeforePlay") << "SetRateBeforePlay"_L1; + QTest::addRow("SetRateAfterPlay") << "SetRateAfterPlay"_L1; + QTest::addRow("SetRateAfterPlaybackStarted") << "SetRateAfterPlaybackStarted"_L1; +} + +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"); + + 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); + } + + m_fixture->player.play(); + 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); + + //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(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.playbackState(), 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); - QTRY_COMPARE(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.playbackState(), 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::PlaybackState>(), 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() @@ -603,7 +2087,7 @@ private slots: void onMediaStatusChanged(QMediaPlayer::MediaStatus status) { if (status == QMediaPlayer::EndOfMedia) { - player-> deleteLater(); + player->deleteLater(); player = nullptr; } } @@ -616,78 +2100,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->setSource(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; - QAudioOutput output; - player.setAudioOutput(&output); - QCOMPARE(output.volume(), 1.); - QVERIFY(!output.isMuted()); - - player.setSource(localWavFile); - player.pause(); - - QCOMPARE(output.volume(), 1.); - QVERIFY(!output.isMuted()); - - QSignalSpy volumeSpy(&output, SIGNAL(volumeChanged(float))); - QSignalSpy mutedSpy(&output, SIGNAL(mutedChanged(bool))); - - //setting volume to 0 should not trigger muted - output.setVolume(0); - QTRY_COMPARE(output.volume(), 0); - QVERIFY(!output.isMuted()); - QCOMPARE(volumeSpy.count(), 1); - QCOMPARE(volumeSpy.last()[0].toFloat(), output.volume()); - QCOMPARE(mutedSpy.count(), 0); - - output.setVolume(0.5); - QTRY_COMPARE(output.volume(), 0.5); - QVERIFY(!output.isMuted()); - QCOMPARE(volumeSpy.count(), 2); - QCOMPARE(volumeSpy.last()[0].toFloat(), output.volume()); - QCOMPARE(mutedSpy.count(), 0); - - output.setMuted(true); - QTRY_VERIFY(output.isMuted()); - QVERIFY(output.volume() > 0); - QCOMPARE(volumeSpy.count(), 2); - QCOMPARE(mutedSpy.count(), 1); - QCOMPARE(mutedSpy.last()[0].toBool(), output.isMuted()); - - output.setMuted(false); - QTRY_VERIFY(!output.isMuted()); - QVERIFY(output.volume() > 0); - QCOMPARE(volumeSpy.count(), 2); - QCOMPARE(mutedSpy.count(), 2); - QCOMPARE(mutedSpy.last()[0].toBool(), output.isMuted()); - -} - void tst_QMediaPlayerBackend::volumeAcrossFiles_data() { QTest::addColumn<int>("volume"); @@ -703,12 +2137,15 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles_data() void tst_QMediaPlayerBackend::volumeAcrossFiles() { + CHECK_SELECTED_URL(m_localWavFile); + QFETCH(int, volume); QFETCH(bool, muted); float vol = volume/100.; - QMediaPlayer player; QAudioOutput output; + QMediaPlayer player; + player.setAudioOutput(&output); //volume and muted should not be preserved between player instances @@ -721,7 +2158,7 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles() QTRY_COMPARE(output.volume(), vol); QTRY_COMPARE(output.isMuted(), muted); - player.setSource(localWavFile); + player.setSource(*m_localWavFile); QCOMPARE(output.volume(), vol); QCOMPARE(output.isMuted(), muted); @@ -737,7 +2174,7 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles() QTRY_COMPARE(output.volume(), vol); QCOMPARE(output.isMuted(), muted); - player.setSource(localWavFile); + player.setSource(*m_localWavFile); player.pause(); QTRY_COMPARE(output.volume(), vol); @@ -746,15 +2183,14 @@ void tst_QMediaPlayerBackend::volumeAcrossFiles() void tst_QMediaPlayerBackend::initialVolume() { - if (!isWavSupported()) - QSKIP("Sound format is not supported"); + CHECK_SELECTED_URL(m_localWavFile); { - QMediaPlayer player; QAudioOutput output; + QMediaPlayer player; player.setAudioOutput(&output); output.setVolume(1); - player.setSource(localWavFile); + player.setSource(*m_localWavFile); QCOMPARE(output.volume(), 1); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); @@ -762,10 +2198,10 @@ void tst_QMediaPlayerBackend::initialVolume() } { - QMediaPlayer player; QAudioOutput output; + QMediaPlayer player; player.setAudioOutput(&output); - player.setSource(localWavFile); + player.setSource(*m_localWavFile); QCOMPARE(output.volume(), 1); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); @@ -775,22 +2211,25 @@ void tst_QMediaPlayerBackend::initialVolume() void tst_QMediaPlayerBackend::seekPauseSeek() { - if (localVideoFile.isEmpty()) - 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); - QMediaPlayer player; + TestVideoSink surface(true); QAudioOutput output; + QMediaPlayer player; + player.setAudioOutput(&output); - QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); + QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged); - TestVideoSink *surface = new TestVideoSink; - player.setVideoOutput(surface); + player.setVideoOutput(&surface); - player.setSource(localVideoFile); + 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() + QVERIFY(surface.m_frameList.isEmpty()); // frame must not appear until we call pause() or play() positionSpy.clear(); qint64 position = 7000; @@ -799,21 +2238,22 @@ void tst_QMediaPlayerBackend::seekPauseSeek() 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.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 + 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(); + 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()); - QCOMPARE(frame.width(), 160); + QCOMPARE(frame.width(), 213); QCOMPARE(frame.height(), 120); // create QImage for QVideoFrame to verify RGB pixel colors @@ -824,19 +2264,19 @@ void tst_QMediaPlayerBackend::seekPauseSeek() QVERIFY(qBlue(image.pixel(0, 0)) < 20); } - 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.playbackState(), QMediaPlayer::PausedState); - QTRY_VERIFY(!surface->m_frameList.isEmpty()); + QTRY_VERIFY(!surface.m_frameList.isEmpty()); { - QVideoFrame frame = surface->m_frameList.back(); + QVideoFrame frame = surface.m_frameList.back(); const qint64 elapsed = (frame.startTime() / 1000) - position; QVERIFY2(qAbs(elapsed) < (qint64)500, QByteArray::number(elapsed).constData()); - QCOMPARE(frame.width(), 160); + QCOMPARE(frame.width(), 213); QCOMPARE(frame.height(), 120); QImage image = frame.toImage(); @@ -849,19 +2289,19 @@ void tst_QMediaPlayerBackend::seekPauseSeek() void tst_QMediaPlayerBackend::seekInStoppedState() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + CHECK_SELECTED_URL(m_localVideoFile); - QMediaPlayer player; + TestVideoSink surface(false); QAudioOutput output; + QMediaPlayer player; + player.setAudioOutput(&output); - TestVideoSink surface(false); player.setVideoOutput(&surface); - QSignalSpy stateSpy(&player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); - QSignalSpy positionSpy(&player, SIGNAL(positionChanged(qint64))); + QSignalSpy stateSpy(&player, &QMediaPlayer::playbackStateChanged); + QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged); - player.setSource(localVideoFile); + player.setSource(*m_localVideoFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); QCOMPARE(player.position(), 0); @@ -874,11 +2314,11 @@ void tst_QMediaPlayerBackend::seekInStoppedState() player.setPosition(position); QTRY_VERIFY(qAbs(player.position() - position) < qint64(200)); - QTRY_VERIFY(positionSpy.count() > 0); + QTRY_VERIFY(positionSpy.size() > 0); QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200)); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.count(), 0); + QCOMPARE(stateSpy.size(), 0); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); @@ -888,12 +2328,11 @@ void tst_QMediaPlayerBackend::seekInStoppedState() QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_VERIFY(player.position() > position); - QCOMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QTest::qWait(100); // Check that it never played from the beginning QVERIFY(player.position() > position); - for (int i = 0; i < positionSpy.count(); ++i) + for (int i = 0; i < positionSpy.size(); ++i) QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200)); // ------ @@ -910,11 +2349,11 @@ void tst_QMediaPlayerBackend::seekInStoppedState() player.setPosition(position); QTRY_VERIFY(qAbs(player.position() - position) < qint64(200)); - QTRY_VERIFY(positionSpy.count() > 0); + QTRY_VERIFY(positionSpy.size() > 0); QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200)); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.count(), 0); + QCOMPARE(stateSpy.size(), 0); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); @@ -923,13 +2362,12 @@ void tst_QMediaPlayerBackend::seekInStoppedState() player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); QVERIFY(qAbs(player.position() - position) < qint64(200)); QTest::qWait(500); // Check that it never played from the beginning QVERIFY(player.position() > (position - 200)); - for (int i = 0; i < positionSpy.count(); ++i) + for (int i = 0; i < positionSpy.size(); ++i) QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200)); // ------ @@ -946,16 +2384,17 @@ void tst_QMediaPlayerBackend::seekInStoppedState() player.setPosition(position); QTRY_VERIFY(qAbs(player.position() - position) < qint64(200)); - QTRY_VERIFY(positionSpy.count() > 0); + QTRY_VERIFY(positionSpy.size() > 0); QVERIFY(qAbs(positionSpy.last()[0].value<qint64>() - position) < qint64(200)); QCOMPARE(player.playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(stateSpy.count(), 0); + QCOMPARE(stateSpy.size(), 0); QCOMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); player.play(); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::BufferedMedia); + QTRY_VERIFY(player.mediaStatus() == QMediaPlayer::BufferedMedia + || player.mediaStatus() == QMediaPlayer::EndOfMedia); positionSpy.clear(); QTRY_VERIFY(player.position() > (position - 200)); @@ -963,19 +2402,20 @@ void tst_QMediaPlayerBackend::seekInStoppedState() QTest::qWait(500); // Check that it never played from the beginning QVERIFY(player.position() > (position - 200)); - for (int i = 0; i < positionSpy.count(); ++i) + for (int i = 0; i < positionSpy.size(); ++i) QVERIFY(positionSpy.at(i)[0].value<qint64>() > (position - 200)); } void tst_QMediaPlayerBackend::subsequentPlayback() { - if (localCompressedSoundFile.isEmpty()) - QSKIP("Sound format is not supported"); + QSKIP_GSTREAMER("QTBUG-124005: spurious seek failures with gstreamer"); + + CHECK_SELECTED_URL(m_localCompressedSoundFile); - QMediaPlayer player; QAudioOutput output; + QMediaPlayer player; player.setAudioOutput(&output); - player.setSource(localCompressedSoundFile); + player.setSource(*m_localCompressedSoundFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QTRY_VERIFY(player.isSeekable()); player.setPosition(5000); @@ -983,7 +2423,7 @@ void tst_QMediaPlayerBackend::subsequentPlayback() QCOMPARE(player.error(), QMediaPlayer::NoError); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + 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); @@ -991,7 +2431,7 @@ void tst_QMediaPlayerBackend::subsequentPlayback() player.play(); QTRY_COMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_VERIFY(player.position() > 1000); + QTRY_COMPARE_GT(player.position(), 1000); player.pause(); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); // make sure position does not "jump" closer to the end of the file @@ -1001,26 +2441,26 @@ void tst_QMediaPlayerBackend::subsequentPlayback() QTRY_COMPARE(player.position(), qint64(0)); player.play(); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); - QTRY_VERIFY(player.position() > 1000); + QTRY_COMPARE_GT(player.position(), 1000); player.pause(); QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); - QVERIFY(player.position() > 1000); + QCOMPARE_GT(player.position(), 1000); } void tst_QMediaPlayerBackend::multipleMediaPlayback() { - if (localVideoFile.isEmpty() || localVideoFile2.isEmpty()) - QSKIP("Video format is not supported"); + CHECK_SELECTED_URL(m_localVideoFile); + CHECK_SELECTED_URL(m_localVideoFile2); + QAudioOutput output; TestVideoSink surface(false); QMediaPlayer player; - QAudioOutput output; player.setVideoOutput(&surface); player.setAudioOutput(&output); - player.setSource(localVideoFile); + player.setSource(*m_localVideoFile); - QCOMPARE(player.source(), localVideoFile); + QCOMPARE(player.source(), *m_localVideoFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); player.setPosition(0); @@ -1028,14 +2468,15 @@ void tst_QMediaPlayerBackend::multipleMediaPlayback() QCOMPARE(player.error(), QMediaPlayer::NoError); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); + QVERIFY(player.isSeekable()); QTRY_VERIFY(player.position() > 0); - QCOMPARE(player.source(), localVideoFile); + QCOMPARE(player.source(), *m_localVideoFile); player.stop(); - player.setSource(localVideoFile2); + player.setSource(*m_localVideoFile2); - QCOMPARE(player.source(), localVideoFile2); + QCOMPARE(player.source(), *m_localVideoFile2); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); QTRY_VERIFY(player.isSeekable()); @@ -1045,95 +2486,437 @@ void tst_QMediaPlayerBackend::multipleMediaPlayback() QCOMPARE(player.error(), QMediaPlayer::NoError); QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); QTRY_VERIFY(player.position() > 0); - QCOMPARE(player.source(), localVideoFile2); + QCOMPARE(player.source(), *m_localVideoFile2); player.stop(); QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); } -void tst_QMediaPlayerBackend::surfaceTest() +void tst_QMediaPlayerBackend::multiplePlaybackRateChangingStressTest() { - // 25 fps video file - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + 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); - QMediaPlayer player; QAudioOutput output; + QMediaPlayer player; + player.setAudioOutput(&output); player.setVideoOutput(&surface); - player.setSource(localVideoFile); + + player.setSource(*m_localVideoFile3ColorsWithSound); + player.play(); - QTRY_VERIFY(player.position() >= 1000); - QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QString("Expected >= 25, got %1").arg(surface.m_totalFrames))); + + surface.waitForFrame(); + + QSignalSpy spy(&player, &QMediaPlayer::playbackStateChanged); + + using namespace std::chrono_literals; + using namespace std::chrono; + + constexpr milliseconds expectedVideoDuration = 3000ms; + constexpr milliseconds waitingInterval = 200ms; + constexpr milliseconds maxDuration = expectedVideoDuration + 2000ms; + constexpr milliseconds minDuration = expectedVideoDuration - 100ms; + constexpr milliseconds maxFrameDelay = 2000ms; + + surface.m_elapsedTimer.start(); + + nanoseconds duration = 0ns; + + auto waitForPlaybackStateChange = [&]() { + QElapsedTimer timer; + timer.start(); + + QScopeGuard addDuration([&]() { + duration += duration_cast<nanoseconds>(timer.durationElapsed() * player.playbackRate()); + }); + return spy.wait(waitingInterval); + }; + + for (int i = 0; !waitForPlaybackStateChange(); ++i) { + player.setPlaybackRate(0.5 * (i % 4 + 1)); + + QCOMPARE_LE(duration, maxDuration); + + QVERIFY2(surface.m_elapsedTimer.durationElapsed() < maxFrameDelay, + "If the delay is more than 2s, we consider the video playing is hanging."); + + /* 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(); + }*/ + } + + 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); } -#if 0 -void tst_QMediaPlayerBackend::multipleSurfaces() +void tst_QMediaPlayerBackend::multipleSeekStressTest() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + QSKIP_GSTREAMER("QTBUG-124005: spurious test failures with gstreamer"); - QVideoSink surface1; - QVideoSink surface2; +#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; - player.setVideoOutput(QList<QVideoSink *>() << &surface1 << &surface2); - player.setSource(localVideoFile); + + player.setAudioOutput(&output); + player.setVideoOutput(&surface); + + player.setSource(*m_localVideoFile3ColorsWithSound); + 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); + + auto waitAndCheckFrame = [&](qint64 pos, QString checkInfo) { + auto errorPrintingGuard = qScopeGuard([&]() { + qDebug() << "Error:" << checkInfo; + qDebug() << "Position:" << pos; + }); + + auto frame = surface.waitForFrame(); + QVERIFY(frame.isValid()); + + const auto trackTime = pos * 1000; + + // 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_GE(frame.startTime(), trackTime - 200'000); + QCOMPARE_LE(frame.endTime(), trackTime + 200'000); + + auto frameImage = frame.toImage(); + const auto actualColor = frameImage.pixel(1, 1); + + const auto actualColorIndex = findSimilarColorIndex(m_video3Colors, actualColor); + + const auto expectedColorIndex = pos / 1000; + + QCOMPARE(actualColorIndex, expectedColorIndex); + + errorPrintingGuard.dismiss(); + }; + + auto seekAndCheck = [&](qint64 pos) { + QSignalSpy positionSpy(&player, &QMediaPlayer::positionChanged); + player.setPosition(pos); + + QTRY_VERIFY(positionSpy.size() >= 1); + int setPosition = positionSpy.first().first().toInt(); + QCOMPARE_GT(setPosition, pos - 100); + QCOMPARE_LT(setPosition, pos + 100); + }; + + constexpr qint64 posInterval = 10; + + { + for (qint64 pos = posInterval; pos <= 2200; pos += posInterval) + seekAndCheck(pos); + + waitAndCheckFrame(2200, "emulate fast moving of a seek slider forward"); + + QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); + } + + { + for (qint64 pos = 2100; pos >= 800; pos -= posInterval) + seekAndCheck(pos); + + waitAndCheckFrame(800, "emulate fast moving of a seek slider backward"); + + QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::PlayingState); + } + + { + player.pause(); + + for (qint64 pos = 500; pos <= 1100; pos += posInterval) + seekAndCheck(pos); + + waitAndCheckFrame(1100, "emulate fast moving of a seek slider forward on paused state"); + + QCOMPARE_NE(player.mediaStatus(), QMediaPlayer::EndOfMedia); + QCOMPARE(player.playbackState(), QMediaPlayer::PausedState); + } +} + +void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime_data() +{ + QTest::addColumn<bool>("withAudio"); + QTest::addColumn<int>("positionDeviationMs"); + + QTest::newRow("Without audio") << false << 170; + + // set greater positionDeviationMs for case with audio due to possible synchronization. + QTest::newRow("With audio") << true << 200; } + +void tst_QMediaPlayerBackend::setPlaybackRate_changesActualRateAndFramesRenderingTime() +{ + QSKIP_GSTREAMER("QTBUG-124005: timing issues"); + + QFETCH(bool, withAudio); + QFETCH(int, positionDeviationMs); + +#ifdef Q_OS_ANDROID + QSKIP("frame.toImage will return null image because of QTBUG-108446"); #endif + CHECK_SELECTED_URL(m_localVideoFile3ColorsWithSound); -void tst_QMediaPlayerBackend::metadata() +#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(); + + 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() { - if (localFileWithMetadata.isEmpty()) - QSKIP("No supported media file"); + QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery"); + + CHECK_SELECTED_URL(m_localVideoFile); + // 25 fps video file - QMediaPlayer player; QAudioOutput output; + TestVideoSink surface(false); + QMediaPlayer player; player.setAudioOutput(&output); + player.setVideoOutput(&surface); + player.setSource(*m_localVideoFile); + player.play(); + QTRY_VERIFY(player.position() >= 1000); + QVERIFY2(surface.m_totalFrames >= 25, qPrintable(QStringLiteral("Expected >= 25, got %1").arg(surface.m_totalFrames))); +} - QSignalSpy metadataChangedSpy(&player, SIGNAL(metaDataChanged())); +void tst_QMediaPlayerBackend::metadata() +{ + // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage + QMediaMetaData::Key thumbnailKey = + isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage; - player.setSource(localFileWithMetadata); + CHECK_SELECTED_URL(m_localFileWithMetadata); - QTRY_VERIFY(metadataChangedSpy.count() > 0); + m_fixture->player.setSource(*m_localFileWithMetadata); - QCOMPARE(player.metaData().value(QMediaMetaData::Title).toString(), QStringLiteral("Nokia Tune")); - QCOMPARE(player.metaData().value(QMediaMetaData::ContributingArtist).toString(), QStringLiteral("TestArtist")); - QCOMPARE(player.metaData().value(QMediaMetaData::AlbumTitle).toString(), QStringLiteral("TestAlbum")); + QTRY_VERIFY(m_fixture->metadataChanged.size() > 0); - metadataChangedSpy.clear(); + 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(); - player.setSource(QUrl()); + m_fixture->player.setSource(QUrl()); - QCOMPARE(metadataChangedSpy.count(), 1); - QVERIFY(player.metaData().isEmpty()); + QCOMPARE(m_fixture->metadataChanged.size(), 1); + QVERIFY(m_fixture->player.metaData().isEmpty()); +} + +void tst_QMediaPlayerBackend::metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail_data() +{ + 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); +} + +void tst_QMediaPlayerBackend::metadata_returnsMetadataWithThumbnail_whenMediaHasThumbnail() +{ + // QTBUG-124380: gstreamer reports CoverArtImage instead of ThumbnailImage + QMediaMetaData::Key key = + isGStreamerPlatform() ? QMediaMetaData::CoverArtImage : QMediaMetaData::ThumbnailImage; + + // Arrange + QFETCH(const MaybeUrl, mediaUrl); + QFETCH(const bool, hasThumbnail); + QFETCH(const QSize, expectedSize); + QFETCH(const QColor, expectedColor); + + CHECK_SELECTED_URL(mediaUrl); + + 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); + + if (!isFFMPEGPlatform() && !isDarwinPlatform()) + QSKIP("This test is only for FFmpeg and Darwin backends"); + + m_fixture->player.setSource(*mediaUrl); + QTRY_VERIFY(!m_fixture->metadataChanged.empty()); + + const QMediaMetaData metadata = m_fixture->player.videoTracks().front(); + const bool hdrContent = metadata.value(QMediaMetaData::HasHdrContent).value<bool>(); + + QCOMPARE_EQ(hasHdrContent, hdrContent); } void tst_QMediaPlayerBackend::playerStateAtEOS() { - if (!isWavSupported()) - QSKIP("Sound format is not supported"); + CHECK_SELECTED_URL(m_localWavFile); - QMediaPlayer player; 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.playbackState(), QMediaPlayer::StoppedState); endOfMediaReceived = true; } }); - player.setSource(localWavFile); + player.setSource(*m_localWavFile); player.play(); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::EndOfMedia); @@ -1142,74 +2925,106 @@ void tst_QMediaPlayerBackend::playerStateAtEOS() void tst_QMediaPlayerBackend::playFromBuffer() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + QSKIP_GSTREAMER("QTBUG-124005: spurious failure, probably asynchronous event delivery"); + + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(false); QMediaPlayer player; player.setVideoOutput(&surface); - QFile file(localVideoFile.toLocalFile()); - if (!file.open(QIODevice::ReadOnly)) - QSKIP("Could not open file"); - player.setSourceDevice(&file, localVideoFile); + 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); - 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::audioVideoAvailable() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(false); QAudioOutput output; QMediaPlayer player; - QSignalSpy hasVideoSpy(&player, SIGNAL(hasVideoChanged(bool))); - QSignalSpy hasAudioSpy(&player, SIGNAL(hasAudioChanged(bool))); + QSignalSpy hasVideoSpy(&player, &QMediaPlayer::hasVideoChanged); + QSignalSpy hasAudioSpy(&player, &QMediaPlayer::hasAudioChanged); player.setVideoOutput(&surface); player.setAudioOutput(&output); - player.setSource(localVideoFile); + player.setSource(*m_localVideoFile); QTRY_VERIFY(player.hasVideo()); QTRY_VERIFY(player.hasAudio()); - QCOMPARE(hasVideoSpy.count(), 1); - QCOMPARE(hasAudioSpy.count(), 1); + QCOMPARE(hasVideoSpy.size(), 1); + QCOMPARE(hasAudioSpy.size(), 1); player.setSource(QUrl()); QTRY_VERIFY(!player.hasVideo()); QTRY_VERIFY(!player.hasAudio()); - QCOMPARE(hasVideoSpy.count(), 2); - QCOMPARE(hasAudioSpy.count(), 2); + QCOMPARE(hasVideoSpy.size(), 2); + QCOMPARE(hasAudioSpy.size(), 2); +} + +void tst_QMediaPlayerBackend::audioVideoAvailable_updatedOnNewMedia() +{ + 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 { + QCOMPARE(hasAudioSpy.size(), 0); + } } void tst_QMediaPlayerBackend::isSeekable() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(false); QMediaPlayer player; player.setVideoOutput(&surface); -#ifdef Q_OS_ANDROID - QEXPECT_FAIL("", "On Android isSeekable() is always set to true due to QTBUG-96952", Continue); -#endif QVERIFY(!player.isSeekable()); - player.setSource(localVideoFile); + player.setSource(*m_localVideoFile); QTRY_VERIFY(player.isSeekable()); } void tst_QMediaPlayerBackend::positionAfterSeek() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(false); QMediaPlayer player; player.setVideoOutput(&surface); -#ifdef Q_OS_ANDROID - QEXPECT_FAIL("", "On Android isSeekable() is always set to true due to QTBUG-96952", Continue); -#endif QVERIFY(!player.isSeekable()); - player.setSource(localVideoFile); + player.setSource(*m_localVideoFile); QTRY_COMPARE(player.mediaStatus(), QMediaPlayer::LoadedMedia); player.pause(); player.setPosition(500); @@ -1224,41 +3039,59 @@ void tst_QMediaPlayerBackend::positionAfterSeek() QTRY_VERIFY(player.position() < 700); } -void tst_QMediaPlayerBackend::videoDimensions() +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() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); +#ifdef Q_OS_ANDROID + QSKIP("SKIP initTestCase on CI, because of QTBUG-126428"); +#endif + 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); -#ifdef Q_OS_ANDROID - QEXPECT_FAIL("", "On Android isSeekable() is always set to true due to QTBUG-96952", Continue); -#endif QVERIFY(!player.isSeekable()); - player.setSource(videoDimensionTestFile); + 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); - QCOMPARE(surface.m_frameList.last().width(), 540); - QCOMPARE(surface.videoSize().width(), 540); - QCOMPARE(surface.m_frameList.last().height(), 320); - QCOMPARE(surface.videoSize().height(), 320); + + // 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() { - if (localVideoFile.isEmpty()) - QSKIP("No supported video file"); + CHECK_SELECTED_URL(m_localVideoFile); TestVideoSink surface(true); QMediaPlayer player; player.setVideoOutput(&surface); -#ifdef Q_OS_ANDROID - QEXPECT_FAIL("", "On Android isSeekable() is always set to true due to QTBUG-96952", Continue); -#endif QVERIFY(!player.isSeekable()); - player.setSource(localVideoFile); + player.setSource(*m_localVideoFile); QTRY_VERIFY(player.isSeekable()); player.play(); @@ -1276,6 +3109,1141 @@ void tst_QMediaPlayerBackend::position() 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 ¶ms : 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); + } +} + +void tst_QMediaPlayerBackend::infiniteLoops() +{ + 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()); + } + + 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); + + 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 tst_QMediaPlayerBackend::changeLoopsOnTheFly() +{ + 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); +} + +void tst_QMediaPlayerBackend::seekAfterLoopReset() +{ + 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"); + + 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() +{ + 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() +{ +#ifdef Q_OS_ANDROID + QSKIP("SKIP initTestCase on CI, because of QTBUG-126428"); +#endif + 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 tst_QMediaPlayerBackend::setMedia_setsVideoSinkSize_beforePlaying() +{ + 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); +} + +#if QT_CONFIG(process) +std::unique_ptr<QProcess> tst_QMediaPlayerBackend::createRtpStreamProcess(QString fileName, + QString sdpUrl) +{ + 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 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); +#ifdef Q_OS_ANDROID + QSKIP("frame.toImage will return null image because of QTBUG-108446"); +#endif + 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); +} + +void tst_QMediaPlayerBackend::setVideoOutput_doesNotStopPlayback() +{ + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + + QFETCH(QMediaPlayer::PlaybackState, playbackState); + + TestVideoSink surface(false); + QAudioOutput audioOut; + + QMediaPlayer player; + player.setAudioOutput(&audioOut); + player.setSource(*m_15sVideo); + + switch (playbackState) { + case QMediaPlayer::StoppedState: + break; + case QMediaPlayer::PausedState: + QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend"); + player.pause(); + break; + case QMediaPlayer::PlayingState: + QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend"); + QSKIP_GSTREAMER("QTBUG-124005: Test failure with the gstreamer backend"); + player.play(); + break; + } + + // set video output + QTest::qWait(1s); + player.setVideoOutput(&surface); + + if (playbackState == QMediaPlayer::PlayingState) { + QVideoFrame frame = surface.waitForFrame(); + QCOMPARE(frame.size(), QSize(20, 20)); + } + + // unset video output + QTest::qWait(1s); + player.setVideoOutput(nullptr); + + // wait for play until end + if (playbackState != QMediaPlayer::PlayingState) + player.play(); + + player.setPlaybackRate(5); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); +} + +void tst_QMediaPlayerBackend::setVideoOutput_doesNotStopPlayback_data() +{ + QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState"); + QTest::newRow("StoppedState") << QMediaPlayer::StoppedState; + QTest::newRow("PausedState") << QMediaPlayer::PausedState; + QTest::newRow("PlayingState") << QMediaPlayer::PlayingState; +} + +void tst_QMediaPlayerBackend::setAudioOutput_doesNotStopPlayback() +{ + QSKIP_FFMPEG("QTBUG-126014: Test failure with the ffmpeg backend"); + + using namespace std::chrono_literals; + + CHECK_SELECTED_URL(m_15sVideo); + QFETCH(QMediaPlayer::PlaybackState, playbackState); + + TestVideoSink surface(false); + QAudioOutput audioOut; + + QMediaPlayer player; + player.setVideoOutput(&surface); + player.setSource(*m_15sVideo); + + switch (playbackState) { + case QMediaPlayer::StoppedState: + break; + case QMediaPlayer::PausedState: + player.pause(); + break; + case QMediaPlayer::PlayingState: + player.play(); + break; + } + + // set audio output + QTest::qWait(1s); + player.setAudioOutput(&audioOut); + + // unset audio output + QTest::qWait(1s); + player.setAudioOutput(nullptr); + + // wait for play until end + if (playbackState != QMediaPlayer::PlayingState) + player.play(); + player.setPlaybackRate(5); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); +} + +void tst_QMediaPlayerBackend::setAudioOutput_doesNotStopPlayback_data() +{ + QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState"); + QTest::newRow("StoppedState") << QMediaPlayer::StoppedState; + QTest::newRow("PausedState") << QMediaPlayer::PausedState; + QTest::newRow("PlayingState") << QMediaPlayer::PlayingState; +} + +void tst_QMediaPlayerBackend::swapAudioDevice_doesNotStopPlayback() +{ + using namespace std::chrono_literals; + + const QList<QAudioDevice> outputDevices = QMediaDevices::audioOutputs(); + + if (outputDevices.size() < 2) + QSKIP("swapAudioDevice_doesNotStopPlayback requires two audio output devices"); + + CHECK_SELECTED_URL(m_15sVideo); + QFETCH(QMediaPlayer::PlaybackState, playbackState); + + TestVideoSink surface(false); + QAudioOutput audioOut; + + QMediaPlayer player; + player.setVideoOutput(&surface); + player.setAudioOutput(&audioOut); + player.setSource(*m_15sVideo); + switch (playbackState) { + case QMediaPlayer::StoppedState: + break; + case QMediaPlayer::PausedState: + player.pause(); + break; + case QMediaPlayer::PlayingState: + player.play(); + break; + } + + // swap output device + QTest::qWait(1s); + audioOut.setDevice(outputDevices[0]); + + QTest::qWait(1s); + audioOut.setDevice(outputDevices[1]); + + QTest::qWait(1s); + audioOut.setDevice(outputDevices[0]); + + // wait for play until end + if (playbackState != QMediaPlayer::PlayingState) + player.play(); + player.setPlaybackRate(5); + QTRY_COMPARE(player.playbackState(), QMediaPlayer::StoppedState); +} + +void tst_QMediaPlayerBackend::swapAudioDevice_doesNotStopPlayback_data() +{ + QTest::addColumn<QMediaPlayer::PlaybackState>("playbackState"); + QTest::newRow("StoppedState") << QMediaPlayer::StoppedState; + QTest::newRow("PausedState") << QMediaPlayer::PausedState; + QTest::newRow("PlayingState") << QMediaPlayer::PlayingState; +} + +void tst_QMediaPlayerBackend::play_readsSubtitle() +{ + using namespace std::chrono_literals; + CHECK_SELECTED_URL(m_subtitleVideo); + + QVideoSink &sink = m_fixture->surface; + QMediaPlayer &player = m_fixture->player; + + TestSubtitleSink subtitleSink; + QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink, + &TestSubtitleSink::addSubtitle); + + player.setSource(*m_subtitleVideo); + QTRY_COMPARE(player.subtitleTracks().size(), 1); + QCOMPARE_EQ(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 3000); + + player.setActiveSubtitleTrack(0); + + if (!isGStreamerPlatform()) // FIXME: spurious deadlocks + player.setPlaybackRate(5.f); + + player.play(); + + QStringList expectedSubtitleList = { + u"Hello"_s, + u""_s, + u"World"_s, + u""_s, + }; + + QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitleList); +} + +void tst_QMediaPlayerBackend::multiTrack_validateMetadata() +{ + CHECK_SELECTED_URL(m_multitrackVideo); + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + QTRY_COMPARE(player.videoTracks().size(), 2); + QTRY_COMPARE(player.audioTracks().size(), 2); + QTRY_COMPARE(player.subtitleTracks().size(), 2); + + QSKIP_GSTREAMER("GStreamer does not provide correct track order"); + + QCOMPARE(player.videoTracks()[0][QMediaMetaData::Title], u"One"_s); + QCOMPARE(player.videoTracks()[1][QMediaMetaData::Title], u"Two"_s); + + QCOMPARE(player.audioTracks()[0][QMediaMetaData::Language], QLocale::Language::English); + QCOMPARE(player.audioTracks()[1][QMediaMetaData::Language], QLocale::Language::Spanish); + QCOMPARE(player.subtitleTracks()[0][QMediaMetaData::Language], QLocale::Language::English); + QCOMPARE(player.subtitleTracks()[1][QMediaMetaData::Language], QLocale::Language::Spanish); +} + +void tst_QMediaPlayerBackend::play_readsSubtitle_fromMultiTrack() +{ + using namespace std::chrono_literals; + CHECK_SELECTED_URL(m_multitrackVideo); + + QFETCH(int, track); + QFETCH(const QStringList, expectedSubtitles); + + QVideoSink &sink = m_fixture->surface; + QMediaPlayer &player = m_fixture->player; + + TestSubtitleSink subtitleSink; + QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink, + &TestSubtitleSink::addSubtitle); + + player.setSource(*m_multitrackVideo); + + QTRY_COMPARE(player.subtitleTracks().size(), 2); + + if (track != -1) { + if (isGStreamerPlatform()) + QCOMPARE(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 4000); + if (isFFMPEGPlatform()) + QCOMPARE(player.subtitleTracks()[0].value(QMediaMetaData::Duration), 15046); + } + + if (isGStreamerPlatform()) { + bool swapTracks = + player.subtitleTracks()[0][QMediaMetaData::Language] == QLocale::Language::Spanish; + + if (swapTracks && track == 1) + track = 0; + if (swapTracks && track == 0) + track = 1; + } + + player.setActiveSubtitleTrack(track); + if (!isGStreamerPlatform()) + player.setPlaybackRate(5.f); + player.play(); + + if (expectedSubtitles.isEmpty()) + QTRY_COMPARE_GT(player.position(), 2000); + + QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitles); +} + +void tst_QMediaPlayerBackend::play_readsSubtitle_fromMultiTrack_data() +{ + QSKIP_GSTREAMER("GStreamer does not provide consistent track order"); + + QTest::addColumn<int>("track"); + QTest::addColumn<QStringList>("expectedSubtitles"); + + QTest::addRow("track 0") << 0 + << QStringList{ + u"1s track 1"_s, + u""_s, + u"3s track 1"_s, + u""_s, + }; + QTest::addRow("track 1") << 1 + << QStringList{ + u"1s track 2"_s, + u""_s, + u"3s track 2"_s, + u""_s, + }; + + QTest::addRow("no subtitles") << -1 << QStringList{}; +} + +void tst_QMediaPlayerBackend::setActiveSubtitleTrack_switchesSubtitles() +{ + QVideoSink &sink = m_fixture->surface; + QMediaPlayer &player = m_fixture->player; + + QFETCH(const QUrl, media); + QFETCH(const int, positionToSwapTrack); + QFETCH(const QLatin1String, testMode); + QFETCH(const QStringList, expectedSubtitles); + + TestSubtitleSink subtitleSink; + QObject::connect(&sink, &QVideoSink::subtitleTextChanged, &subtitleSink, + &TestSubtitleSink::addSubtitle); + + player.setSource(media); + + QTRY_COMPARE(player.subtitleTracks().size(), 2); + + int track0 = 0; + int track1 = 1; + if (isGStreamerPlatform()) { + bool swapTracks = + player.subtitleTracks()[0][QMediaMetaData::Language] == QLocale::Language::Spanish; + + if (swapTracks) { + track1 = 0; + track0 = 1; + } + } + + player.setActiveSubtitleTrack(track0); + + player.play(); + QTRY_COMPARE_GT(player.position(), positionToSwapTrack); + + if (testMode == "setWhilePaused"_L1) { + player.pause(); + player.setActiveSubtitleTrack(track1); + player.play(); + } else if (testMode == "setWhilePlaying"_L1) { + player.setActiveSubtitleTrack(track1); + } else { + QFAIL("should not reach"); + } + + QTRY_COMPARE(subtitleSink.subtitles, expectedSubtitles); +} + +void tst_QMediaPlayerBackend::setActiveSubtitleTrack_switchesSubtitles_data() +{ + QSKIP_GSTREAMER("GStreamer does not provide consistent track order"); + + QTest::addColumn<QUrl>("media"); + QTest::addColumn<QLatin1String>("testMode"); + QTest::addColumn<int>("positionToSwapTrack"); + QTest::addColumn<QStringList>("expectedSubtitles"); + + QTest::addRow("while paused") << *m_multitrackVideo << "setWhilePaused"_L1 << 2100 + << QStringList{ + u"1s track 1"_s, + u""_s, + u"3s track 2"_s, + u""_s, + }; + QTest::addRow("while playing") << *m_multitrackVideo << "setWhilePlaying"_L1 << 2100 + << QStringList{ + u"1s track 1"_s, + u""_s, + u"3s track 2"_s, + u""_s, + }; + + QTest::addRow("while paused, subtitles start at zero") + << *m_multitrackSubtitleStartsAtZeroVideo << "setWhilePaused"_L1 << 1100 + << QStringList{ + u"0s track 1"_s, + u""_s, + u"2s track 2"_s, + u""_s, + }; + QTest::addRow("while playing, subtitles start at zero") + << *m_multitrackSubtitleStartsAtZeroVideo << "setWhilePlaying"_L1 << 1100 + << QStringList{ + u"0s track 1"_s, + u""_s, + u"2s track 2"_s, + u""_s, + }; +} + +void tst_QMediaPlayerBackend::setActiveVideoTrack_switchesVideoTrack() +{ + using namespace std::chrono_literals; + QSKIP_GSTREAMER("GStreamer does not provide consistent track order"); + + TestVideoSink &sink = m_fixture->surface; + sink.setStoreFrames(); + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + QTRY_COMPARE(player.videoTracks().size(), 2); + + int track0 = 0; + int track1 = 1; + if (isGStreamerPlatform()) { + bool swapTracks = player.subtitleTracks()[0][QMediaMetaData::Title] != u"One"_s; + + if (swapTracks) { + track0 = 1; + track1 = 0; + } + } + + player.setActiveVideoTrack(track0); + player.play(); + + sink.waitForFrame(); + + QTest::qWait(500ms); + sink.waitForFrame(); + QCOMPARE(sink.m_frameList.back().toImage().pixel(10, 10), QColor(0xff, 0x80, 0x7f).rgb()); + + player.setActiveVideoTrack(track1); + + QTest::qWait(500ms); + sink.waitForFrame(); + QCOMPARE(sink.m_frameList.back().toImage().pixel(10, 10), QColor(0x80, 0x80, 0xff).rgb()); +} + +void tst_QMediaPlayerBackend::disablingAllTracks_doesNotStopPlayback() +{ + QSKIP_GSTREAMER("position does not advance in GStreamer"); + + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + // CAVEAT: we cannot set active tracks before tracksChanged is emitted + QTRY_COMPARE(player.videoTracks().size(), 2); + + player.setActiveVideoTrack(-1); + player.setActiveAudioTrack(-1); + + player.play(); + QTRY_VERIFY(player.position() > 1000); + + QCOMPARE(m_fixture->surface.m_totalFrames, 0); +} + +void tst_QMediaPlayerBackend::disablingAllTracks_beforeTracksChanged_doesNotStopPlayback() +{ + QSKIP_GSTREAMER("position does not advance in GStreamer"); + QSKIP_FFMPEG("setActiveXXXTrack(-1) only works after tracksChanged"); + + QMediaPlayer &player = m_fixture->player; + + player.setSource(*m_multitrackVideo); + + player.setActiveVideoTrack(-1); + player.setActiveAudioTrack(-1); + + player.play(); + QTRY_VERIFY(player.position() > 1000); + + QCOMPARE(m_fixture->surface.m_totalFrames, 0); +} + QTEST_MAIN(tst_QMediaPlayerBackend) + #include "tst_qmediaplayerbackend.moc" 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 Binary files differnew file mode 100644 index 000000000..a48028550 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.avi diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkv b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkv Binary files differnew file mode 100644 index 000000000..2e362d7ca --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mkv diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4 b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4 Binary files differnew file mode 100644 index 000000000..bff40278d --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mp4 diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpeg b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpeg Binary files differnew file mode 100644 index 000000000..b94a47f0a --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.mpeg diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmv b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmv Binary files differnew file mode 100644 index 000000000..1a7577e3f --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/supported/container.wmv diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webp b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webp Binary files differnew file mode 100644 index 000000000..ed8b6f631 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/containers/unsupported/container.webp diff --git a/tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gif b/tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gif Binary files differnew file mode 100644 index 000000000..fd187906b --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/flipable.gif 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 Binary files differnew file mode 100644 index 000000000..ff9eacb65 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr0.mp4 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 Binary files differnew file mode 100644 index 000000000..ff9eacb65 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_bgr24.mp4 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 Binary files differnew file mode 100644 index 000000000..c69357d2b --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray.mp4 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 Binary files differnew file mode 100644 index 000000000..339912757 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_gray10le.mp4 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 Binary files differnew file mode 100644 index 000000000..20b51609a --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv12.mp4 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 Binary files differnew file mode 100644 index 000000000..165819b22 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv16.mp4 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 Binary files differnew file mode 100644 index 000000000..20b51609a --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_nv21.mp4 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 Binary files differnew file mode 100644 index 000000000..ff9eacb65 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_rgb24.mp4 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 Binary files differnew file mode 100644 index 000000000..53ef62b45 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p.mp4 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 Binary files differnew file mode 100644 index 000000000..bb45d344c --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10.mp4 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 Binary files differnew file mode 100644 index 000000000..bb45d344c --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv420p10le.mp4 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 Binary files differnew file mode 100644 index 000000000..165819b22 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p.mp4 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 Binary files differnew file mode 100644 index 000000000..563fce6da --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10.mp4 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 Binary files differnew file mode 100644 index 000000000..563fce6da --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv422p10le.mp4 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 Binary files differnew file mode 100644 index 000000000..953d014ec --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p.mp4 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 Binary files differnew file mode 100644 index 000000000..c27683ed7 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuv444p10.mp4 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 Binary files differnew file mode 100644 index 000000000..3906325e1 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj420p.mp4 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 Binary files differnew file mode 100644 index 000000000..6b2d43fd9 --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj422p.mp4 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 Binary files differnew file mode 100644 index 000000000..75008f2aa --- /dev/null +++ b/tests/auto/integration/qmediaplayerformatsupport/testdata/pixel_formats/supported/h264_yuvj444p.mp4 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 index 35837cbad..1b7a1f686 100644 --- a/tests/auto/integration/qml/CMakeLists.txt +++ b/tests/auto/integration/qml/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qml.pro. ##################################################################### @@ -9,7 +12,7 @@ qt_internal_add_test(tst_qml QMLTEST SOURCES tst_qml.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui ) diff --git a/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml b/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml index 7ebbe2e73..0b5867bce 100644 --- a/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml +++ b/tests/auto/integration/qml/soundeffect/tst_soundeffect.qml @@ -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 import QtQuick import QtMultimedia 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 index 145dd8875..909416140 100644 --- a/tests/auto/integration/qquickvideooutput/CMakeLists.txt +++ b/tests/auto/integration/qquickvideooutput/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qdeclarativevideooutput.pro. ##################################################################### @@ -9,7 +12,7 @@ qt_internal_add_test(tst_qquickvideooutput tst_qquickvideooutput.cpp INCLUDE_DIRECTORIES ../../../../src/imports/multimedia - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::MultimediaQuickPrivate diff --git a/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp b/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp index 519fc6944..85a30bbb7 100644 --- a/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp +++ b/tests/auto/integration/qquickvideooutput/tst_qquickvideooutput.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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 //TESTED_COMPONENT=plugins/declarative/multimedia @@ -124,19 +99,19 @@ void tst_QQuickVideoOutput::fillMode() // Default is preserveaspectfit QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::PreserveAspectFit); - QCOMPARE(propSpy.count(), 0); + QCOMPARE(propSpy.size(), 0); videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::PreserveAspectCrop))); QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::PreserveAspectCrop); - QCOMPARE(propSpy.count(), 1); + QCOMPARE(propSpy.size(), 1); videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch))); QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::Stretch); - QCOMPARE(propSpy.count(), 2); + QCOMPARE(propSpy.size(), 2); videoOutput->setProperty("fillMode", QVariant(int(QQuickVideoOutput::Stretch))); QCOMPARE(videoOutput->property("fillMode").value<QQuickVideoOutput::FillMode>(), QQuickVideoOutput::Stretch); - QCOMPARE(propSpy.count(), 2); + QCOMPARE(propSpy.size(), 2); delete videoOutput; } @@ -153,43 +128,43 @@ void tst_QQuickVideoOutput::orientation() // Default orientation is 0 QCOMPARE(videoOutput->property("orientation").toInt(), 0); - QCOMPARE(propSpy.count(), 0); + QCOMPARE(propSpy.size(), 0); videoOutput->setProperty("orientation", QVariant(90)); QCOMPARE(videoOutput->property("orientation").toInt(), 90); - QCOMPARE(propSpy.count(), 1); + QCOMPARE(propSpy.size(), 1); videoOutput->setProperty("orientation", QVariant(180)); QCOMPARE(videoOutput->property("orientation").toInt(), 180); - QCOMPARE(propSpy.count(), 2); + QCOMPARE(propSpy.size(), 2); videoOutput->setProperty("orientation", QVariant(270)); QCOMPARE(videoOutput->property("orientation").toInt(), 270); - QCOMPARE(propSpy.count(), 3); + QCOMPARE(propSpy.size(), 3); videoOutput->setProperty("orientation", QVariant(360)); QCOMPARE(videoOutput->property("orientation").toInt(), 360); - QCOMPARE(propSpy.count(), 4); + QCOMPARE(propSpy.size(), 4); // More than 360 should be fine videoOutput->setProperty("orientation", QVariant(540)); QCOMPARE(videoOutput->property("orientation").toInt(), 540); - QCOMPARE(propSpy.count(), 5); + QCOMPARE(propSpy.size(), 5); // Negative should be fine videoOutput->setProperty("orientation", QVariant(-180)); QCOMPARE(videoOutput->property("orientation").toInt(), -180); - QCOMPARE(propSpy.count(), 6); + QCOMPARE(propSpy.size(), 6); // Same value should not reemit videoOutput->setProperty("orientation", QVariant(-180)); QCOMPARE(videoOutput->property("orientation").toInt(), -180); - QCOMPARE(propSpy.count(), 6); + QCOMPARE(propSpy.size(), 6); // Non multiples of 90 should not work videoOutput->setProperty("orientation", QVariant(-1)); QCOMPARE(videoOutput->property("orientation").toInt(), -180); - QCOMPARE(propSpy.count(), 6); + QCOMPARE(propSpy.size(), 6); delete videoOutput; } @@ -291,7 +266,7 @@ void tst_QQuickVideoOutput::paintSurface() videoOutput->setSize(QSize(2, 2)); QVideoFrame frame(QVideoFrameFormat(QSize(4, 4), QVideoFrameFormat::Format_ARGB8888)); - frame.map(QVideoFrame::ReadWrite); + frame.map(QtVideo::MapMode::ReadWrite); QCOMPARE(frame.mappedBytes(0), 64); memcpy(frame.bits(0), rgb32ImageData, 64); frame.unmap(); @@ -317,11 +292,11 @@ void tst_QQuickVideoOutput::sourceRect() presentDummyFrame(holder.videoSink(), QSize(200,100)); QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); - QCOMPARE(propSpy.count(), 1); + QCOMPARE(propSpy.size(), 1); // Another frame shouldn't cause a source rect change presentDummyFrame(holder.videoSink(), QSize(200,100)); - QCOMPARE(propSpy.count(), 1); + QCOMPARE(propSpy.size(), 1); QCOMPARE(videoOutput->property("sourceRect").toRectF(), QRectF(0, 0, 200, 100)); // Changing orientation and stretch modes should not affect this diff --git a/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt b/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt index 8d9bb078d..e7b624c70 100644 --- a/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt +++ b/tests/auto/integration/qquickvideooutput_window/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qdeclarativevideooutput_window.pro. ##################################################################### @@ -9,7 +12,7 @@ qt_internal_add_test(tst_qquickvideooutput_window tst_qquickvideooutput_window.cpp INCLUDE_DIRECTORIES ../../../../src/imports/multimedia - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::MultimediaQuickPrivate diff --git a/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp b/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp index 5942010e6..68f1771f9 100644 --- a/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp +++ b/tests/auto/integration/qquickvideooutput_window/tst_qquickvideooutput_window.cpp @@ -1,31 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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$ -** -****************************************************************************/ +// 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 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/qsoundeffect/CMakeLists.txt b/tests/auto/integration/qsoundeffect/CMakeLists.txt index 36340f9c1..d403d9514 100644 --- a/tests/auto/integration/qsoundeffect/CMakeLists.txt +++ b/tests/auto/integration/qsoundeffect/CMakeLists.txt @@ -1,40 +1,20 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qsoundeffect.pro. ##################################################################### ## tst_qsoundeffect Test: ##################################################################### -# Collect test data -list(APPEND test_data "test.wav") - qt_internal_add_test(tst_qsoundeffect SOURCES tst_qsoundeffect.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate - TESTDATA ${test_data} -) - -# Resources: -set(resources_resource_files - "test.wav" - "test_corrupted.wav" - "test_tone.wav" -) - -qt_internal_add_resource(tst_qsoundeffect "resources" - PREFIX - "/" - FILES - ${resources_resource_files} -) - - -## Scopes: -##################################################################### - -qt_internal_extend_target(tst_qsoundeffect CONDITION UNIX AND NOT APPLE AND NOT QT_FEATURE_pulseaudio - DEFINES - QT_MULTIMEDIA_QMEDIAPLAYER + TESTDATA + "test.wav" + "test_corrupted.wav" + "test_tone.wav" ) diff --git a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp index 21cda97b2..0d3d9f8b3 100644 --- a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp +++ b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp @@ -1,32 +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$ -** -****************************************************************************/ - -//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> @@ -116,13 +89,13 @@ 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(); @@ -135,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(3); sound->setVolume(0.1f); QCOMPARE(sound->loopCount(), 3); - 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(); - QVERIFY(readSignal_Remaining.count() > 0); + QVERIFY(readSignal_Remaining.size() > 0); // 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() == 4); + QVERIFY(readSignal_Remaining.size() == 4); QTRY_VERIFY(!sound->isPlaying()); // QTBUG-36643 (setting the loop count while playing should work) @@ -162,29 +135,29 @@ void tst_QSoundEffect::testLooping() sound->setLoopCount(10); QCOMPARE(sound->loopCount(), 10); - 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(); - QVERIFY(readSignal_Remaining.count() > 0); + QVERIFY(readSignal_Remaining.size() > 0); // wait for the sound to be played several times QTRY_VERIFY(sound->loopsRemaining() <= 7); - QVERIFY(readSignal_Remaining.count() >= 3); + QVERIFY(readSignal_Remaining.size() >= 3); readSignal_Count.clear(); readSignal_Remaining.clear(); // change the loop count while playing sound->setLoopCount(3); QCOMPARE(sound->loopCount(), 3); - QCOMPARE(readSignal_Count.count(), 1); + QCOMPARE(readSignal_Count.size(), 1); QCOMPARE(sound->loopsRemaining(), 3); - QCOMPARE(readSignal_Remaining.count(), 1); + QCOMPARE(readSignal_Remaining.size(), 1); // wait for all the loops to be completed QTRY_COMPARE(sound->loopsRemaining(), 0); - QTRY_VERIFY(readSignal_Remaining.count() == 4); + QTRY_VERIFY(readSignal_Remaining.size() == 4); QTRY_VERIFY(!sound->isPlaying()); } @@ -194,13 +167,13 @@ 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(500); QVERIFY(sound->isPlaying()); @@ -210,34 +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); - QTRY_COMPARE(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); - QTRY_COMPARE(readSignal.count(),1); + QTRY_COMPARE(readSignal.size(),1); } void tst_QSoundEffect::testPlaying() @@ -401,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()); 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 Binary files differnew file mode 100644 index 000000000..30ddda8b0 --- /dev/null +++ b/tests/auto/integration/qvideoframebackend/testdata/colors.mp4 diff --git a/tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4 b/tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4 Binary files differnew file mode 100644 index 000000000..6b67a3433 --- /dev/null +++ b/tests/auto/integration/qvideoframebackend/testdata/one_red_frame.mp4 diff --git a/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp b/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp new file mode 100644 index 000000000..30f96fb50 --- /dev/null +++ b/tests/auto/integration/qvideoframebackend/tst_qvideoframebackend.cpp @@ -0,0 +1,264 @@ +// 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 "mediabackendutils.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; + } + + if (isGStreamerPlatform()) { + qWarning() << "createMediaPlayerFrame spuriously fails with gstreamer"; + 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..b1fa30bb2 --- /dev/null +++ b/tests/auto/integration/shared/mediabackendutils.h @@ -0,0 +1,69 @@ +// 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 isQNXPlatform() +{ + return QPlatformMediaIntegration::instance()->name() == "qnx"; +} + +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 8a9c3e86a..aa192f3e9 100644 --- a/tests/auto/integration/shared/mediafileselector.h +++ b/tests/auto/integration/shared/mediafileselector.h @@ -1,75 +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 <QUrl> -#include <QMediaPlayer> -#include <QAudioOutput> +#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 QUrl selectMediaFile(const QStringList& mediaCandidates) +class MediaFileSelector { - QMediaPlayer player; - QAudioOutput audioOutput; - QVideoSink videoOutput; - player.setAudioOutput(&audioOutput); - player.setVideoOutput(&videoOutput); +public: + int failedSelectionsCount() const { return m_failedSelectionsCount; } - QSignalSpy errorSpy(&player, SIGNAL(errorOccurred(QMediaPlayer::Error, const QString&))); + QString dumpErrors() const + { + QStringList failedMedias; + for (const auto &mediaToError : m_mediaToErrors) + if (!mediaToError.second.isEmpty()) + failedMedias.emplace_back(mediaToError.first); - for (const QString &media : mediaCandidates) { - player.setSource(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 QUrl(); -} + 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 diff --git a/tests/auto/runautotests.py b/tests/auto/runautotests.py index ba277e943..d163cf98c 100755 --- a/tests/auto/runautotests.py +++ b/tests/auto/runautotests.py @@ -1,31 +1,6 @@ #! /usr/bin/env python -############################################################################# -## -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of the build configuration tools 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 WITH Qt-GPL-exception-1.0 import sys; diff --git a/tests/auto/shared/qscopedenvironmentvariable.h b/tests/auto/shared/qscopedenvironmentvariable.h new file mode 100644 index 000000000..390dfd400 --- /dev/null +++ b/tests/auto/shared/qscopedenvironmentvariable.h @@ -0,0 +1,29 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QSCOPEDENVIRONMENTVARIABLE_H +#define QSCOPEDENVIRONMENTVARIABLE_H + +#include <QtCore/qbytearrayview.h> +#include <QtCore/qtenvironmentvariables.h> + +struct QScopedEnvironmentVariable +{ + QScopedEnvironmentVariable(const QScopedEnvironmentVariable &) = delete; + QScopedEnvironmentVariable(QScopedEnvironmentVariable &&) = delete; + QScopedEnvironmentVariable &operator=(const QScopedEnvironmentVariable &) = delete; + QScopedEnvironmentVariable &operator=(QScopedEnvironmentVariable &&) = delete; + + QScopedEnvironmentVariable(const char *envvar, QByteArrayView name) : envvar{ envvar } + { + Q_ASSERT(envvar); + qputenv(envvar, name); + }; + + ~QScopedEnvironmentVariable() { qunsetenv(envvar); } + +private: + const char *envvar; +}; + +#endif // QSCOPEDENVIRONMENTVARIABLE_H diff --git a/tests/auto/unit/CMakeLists.txt b/tests/auto/unit/CMakeLists.txt index d6fe98cc4..d43c29198 100644 --- a/tests/auto/unit/CMakeLists.txt +++ b/tests/auto/unit/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from unit.pro. add_subdirectory(mockbackend) diff --git a/tests/auto/unit/mockbackend/CMakeLists.txt b/tests/auto/unit/mockbackend/CMakeLists.txt index 56f2c11aa..959341366 100644 --- a/tests/auto/unit/mockbackend/CMakeLists.txt +++ b/tests/auto/unit/mockbackend/CMakeLists.txt @@ -1,32 +1,33 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from mockbackend.pro. ##################################################################### -## QtMultimediaMockBackend Generic Library: +## MockMultimediaPlugin Generic Library: ##################################################################### -# special case begin -add_library(QtMultimediaMockBackend INTERFACE) -target_include_directories(QtMultimediaMockBackend INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(QtMultimediaMockBackend INTERFACE - Qt::Core - Qt::Gui - Qt::MultimediaPrivate -) -target_sources(QtMultimediaMockBackend INTERFACE - qmockaudiodecoder.h - qmockaudiooutput.h - qmockcamera.h - qmockimagecapture.h qmockimagecapture.cpp - qmockmediaplayer.h - qmockmediaencoder.h - qmockmediacapturesession.h - qmockvideosink.h - qmockmediadevices.cpp - qmockmediadevices_p.h - qmockintegration.cpp - qmockintegration_p.h +qt_internal_add_plugin(MockMultimediaPlugin + STATIC + OUTPUT_NAME mockmultimediaplugin + PLUGIN_TYPE multimedia + OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/../plugins" + DEFAULT_IF FALSE + SOURCES + qmockaudiodecoder.cpp qmockaudiodecoder.h + qmockaudiooutput.h + qmockcamera.cpp qmockcamera.h + qmockimagecapture.cpp qmockimagecapture.h + qmockmediaplayer.h + qmockmediaencoder.h + qmockmediacapturesession.h + qmockvideosink.h + qmockmediadevices.cpp qmockmediadevices.h + qmockintegration.cpp qmockintegration.h + LIBRARIES + Qt::MultimediaPrivate + Qt::CorePrivate ) -# special case end #### Keys ignored in scope 1:.:.:mockbackend.pro:<TRUE>: # TEMPLATE = "lib" diff --git a/tests/auto/unit/mockbackend/mock.json b/tests/auto/unit/mockbackend/mock.json new file mode 100644 index 000000000..499a3de8c --- /dev/null +++ b/tests/auto/unit/mockbackend/mock.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "mock" ] +} diff --git a/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp b/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp new file mode 100644 index 000000000..3c6b940a9 --- /dev/null +++ b/tests/auto/unit/mockbackend/qmockaudiodecoder.cpp @@ -0,0 +1,139 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qmockaudiodecoder.h" + +QT_BEGIN_NAMESPACE + +QMockAudioDecoder::QMockAudioDecoder(QAudioDecoder *parent) + : QPlatformAudioDecoder(parent), mDevice(0), mPosition(-1), mSerial(0) +{ + mFormat.setChannelCount(1); + mFormat.setSampleFormat(QAudioFormat::UInt8); + mFormat.setSampleRate(1000); +} + +QUrl QMockAudioDecoder::source() const +{ + return mSource; +} + +void QMockAudioDecoder::setSource(const QUrl &fileName) +{ + mSource = fileName; + mDevice = 0; + stop(); +} + +QIODevice *QMockAudioDecoder::sourceDevice() const +{ + return mDevice; +} + +void QMockAudioDecoder::setSourceDevice(QIODevice *device) +{ + mDevice = device; + mSource.clear(); + stop(); +} + +QAudioFormat QMockAudioDecoder::audioFormat() const +{ + return mFormat; +} + +void QMockAudioDecoder::setAudioFormat(const QAudioFormat &format) +{ + if (mFormat != format) { + mFormat = format; + formatChanged(mFormat); + } +} + +// When decoding we decode to first buffer, then second buffer +// we then stop until the first is read again and so on, for +// 5 buffers +void QMockAudioDecoder::start() +{ + if (!isDecoding()) { + if (!mSource.isEmpty()) { + setIsDecoding(true); + durationChanged(duration()); + + QTimer::singleShot(50, this, &QMockAudioDecoder::pretendDecode); + } else { + error(QAudioDecoder::ResourceError, "No source set"); + } + } +} + +void QMockAudioDecoder::stop() +{ + if (isDecoding()) { + mSerial = 0; + mPosition = 0; + mBuffers.clear(); + setIsDecoding(false); + bufferAvailableChanged(false); + } +} + +QAudioBuffer QMockAudioDecoder::read() +{ + QAudioBuffer a; + if (mBuffers.size() > 0) { + a = mBuffers.takeFirst(); + mPosition = a.startTime() / 1000; + positionChanged(mPosition); + + if (mBuffers.isEmpty()) + bufferAvailableChanged(false); + + if (mBuffers.isEmpty() && mSerial >= MOCK_DECODER_MAX_BUFFERS) { + finished(); + } else + QTimer::singleShot(50, this, &QMockAudioDecoder::pretendDecode); + } + + return a; +} + +bool QMockAudioDecoder::bufferAvailable() const +{ + return mBuffers.size() > 0; +} + +qint64 QMockAudioDecoder::position() const +{ + return mPosition; +} + +qint64 QMockAudioDecoder::duration() const +{ + return (sizeof(mSerial) * MOCK_DECODER_MAX_BUFFERS * qint64(1000)) + / (mFormat.sampleRate() * mFormat.channelCount()); +} + +void QMockAudioDecoder::pretendDecode() +{ + // Check if we've reached end of stream + if (mSerial >= MOCK_DECODER_MAX_BUFFERS) + return; + + // We just keep the length of mBuffers to 3 or less. + if (mBuffers.size() < 3) { + QByteArray b(sizeof(mSerial), 0); + memcpy(b.data(), &mSerial, sizeof(mSerial)); + qint64 position = (sizeof(mSerial) * mSerial * qint64(1000000)) + / (mFormat.sampleRate() * mFormat.channelCount()); + mSerial++; + mBuffers.push_back(QAudioBuffer(b, mFormat, position)); + bufferReady(); + if (mBuffers.size() == 1) + bufferAvailableChanged(true); + } +} + +QT_END_NAMESPACE + +#include "moc_qmockaudiodecoder.cpp" diff --git a/tests/auto/unit/mockbackend/qmockaudiodecoder.h b/tests/auto/unit/mockbackend/qmockaudiodecoder.h index abc710ac8..89e21ef23 100644 --- a/tests/auto/unit/mockbackend/qmockaudiodecoder.h +++ b/tests/auto/unit/mockbackend/qmockaudiodecoder.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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 #ifndef MOCKAUDIODECODERCONTROL_H #define MOCKAUDIODECODERCONTROL_H @@ -47,136 +22,37 @@ class QMockAudioDecoder : public QPlatformAudioDecoder Q_OBJECT public: - QMockAudioDecoder(QAudioDecoder *parent = 0) - : QPlatformAudioDecoder(parent) - , mDevice(0) - , mPosition(-1) - , mSerial(0) - { - mFormat.setChannelCount(1); - mFormat.setSampleFormat(QAudioFormat::UInt8); - mFormat.setSampleRate(1000); - } - - QUrl source() const override - { - return mSource; - } - - void setSource(const QUrl &fileName) override - { - mSource = fileName; - mDevice = 0; - stop(); - } - - QIODevice* sourceDevice() const override - { - return mDevice; - } - - void setSourceDevice(QIODevice *device) override - { - mDevice = device; - mSource.clear(); - stop(); - } - - QAudioFormat audioFormat() const override - { - return mFormat; - } - - void setAudioFormat(const QAudioFormat &format) override - { - if (mFormat != format) { - mFormat = format; - emit formatChanged(mFormat); - } - } + QMockAudioDecoder(QAudioDecoder *parent = nullptr); + + QUrl source() const override; + + void setSource(const QUrl &fileName) override; + + QIODevice *sourceDevice() const override; + + void setSourceDevice(QIODevice *device) override; + + QAudioFormat audioFormat() const override; + + void setAudioFormat(const QAudioFormat &format) override; // When decoding we decode to first buffer, then second buffer // we then stop until the first is read again and so on, for // 5 buffers - void start() override - { - if (!isDecoding()) { - if (!mSource.isEmpty()) { - setIsDecoding(true); - emit durationChanged(duration()); - - QTimer::singleShot(50, this, SLOT(pretendDecode())); - } else { - emit error(QAudioDecoder::ResourceError, "No source set"); - } - } - } - - void stop() override - { - if (isDecoding()) { - mSerial = 0; - mPosition = 0; - mBuffers.clear(); - setIsDecoding(false); - emit bufferAvailableChanged(false); - } - } - - QAudioBuffer read() override - { - QAudioBuffer a; - if (mBuffers.length() > 0) { - a = mBuffers.takeFirst(); - mPosition = a.startTime() / 1000; - positionChanged(mPosition); - - if (mBuffers.isEmpty()) - emit bufferAvailableChanged(false); - - if (mBuffers.isEmpty() && mSerial >= MOCK_DECODER_MAX_BUFFERS) { - emit finished(); - } else - QTimer::singleShot(50, this, SLOT(pretendDecode())); - } - - return a; - } - - bool bufferAvailable() const override - { - return mBuffers.length() > 0; - } - - qint64 position() const override - { - return mPosition; - } - - qint64 duration() const override - { - return (sizeof(mSerial) * MOCK_DECODER_MAX_BUFFERS * qint64(1000)) / (mFormat.sampleRate() * mFormat.channelCount()); - } + void start() override; + + void stop() override; + + QAudioBuffer read() override; + + bool bufferAvailable() const override; + + qint64 position() const override; + + qint64 duration() const override; private slots: - void pretendDecode() - { - // Check if we've reached end of stream - if (mSerial >= MOCK_DECODER_MAX_BUFFERS) - return; - - // We just keep the length of mBuffers to 3 or less. - if (mBuffers.length() < 3) { - QByteArray b(sizeof(mSerial), 0); - memcpy(b.data(), &mSerial, sizeof(mSerial)); - qint64 position = (sizeof(mSerial) * mSerial * qint64(1000000)) / (mFormat.sampleRate() * mFormat.channelCount()); - mSerial++; - mBuffers.push_back(QAudioBuffer(b, mFormat, position)); - emit bufferReady(); - if (mBuffers.count() == 1) - emit bufferAvailableChanged(true); - } - } + void pretendDecode(); public: QUrl mSource; diff --git a/tests/auto/unit/mockbackend/qmockaudiooutput.h b/tests/auto/unit/mockbackend/qmockaudiooutput.h index 30cc46604..a35fc8fe4 100644 --- a/tests/auto/unit/mockbackend/qmockaudiooutput.h +++ b/tests/auto/unit/mockbackend/qmockaudiooutput.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QMOCKAUDIOOUTPUT_H #define QMOCKAUDIOOUTPUT_H diff --git a/tests/auto/unit/mockbackend/qmockcamera.cpp b/tests/auto/unit/mockbackend/qmockcamera.cpp new file mode 100644 index 000000000..23ecc36ae --- /dev/null +++ b/tests/auto/unit/mockbackend/qmockcamera.cpp @@ -0,0 +1,158 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "qmockcamera.h" + +QT_BEGIN_NAMESPACE + +QMockCamera::QMockCamera(QCamera *parent) + : QPlatformCamera(parent), m_propertyChangesSupported(false) +{ + if (!simpleCamera) { + minIsoChanged(100); + maxIsoChanged(800); + minExposureTimeChanged(.001f); + maxExposureTimeChanged(1.f); + exposureCompensationRangeChanged(-2, 2); + maximumZoomFactorChanged(4.); + setFlashMode(QCamera::FlashAuto); + } +} + +QMockCamera::~QMockCamera() { } + +bool QMockCamera::isActive() const +{ + return m_active; +} + +void QMockCamera::setActive(bool active) +{ + if (m_active == active) + return; + m_active = active; + emit activeChanged(active); +} + +void QMockCamera::setCamera(const QCameraDevice &camera) +{ + m_camera = camera; +} + +bool QMockCamera::setCameraFormat(const QCameraFormat &format) +{ + if (!format.isNull() && !m_camera.videoFormats().contains(format)) + return false; + return true; +} + +void QMockCamera::setFocusMode(QCamera::FocusMode mode) +{ + if (isFocusModeSupported(mode)) + focusModeChanged(mode); +} + +bool QMockCamera::isFocusModeSupported(QCamera::FocusMode mode) const +{ + return simpleCamera ? mode == QCamera::FocusModeAuto : mode != QCamera::FocusModeInfinity; +} + +void QMockCamera::setCustomFocusPoint(const QPointF &point) +{ + if (!simpleCamera) + customFocusPointChanged(point); +} + +void QMockCamera::setFocusDistance(float d) +{ + if (!simpleCamera) + focusDistanceChanged(d); +} + +void QMockCamera::zoomTo(float newZoomFactor, float /*rate*/) +{ + zoomFactorChanged(newZoomFactor); +} + +void QMockCamera::setFlashMode(QCamera::FlashMode mode) +{ + if (!simpleCamera) + flashModeChanged(mode); + flashReadyChanged(mode != QCamera::FlashOff); +} +bool QMockCamera::isFlashModeSupported(QCamera::FlashMode mode) const +{ + return simpleCamera ? mode == QCamera::FlashOff : true; +} + +bool QMockCamera::isFlashReady() const +{ + return flashMode() != QCamera::FlashOff; +} + +void QMockCamera::setExposureMode(QCamera::ExposureMode mode) +{ + if (!simpleCamera && isExposureModeSupported(mode)) + exposureModeChanged(mode); +} + +bool QMockCamera::isExposureModeSupported(QCamera::ExposureMode mode) const +{ + return simpleCamera ? mode == QCamera::ExposureAuto : mode <= QCamera::ExposureBeach; +} + +void QMockCamera::setExposureCompensation(float c) +{ + if (!simpleCamera) + exposureCompensationChanged(qBound(-2., c, 2.)); +} + +int QMockCamera::isoSensitivity() const +{ + if (simpleCamera) + return -1; + return manualIsoSensitivity() > 0 ? manualIsoSensitivity() : 100; +} + +void QMockCamera::setManualIsoSensitivity(int iso) +{ + if (!simpleCamera) + isoSensitivityChanged(qBound(100, iso, 800)); +} + +void QMockCamera::setManualExposureTime(float secs) +{ + if (!simpleCamera) + exposureTimeChanged(qBound(0.001, secs, 1.)); +} + +float QMockCamera::exposureTime() const +{ + if (simpleCamera) + return -1.; + return manualExposureTime() > 0 ? manualExposureTime() : .05; +} + +bool QMockCamera::isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const +{ + if (simpleCamera) + return mode == QCamera::WhiteBalanceAuto; + return mode == QCamera::WhiteBalanceAuto || mode == QCamera::WhiteBalanceManual + || mode == QCamera::WhiteBalanceSunlight; +} + +void QMockCamera::setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) +{ + if (isWhiteBalanceModeSupported(mode)) + whiteBalanceModeChanged(mode); +} + +void QMockCamera::setColorTemperature(int temperature) +{ + if (!simpleCamera) + colorTemperatureChanged(temperature); +} + +QT_END_NAMESPACE + +#include "moc_qmockcamera.cpp" diff --git a/tests/auto/unit/mockbackend/qmockcamera.h b/tests/auto/unit/mockbackend/qmockcamera.h index 69251e1a6..3d8159e84 100644 --- a/tests/auto/unit/mockbackend/qmockcamera.h +++ b/tests/auto/unit/mockbackend/qmockcamera.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QMOCKCAMERA_H #define QMOCKCAMERA_H @@ -48,135 +23,53 @@ public: ~Simple() { simpleCamera = false; } }; - QMockCamera(QCamera *parent) - : QPlatformCamera(parent), - m_propertyChangesSupported(false) - { - if (!simpleCamera) { - minIsoChanged(100); - maxIsoChanged(800); - minExposureTimeChanged(.001f); - maxExposureTimeChanged(1.f); - exposureCompensationRangeChanged(-2, 2); - maximumZoomFactorChanged(4.); - setFlashMode(QCamera::FlashAuto); - } - } - - ~QMockCamera() {} - - bool isActive() const override { return m_active; } - void setActive(bool active) override { - if (m_active == active) - return; - m_active = active; - emit activeChanged(active); - } - - /* helper method to emit the signal error */ - void setError(QCamera::Error err, QString errorString) - { - emit error(err, errorString); - } - - void setCamera(const QCameraDevice &camera) override - { - m_camera = camera; - } - - bool setCameraFormat(const QCameraFormat& format) override - { - if (!format.isNull() && !m_camera.videoFormats().contains(format)) - return false; - return true; - } - - void setFocusMode(QCamera::FocusMode mode) override - { - if (isFocusModeSupported(mode)) - focusModeChanged(mode); - } - bool isFocusModeSupported(QCamera::FocusMode mode) const override - { return simpleCamera ? mode == QCamera::FocusModeAuto : mode != QCamera::FocusModeInfinity; } - - void setCustomFocusPoint(const QPointF &point) override - { - if (!simpleCamera) - customFocusPointChanged(point); - } - - void setFocusDistance(float d) override - { - if (!simpleCamera) - focusDistanceChanged(d); - } - - void zoomTo(float newZoomFactor, float /*rate*/) override { zoomFactorChanged(newZoomFactor); } - - void setFlashMode(QCamera::FlashMode mode) override - { - if (!simpleCamera) - flashModeChanged(mode); - flashReadyChanged(mode != QCamera::FlashOff); - } - bool isFlashModeSupported(QCamera::FlashMode mode) const override { return simpleCamera ? mode == QCamera::FlashOff : true; } - bool isFlashReady() const override { return flashMode() != QCamera::FlashOff; } - - void setExposureMode(QCamera::ExposureMode mode) override - { - if (!simpleCamera && isExposureModeSupported(mode)) - exposureModeChanged(mode); - } - bool isExposureModeSupported(QCamera::ExposureMode mode) const override - { - return simpleCamera ? mode == QCamera::ExposureAuto : mode <= QCamera::ExposureBeach; - } - void setExposureCompensation(float c) override - { - if (!simpleCamera) - exposureCompensationChanged(qBound(-2., c, 2.)); - } - int isoSensitivity() const override - { - if (simpleCamera) - return -1; - return manualIsoSensitivity() > 0 ? manualIsoSensitivity() : 100; - } - void setManualIsoSensitivity(int iso) override - { - if (!simpleCamera) - isoSensitivityChanged(qBound(100, iso, 800)); - } - void setManualExposureTime(float secs) override - { - if (!simpleCamera) - exposureTimeChanged(qBound(0.001, secs, 1.)); - } - float exposureTime() const override - { - if (simpleCamera) - return -1.; - return manualExposureTime() > 0 ? manualExposureTime() : .05; - } - - bool isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const override - { - if (simpleCamera) - return mode == QCamera::WhiteBalanceAuto; - return mode == QCamera::WhiteBalanceAuto || - mode == QCamera::WhiteBalanceManual || - mode == QCamera::WhiteBalanceSunlight; - } - void setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) override - { - if (isWhiteBalanceModeSupported(mode)) - whiteBalanceModeChanged(mode); - } - void setColorTemperature(int temperature) override - { - if (!simpleCamera) - colorTemperatureChanged(temperature); - } + QMockCamera(QCamera *parent); + + ~QMockCamera() override; + + bool isActive() const override; + + void setActive(bool active) override; + + void setCamera(const QCameraDevice &camera) override; + + bool setCameraFormat(const QCameraFormat &format) override; + + void setFocusMode(QCamera::FocusMode mode) override; + + bool isFocusModeSupported(QCamera::FocusMode mode) const override; + + void setCustomFocusPoint(const QPointF &point) override; + + void setFocusDistance(float d) override; + + void zoomTo(float newZoomFactor, float /*rate*/) override; + + void setFlashMode(QCamera::FlashMode mode) override; + + bool isFlashModeSupported(QCamera::FlashMode mode) const override; + + bool isFlashReady() const override; + + void setExposureMode(QCamera::ExposureMode mode) override; + + bool isExposureModeSupported(QCamera::ExposureMode mode) const override; + + void setExposureCompensation(float c) override; + + int isoSensitivity() const override; + + void setManualIsoSensitivity(int iso) override; + + void setManualExposureTime(float secs) override; + + float exposureTime() const override; + + bool isWhiteBalanceModeSupported(QCamera::WhiteBalanceMode mode) const override; + + void setWhiteBalanceMode(QCamera::WhiteBalanceMode mode) override; + + void setColorTemperature(int temperature) override; bool m_active = false; QCameraDevice m_camera; diff --git a/tests/auto/unit/mockbackend/qmockimagecapture.cpp b/tests/auto/unit/mockbackend/qmockimagecapture.cpp index 9e2396113..96e53b2f4 100644 --- a/tests/auto/unit/mockbackend/qmockimagecapture.cpp +++ b/tests/auto/unit/mockbackend/qmockimagecapture.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qmockimagecapture.h> #include <qmockcamera.h> @@ -32,6 +7,8 @@ #include <qimagecapture.h> #include <qcamera.h> +QT_BEGIN_NAMESPACE + QMockImageCapture::QMockImageCapture(QImageCapture *parent) : QPlatformImageCapture(parent) { @@ -48,7 +25,7 @@ int QMockImageCapture::capture(const QString &fileName) m_fileName = fileName; m_captureRequest++; emit readyForCaptureChanged(m_ready = false); - QTimer::singleShot(5, this, SLOT(captured())); + QTimer::singleShot(5, this, &QMockImageCapture::captured); return m_captureRequest; } else { emit error(-1, QImageCapture::NotReadyError, @@ -63,7 +40,7 @@ void QMockImageCapture::captured() emit imageCaptured(m_captureRequest, QImage()); QMediaMetaData metaData; - metaData.insert(QMediaMetaData::Author, QString::fromUtf8("Author")); + metaData.insert(QMediaMetaData::Author, QStringLiteral("Author")); metaData.insert(QMediaMetaData::Date, QDateTime(QDate(2021, 1, 1), QTime())); emit imageMetadataAvailable(m_captureRequest, metaData); @@ -76,3 +53,7 @@ void QMockImageCapture::captured() emit imageSaved(m_captureRequest, m_fileName); } + +QT_END_NAMESPACE + +#include "moc_qmockimagecapture.cpp" diff --git a/tests/auto/unit/mockbackend/qmockimagecapture.h b/tests/auto/unit/mockbackend/qmockimagecapture.h index 8d7cea1a4..376f53cbc 100644 --- a/tests/auto/unit/mockbackend/qmockimagecapture.h +++ b/tests/auto/unit/mockbackend/qmockimagecapture.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QMOCKCAMERAIMAGECAPTURE_H #define QMOCKCAMERAIMAGECAPTURE_H diff --git a/tests/auto/unit/mockbackend/qmockintegration.cpp b/tests/auto/unit/mockbackend/qmockintegration.cpp index da4ac787f..b554b31e0 100644 --- a/tests/auto/unit/mockbackend/qmockintegration.cpp +++ b/tests/auto/unit/mockbackend/qmockintegration.cpp @@ -1,43 +1,8 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qmockintegration_p.h" +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtMultimedia/private/qplatformmediaplugin_p.h> +#include "qmockintegration.h" #include "qmockmediaplayer.h" #include "qmockaudiodecoder.h" #include "qmockcamera.h" @@ -45,20 +10,106 @@ #include "qmockvideosink.h" #include "qmockimagecapture.h" #include "qmockaudiooutput.h" +#include "qmocksurfacecapture.h" +#include <private/qcameradevice_p.h> +#include <private/qplatformvideodevices_p.h> + +#include "qmockmediadevices.h" QT_BEGIN_NAMESPACE -QMockIntegration::QMockIntegration() +class MockMultimediaPlugin : public QPlatformMediaPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QPlatformMediaPlugin_iid FILE "mock.json") + +public: + MockMultimediaPlugin() : QPlatformMediaPlugin() { } + + QPlatformMediaIntegration *create(const QString &name) override + { + if (name == QLatin1String("mock")) + return new QMockIntegration; + return nullptr; + } +}; + +class QMockVideoDevices : public QPlatformVideoDevices { - setIntegration(this); +public: + QMockVideoDevices(QPlatformMediaIntegration *pmi) + : QPlatformVideoDevices(pmi) + { + QCameraDevicePrivate *info = new QCameraDevicePrivate; + info->description = QStringLiteral("defaultCamera"); + info->id = "default"; + info->isDefault = true; + auto *f = new QCameraFormatPrivate{ + QSharedData(), + QVideoFrameFormat::Format_ARGB8888, + QSize(640, 480), + 0, + 30 + }; + info->videoFormats << f->create(); + m_cameraDevices.append(info->create()); + info = new QCameraDevicePrivate; + info->description = QStringLiteral("frontCamera"); + info->id = "front"; + info->isDefault = false; + info->position = QCameraDevice::FrontFace; + f = new QCameraFormatPrivate{ + QSharedData(), + QVideoFrameFormat::Format_XRGB8888, + QSize(1280, 720), + 0, + 30 + }; + info->videoFormats << f->create(); + m_cameraDevices.append(info->create()); + info = new QCameraDevicePrivate; + info->description = QStringLiteral("backCamera"); + info->id = "back"; + info->isDefault = false; + info->position = QCameraDevice::BackFace; + m_cameraDevices.append(info->create()); + } + + void addNewCamera() + { + auto info = new QCameraDevicePrivate; + info->description = QLatin1String("newCamera") + QString::number(m_cameraDevices.size()); + info->id = + QString(QLatin1String("camera") + QString::number(m_cameraDevices.size())).toUtf8(); + info->isDefault = false; + m_cameraDevices.append(info->create()); + + emit videoInputsChanged(); + } + + QList<QCameraDevice> videoDevices() const override + { + return m_cameraDevices; + } + +private: + QList<QCameraDevice> m_cameraDevices; +}; + +QMockIntegration::QMockIntegration() : QPlatformMediaIntegration(QLatin1String("mock")) { } +QMockIntegration::~QMockIntegration() = default; + +QPlatformVideoDevices *QMockIntegration::createVideoDevices() +{ + return new QMockVideoDevices(this); } -QMockIntegration::~QMockIntegration() +std::unique_ptr<QPlatformMediaDevices> QMockIntegration::createMediaDevices() { - setIntegration(nullptr); + return std::make_unique<QMockMediaDevices>(); } -QPlatformAudioDecoder *QMockIntegration::createAudioDecoder(QAudioDecoder *decoder) +QMaybe<QPlatformAudioDecoder *> QMockIntegration::createAudioDecoder(QAudioDecoder *decoder) { if (m_flags & NoAudioDecoderInterface) m_lastAudioDecoderControl = nullptr; @@ -67,7 +118,7 @@ QPlatformAudioDecoder *QMockIntegration::createAudioDecoder(QAudioDecoder *decod return m_lastAudioDecoderControl; } -QPlatformMediaPlayer *QMockIntegration::createPlayer(QMediaPlayer *parent) +QMaybe<QPlatformMediaPlayer *> QMockIntegration::createPlayer(QMediaPlayer *parent) { if (m_flags & NoPlayerInterface) m_lastPlayer = nullptr; @@ -76,7 +127,7 @@ QPlatformMediaPlayer *QMockIntegration::createPlayer(QMediaPlayer *parent) return m_lastPlayer; } -QPlatformCamera *QMockIntegration::createCamera(QCamera *parent) +QMaybe<QPlatformCamera *> QMockIntegration::createCamera(QCamera *parent) { if (m_flags & NoCaptureInterface) m_lastCamera = nullptr; @@ -85,17 +136,37 @@ QPlatformCamera *QMockIntegration::createCamera(QCamera *parent) return m_lastCamera; } -QPlatformImageCapture *QMockIntegration::createImageCapture(QImageCapture *capture) +QMaybe<QPlatformImageCapture *> QMockIntegration::createImageCapture(QImageCapture *capture) { return new QMockImageCapture(capture); } -QPlatformMediaRecorder *QMockIntegration::createRecorder(QMediaRecorder *recorder) +QMaybe<QPlatformMediaRecorder *> QMockIntegration::createRecorder(QMediaRecorder *recorder) { return new QMockMediaEncoder(recorder); } -QPlatformMediaCaptureSession *QMockIntegration::createCaptureSession() +QPlatformSurfaceCapture *QMockIntegration::createScreenCapture(QScreenCapture * /*capture*/) +{ + if (m_flags & NoCaptureInterface) + m_lastScreenCapture = nullptr; + else + m_lastScreenCapture = new QMockSurfaceCapture(QPlatformSurfaceCapture::ScreenSource{}); + + return m_lastScreenCapture; +} + +QPlatformSurfaceCapture *QMockIntegration::createWindowCapture(QWindowCapture *) +{ + if (m_flags & NoCaptureInterface) + m_lastWindowCapture = nullptr; + else + m_lastWindowCapture = new QMockSurfaceCapture(QPlatformSurfaceCapture::WindowSource{}); + + return m_lastWindowCapture; +} + +QMaybe<QPlatformMediaCaptureSession *> QMockIntegration::createCaptureSession() { if (m_flags & NoCaptureInterface) m_lastCaptureService = nullptr; @@ -104,17 +175,24 @@ QPlatformMediaCaptureSession *QMockIntegration::createCaptureSession() return m_lastCaptureService; } -QPlatformVideoSink *QMockIntegration::createVideoSink(QVideoSink *sink) +QMaybe<QPlatformVideoSink *> QMockIntegration::createVideoSink(QVideoSink *sink) { m_lastVideoSink = new QMockVideoSink(sink); return m_lastVideoSink; } -QPlatformAudioOutput *QMockIntegration::createAudioOutput(QAudioOutput *q) +QMaybe<QPlatformAudioOutput *> QMockIntegration::createAudioOutput(QAudioOutput *q) { return new QMockAudioOutput(q); } +void QMockIntegration::addNewCamera() +{ + static_cast<QMockVideoDevices *>(videoDevices())->addNewCamera(); +} + bool QMockCamera::simpleCamera = false; QT_END_NAMESPACE + +#include "qmockintegration.moc" diff --git a/tests/auto/unit/mockbackend/qmockintegration.h b/tests/auto/unit/mockbackend/qmockintegration.h new file mode 100644 index 000000000..20b61721c --- /dev/null +++ b/tests/auto/unit/mockbackend/qmockintegration.h @@ -0,0 +1,103 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QMOCKINTEGRATION_H +#define QMOCKINTEGRATION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qplatformmediaintegration_p.h> + +QT_BEGIN_NAMESPACE + +class QMockMediaPlayer; +class QMockAudioDecoder; +class QMockCamera; +class QMockMediaCaptureSession; +class QMockVideoSink; +class QMockSurfaceCapture; + +class QMockIntegration : public QPlatformMediaIntegration +{ +public: + QMockIntegration(); + ~QMockIntegration(); + + static QMockIntegration *instance() + { + return static_cast<QMockIntegration *>(QPlatformMediaIntegration::instance()); + } + + QMaybe<QPlatformAudioDecoder *> createAudioDecoder(QAudioDecoder *decoder) override; + QMaybe<QPlatformMediaPlayer *> createPlayer(QMediaPlayer *) override; + QMaybe<QPlatformCamera *> createCamera(QCamera *) override; + QMaybe<QPlatformMediaRecorder *> createRecorder(QMediaRecorder *) override; + QMaybe<QPlatformImageCapture *> createImageCapture(QImageCapture *) override; + QMaybe<QPlatformMediaCaptureSession *> createCaptureSession() override; + QMaybe<QPlatformVideoSink *> createVideoSink(QVideoSink *) override; + + QMaybe<QPlatformAudioOutput *> createAudioOutput(QAudioOutput *) override; + + QPlatformSurfaceCapture *createScreenCapture(QScreenCapture *) override; + QPlatformSurfaceCapture *createWindowCapture(QWindowCapture *) override; + + void addNewCamera(); + + enum Flag { NoPlayerInterface = 0x1, NoAudioDecoderInterface = 0x2, NoCaptureInterface = 0x4 }; + Q_DECLARE_FLAGS(Flags, Flag); + + void setFlags(Flags f) { m_flags = f; } + Flags flags() const { return m_flags; } + + QMockMediaPlayer *lastPlayer() const { return m_lastPlayer; } + QMockAudioDecoder *lastAudioDecoder() const { return m_lastAudioDecoderControl; } + QMockCamera *lastCamera() const { return m_lastCamera; } + // QMockMediaEncoder *lastEncoder const { return m_lastEncoder; } + QMockMediaCaptureSession *lastCaptureService() const { return m_lastCaptureService; } + QMockVideoSink *lastVideoSink() const { return m_lastVideoSink; } + QMockSurfaceCapture *lastScreenCapture() { return m_lastScreenCapture; } + QMockSurfaceCapture *lastWindowCapture() { return m_lastWindowCapture; } + +protected: + QPlatformVideoDevices *createVideoDevices() override; + std::unique_ptr<QPlatformMediaDevices> createMediaDevices() override; + +private: + + Flags m_flags = {}; + QMockMediaPlayer *m_lastPlayer = nullptr; + QMockAudioDecoder *m_lastAudioDecoderControl = nullptr; + QMockCamera *m_lastCamera = nullptr; + // QMockMediaEncoder *m_lastEncoder = nullptr; + QMockMediaCaptureSession *m_lastCaptureService = nullptr; + QMockVideoSink *m_lastVideoSink = nullptr; + QMockSurfaceCapture *m_lastScreenCapture = nullptr; + QMockSurfaceCapture *m_lastWindowCapture = nullptr; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QMockIntegration::Flags); + +#define Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN \ + Q_IMPORT_PLUGIN(MockMultimediaPlugin) \ + struct EnableMockPlugin \ + { \ + EnableMockPlugin() \ + { \ + qputenv("QT_MEDIA_BACKEND", "mock"); \ + } \ + }; \ + static EnableMockPlugin s_mockMultimediaPluginEnabler; + + +QT_END_NAMESPACE + +#endif diff --git a/tests/auto/unit/mockbackend/qmockintegration_p.h b/tests/auto/unit/mockbackend/qmockintegration_p.h deleted file mode 100644 index f5a6d167e..000000000 --- a/tests/auto/unit/mockbackend/qmockintegration_p.h +++ /dev/null @@ -1,113 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QMOCKINTEGRATION_H -#define QMOCKINTEGRATION_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <private/qplatformmediaintegration_p.h> - -QT_BEGIN_NAMESPACE - -class QMockMediaPlayer; -class QMockAudioDecoder; -class QMockCamera; -class QMockMediaCaptureSession; -class QMockVideoSink; - -class QMockIntegration : public QPlatformMediaIntegration -{ -public: - QMockIntegration(); - ~QMockIntegration(); - - QPlatformMediaFormatInfo *formatInfo() override { return nullptr; } - - QPlatformAudioDecoder *createAudioDecoder(QAudioDecoder *decoder) override; - QPlatformMediaPlayer *createPlayer(QMediaPlayer *) override; - QPlatformCamera *createCamera(QCamera *) override; - QPlatformMediaRecorder *createRecorder(QMediaRecorder *) override; - QPlatformImageCapture *createImageCapture(QImageCapture *) override; - QPlatformMediaCaptureSession *createCaptureSession() override; - QPlatformVideoSink *createVideoSink(QVideoSink *) override; - - QPlatformAudioOutput *createAudioOutput(QAudioOutput *) override; - - enum Flag { - NoPlayerInterface = 0x1, - NoAudioDecoderInterface = 0x2, - NoCaptureInterface = 0x4 - }; - Q_DECLARE_FLAGS(Flags, Flag); - - void setFlags(Flags f) { m_flags = f; } - Flags flags() const { return m_flags; } - - QMockMediaPlayer *lastPlayer() const { return m_lastPlayer; } - QMockAudioDecoder *lastAudioDecoder() const { return m_lastAudioDecoderControl; } - QMockCamera *lastCamera() const { return m_lastCamera; } - // QMockMediaEncoder *lastEncoder const { return m_lastEncoder; } - QMockMediaCaptureSession *lastCaptureService() const { return m_lastCaptureService; } - QMockVideoSink *lastVideoSink() const { return m_lastVideoSink; } - -private: - Flags m_flags = {}; - QMockMediaPlayer *m_lastPlayer = nullptr; - QMockAudioDecoder *m_lastAudioDecoderControl = nullptr; - QMockCamera *m_lastCamera = nullptr; - // QMockMediaEncoder *m_lastEncoder = nullptr; - QMockMediaCaptureSession *m_lastCaptureService = nullptr; - QMockVideoSink *m_lastVideoSink; -}; - -Q_DECLARE_OPERATORS_FOR_FLAGS(QMockIntegration::Flags); - -QT_END_NAMESPACE - -#endif diff --git a/tests/auto/unit/mockbackend/qmockmediacapturesession.h b/tests/auto/unit/mockbackend/qmockmediacapturesession.h index c9e31fb56..0a2d3fb60 100644 --- a/tests/auto/unit/mockbackend/qmockmediacapturesession.h +++ b/tests/auto/unit/mockbackend/qmockmediacapturesession.h @@ -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 #ifndef QMOCKMEDIACAPTURESESSION_H #define QMOCKMEDIACAPTURESESSION_H @@ -33,13 +8,13 @@ #include "qmockimagecapture.h" #include "qmockcamera.h" #include "qmockimagecapture.h" +#include "qmocksurfacecapture.h" #include <private/qplatformmediacapture_p.h> QT_BEGIN_NAMESPACE class QMockMediaCaptureSession : public QPlatformMediaCaptureSession { - Q_OBJECT public: QMockMediaCaptureSession() : hasControls(true) @@ -87,10 +62,22 @@ public: m_audioInput = input; } + QPlatformSurfaceCapture *screenCapture() override { return m_screenCapture; } + void setScreenCapture(QPlatformSurfaceCapture *capture) override { m_screenCapture = capture; } + + QPlatformSurfaceCapture *windowCapture() override { return m_windowCapture; } + void setWindowCapture(QPlatformSurfaceCapture *capture) override { m_windowCapture = capture; } + + QPlatformVideoFrameInput *videoFrameInput() override { return m_videoFrameInput; } + void setVideoFrameInput(QPlatformVideoFrameInput *input) override { m_videoFrameInput = input; } + QMockCamera *mockCameraControl = nullptr; QPlatformImageCapture *mockImageCapture = nullptr; QMockMediaEncoder *mockControl = nullptr; QPlatformAudioInput *m_audioInput = nullptr; + QPlatformSurfaceCapture *m_screenCapture = nullptr; + QPlatformSurfaceCapture *m_windowCapture = nullptr; + QPlatformVideoFrameInput *m_videoFrameInput = nullptr; bool hasControls; }; diff --git a/tests/auto/unit/mockbackend/qmockmediadevices.cpp b/tests/auto/unit/mockbackend/qmockmediadevices.cpp index 216991925..7f2478741 100644 --- a/tests/auto/unit/mockbackend/qmockmediadevices.cpp +++ b/tests/auto/unit/mockbackend/qmockmediadevices.cpp @@ -1,43 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only -#include "qmockmediadevices_p.h" +#include "qmockmediadevices.h" #include "private/qcameradevice_p.h" QT_BEGIN_NAMESPACE @@ -45,42 +9,6 @@ QT_BEGIN_NAMESPACE QMockMediaDevices::QMockMediaDevices() : QPlatformMediaDevices() { - setDevices(this); - - QCameraDevicePrivate *info = new QCameraDevicePrivate; - info->description = QString::fromUtf8("defaultCamera"); - info->id = "default"; - info->isDefault = true; - auto *f = new QCameraFormatPrivate{ - QSharedData(), - QVideoFrameFormat::Format_ARGB8888, - QSize(640, 480), - 0, - 30 - }; - info->videoFormats << f->create(); - m_cameraDevices.append(info->create()); - info = new QCameraDevicePrivate; - info->description = QString::fromUtf8("frontCamera"); - info->id = "front"; - info->isDefault = false; - info->position = QCameraDevice::FrontFace; - f = new QCameraFormatPrivate{ - QSharedData(), - QVideoFrameFormat::Format_XRGB8888, - QSize(1280, 720), - 0, - 30 - }; - info->videoFormats << f->create(); - m_cameraDevices.append(info->create()); - info = new QCameraDevicePrivate; - info->description = QString::fromUtf8("backCamera"); - info->id = "back"; - info->isDefault = false; - info->position = QCameraDevice::BackFace; - m_cameraDevices.append(info->create()); - } QMockMediaDevices::~QMockMediaDevices() = default; @@ -95,20 +23,19 @@ QList<QAudioDevice> QMockMediaDevices::audioOutputs() const return m_outputDevices; } -QList<QCameraDevice> QMockMediaDevices::videoInputs() const -{ - return m_cameraDevices; -} - -QPlatformAudioSource *QMockMediaDevices::createAudioSource(const QAudioDevice &info) +QPlatformAudioSource *QMockMediaDevices::createAudioSource(const QAudioDevice &info, + QObject *parent) { Q_UNUSED(info); + Q_UNUSED(parent); return nullptr;// ### } -QPlatformAudioSink *QMockMediaDevices::createAudioSink(const QAudioDevice &info) +QPlatformAudioSink *QMockMediaDevices::createAudioSink(const QAudioDevice &info, + QObject *parent) { Q_UNUSED(info); + Q_UNUSED(parent); return nullptr; //### } diff --git a/tests/auto/unit/mockbackend/qmockmediadevices.h b/tests/auto/unit/mockbackend/qmockmediadevices.h new file mode 100644 index 000000000..e9e823194 --- /dev/null +++ b/tests/auto/unit/mockbackend/qmockmediadevices.h @@ -0,0 +1,45 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QMOCKMEDIADEVICES_H +#define QMOCKMEDIADEVICES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qplatformmediadevices_p.h> +#include <qelapsedtimer.h> +#include <qaudiodevice.h> +#include <qcameradevice.h> + +QT_BEGIN_NAMESPACE + +class QCameraDevice; + +class QMockMediaDevices : public QPlatformMediaDevices +{ +public: + QMockMediaDevices(); + ~QMockMediaDevices(); + + QList<QAudioDevice> audioInputs() const override; + QList<QAudioDevice> audioOutputs() const override; + QPlatformAudioSource *createAudioSource(const QAudioDevice &info, QObject *parent) override; + QPlatformAudioSink *createAudioSink(const QAudioDevice &info, QObject *parent) override; + +private: + QList<QAudioDevice> m_inputDevices; + QList<QAudioDevice> m_outputDevices; +}; + +QT_END_NAMESPACE + +#endif diff --git a/tests/auto/unit/mockbackend/qmockmediadevices_p.h b/tests/auto/unit/mockbackend/qmockmediadevices_p.h deleted file mode 100644 index cfa0398f3..000000000 --- a/tests/auto/unit/mockbackend/qmockmediadevices_p.h +++ /dev/null @@ -1,83 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#ifndef QMOCKMEDIADEVICES_H -#define QMOCKMEDIADEVICES_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <private/qplatformmediadevices_p.h> -#include <qelapsedtimer.h> -#include <qaudiodevice.h> -#include <qcameradevice.h> - -QT_BEGIN_NAMESPACE - -class QCameraDevice; - -class QMockMediaDevices : public QPlatformMediaDevices -{ -public: - QMockMediaDevices(); - ~QMockMediaDevices(); - - QList<QAudioDevice> audioInputs() const override; - QList<QAudioDevice> audioOutputs() const override; - QList<QCameraDevice> videoInputs() const override; - QPlatformAudioSource *createAudioSource(const QAudioDevice &info) override; - QPlatformAudioSink *createAudioSink(const QAudioDevice &info) override; - -private: - QList<QAudioDevice> m_inputDevices; - QList<QAudioDevice> m_outputDevices; - QList<QCameraDevice> m_cameraDevices; -}; - -QT_END_NAMESPACE - -#endif diff --git a/tests/auto/unit/mockbackend/qmockmediaencoder.h b/tests/auto/unit/mockbackend/qmockmediaencoder.h index 88ef5b3f9..cf855488b 100644 --- a/tests/auto/unit/mockbackend/qmockmediaencoder.h +++ b/tests/auto/unit/mockbackend/qmockmediaencoder.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef MOCKRECORDERCONTROL_H #define MOCKRECORDERCONTROL_H @@ -62,11 +37,11 @@ public: virtual void setMetaData(const QMediaMetaData &m) override { m_metaData = m; - emit metaDataChanged(); + metaDataChanged(); } virtual QMediaMetaData metaData() const override { return m_metaData; } - using QPlatformMediaRecorder::error; + using QPlatformMediaRecorder::updateError; public: void record(QMediaEncoderSettings &settings) override @@ -74,30 +49,30 @@ public: m_state = QMediaRecorder::RecordingState; m_settings = settings; m_position=1; - emit stateChanged(m_state); - emit durationChanged(m_position); + stateChanged(m_state); + durationChanged(m_position); QUrl actualLocation = outputLocation().isEmpty() ? QUrl::fromLocalFile("default_name.mp4") : outputLocation(); - emit actualLocationChanged(actualLocation); + actualLocationChanged(actualLocation); } void pause() override { m_state = QMediaRecorder::PausedState; - emit stateChanged(m_state); + stateChanged(m_state); } void resume() override { m_state = QMediaRecorder::RecordingState; - emit stateChanged(m_state); + stateChanged(m_state); } void stop() override { m_position=0; m_state = QMediaRecorder::StoppedState; - emit stateChanged(m_state); + stateChanged(m_state); } void reset() @@ -105,8 +80,8 @@ public: m_state = QMediaRecorder::StoppedState; m_settings = QMediaEncoderSettings(); m_position = 0; - emit stateChanged(m_state); - emit durationChanged(m_position); + stateChanged(m_state); + durationChanged(m_position); clearActualLocation(); } diff --git a/tests/auto/unit/mockbackend/qmockmediaplayer.h b/tests/auto/unit/mockbackend/qmockmediaplayer.h index aef736cf3..a3ba76beb 100644 --- a/tests/auto/unit/mockbackend/qmockmediaplayer.h +++ b/tests/auto/unit/mockbackend/qmockmediaplayer.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QMOCKMEDIAPLAYER_H #define QMOCKMEDIAPLAYER_H @@ -69,11 +44,15 @@ public: } qint64 duration() const override { return _duration; } - void setDuration(qint64 duration) { emit durationChanged(_duration = duration); } + void setDuration(qint64 duration) { durationChanged(_duration = duration); } qint64 position() const override { return _position; } - void setPosition(qint64 position) override { if (position != _position) emit positionChanged(_position = position); } + void setPosition(qint64 position) override + { + if (position != _position) + positionChanged(_position = position); + } float bufferProgress() const override { return _bufferProgress; } void setBufferStatus(float status) @@ -88,13 +67,17 @@ public: bool isVideoAvailable() const override { return _videoAvailable; } bool isSeekable() const override { return _isSeekable; } - void setSeekable(bool seekable) { emit seekableChanged(_isSeekable = seekable); } + void setSeekable(bool seekable) { seekableChanged(_isSeekable = seekable); } QMediaTimeRange availablePlaybackRanges() const override { return QMediaTimeRange(_seekRange.first, _seekRange.second); } void setSeekRange(qint64 minimum, qint64 maximum) { _seekRange = qMakePair(minimum, maximum); } qreal playbackRate() const override { return _playbackRate; } - void setPlaybackRate(qreal rate) override { if (rate != _playbackRate) emit playbackRateChanged(_playbackRate = rate); } + void setPlaybackRate(qreal rate) override + { + if (rate != _playbackRate) + playbackRateChanged(_playbackRate = rate); + } QUrl media() const override { return _media; } void setMedia(const QUrl &content, QIODevice *stream) override @@ -117,10 +100,7 @@ public: void setAudioOutput(QPlatformAudioOutput *output) override { m_audioOutput = output; } - void emitError(QMediaPlayer::Error err, const QString &errorString) - { - emit error(err, errorString); - } + void emitError(QMediaPlayer::Error err, const QString &errorString) { error(err, errorString); } void setState(QMediaPlayer::PlaybackState state) { @@ -144,8 +124,16 @@ public: void setIsValid(bool isValid) { _isValid = isValid; } void setMedia(QUrl media) { _media = media; } void setVideoAvailable(bool videoAvailable) { _videoAvailable = videoAvailable; } - void setError(QMediaPlayer::Error err) { _error = err; emit error(_error, _errorString); } - void setErrorString(QString errorString) { _errorString = errorString; emit error(_error, _errorString); } + void setError(QMediaPlayer::Error err) + { + _error = err; + error(_error, _errorString); + } + void setErrorString(QString errorString) + { + _errorString = errorString; + error(_error, _errorString); + } void reset() { diff --git a/tests/auto/unit/mockbackend/qmocksurfacecapture.h b/tests/auto/unit/mockbackend/qmocksurfacecapture.h new file mode 100644 index 000000000..00ce80ebb --- /dev/null +++ b/tests/auto/unit/mockbackend/qmocksurfacecapture.h @@ -0,0 +1,88 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QMOCKSURFACECAPTURE_H +#define QMOCKSURFACECAPTURE_H + +#include "private/qplatformsurfacecapture_p.h" +#include "private/qvideoframe_p.h" + +#include "qmockvideobuffer.h" +#include "qthread.h" + +QT_BEGIN_NAMESPACE + +class QMockSurfaceCapture : public QPlatformSurfaceCapture +{ + class Grabber : public QThread + { + public: + Grabber(QMockSurfaceCapture &capture) : QThread(&capture), m_capture(capture) { } + + void run() override + { + for (int i = 0; !isInterruptionRequested(); ++i) { + QImage image(m_capture.m_imageSize, QImage::Format_ARGB32); + + image.fill(i % 2 ? Qt::red : Qt::blue); + + QVideoFrame frame = QVideoFramePrivate::createFrame( + std::make_unique<QMockVideoBuffer>(image), + QVideoFrameFormat(m_capture.m_imageSize, + QVideoFrameFormat::pixelFormatFromImageFormat( + m_capture.m_imageFormat))); + + emit m_capture.newVideoFrame(frame); + } + } + + private: + QMockSurfaceCapture &m_capture; + }; + +public: + using QPlatformSurfaceCapture::QPlatformSurfaceCapture; + + ~QMockSurfaceCapture() { resetGrabber(); } + + bool setActiveInternal(bool active) override + { + if (active) { + m_grabber = std::make_unique<Grabber>(*this); + m_grabber->start(); + } else { + resetGrabber(); + } + + return true; + } + + bool isActive() const override { return bool(m_grabber); } + + QVideoFrameFormat frameFormat() const override + { + return m_grabber ? QVideoFrameFormat( + m_imageSize, QVideoFrameFormat::pixelFormatFromImageFormat(m_imageFormat)) + : QVideoFrameFormat{}; + } + +private: + void resetGrabber() + { + if (m_grabber) { + m_grabber->requestInterruption(); + m_grabber->quit(); + m_grabber->wait(); + m_grabber.reset(); + } + } + +private: + std::unique_ptr<Grabber> m_grabber; + const QImage::Format m_imageFormat = QImage::Format_ARGB32; + const QSize m_imageSize = QSize(2, 3); +}; + +QT_END_NAMESPACE + +#endif // QMOCKSURFACECAPTURE_H diff --git a/tests/auto/unit/mockbackend/qmockvideobuffer.h b/tests/auto/unit/mockbackend/qmockvideobuffer.h new file mode 100644 index 000000000..0ee32416c --- /dev/null +++ b/tests/auto/unit/mockbackend/qmockvideobuffer.h @@ -0,0 +1,38 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef QMOCKVIDEOBUFFER_H +#define QMOCKVIDEOBUFFER_H + +#include "qimage.h" +#include "private/qhwvideobuffer_p.h" + +class QMockVideoBuffer : public QHwVideoBuffer +{ +public: + QMockVideoBuffer(QImage image) : QHwVideoBuffer(QVideoFrame::NoHandle), m_image(image) { } + + MapData map(QtVideo::MapMode mode) override + { + MapData mapData; + if (m_mapMode == QtVideo::MapMode::NotMapped && !m_image.isNull() + && mode != QtVideo::MapMode::NotMapped) { + m_mapMode = mode; + + mapData.planeCount = 1; + mapData.bytesPerLine[0] = m_image.bytesPerLine(); + mapData.data[0] = m_image.bits(); + mapData.dataSize[0] = m_image.sizeInBytes(); + } + + return mapData; + } + + void unmap() override { m_mapMode = QtVideo::MapMode::NotMapped; } + +private: + QtVideo::MapMode m_mapMode = QtVideo::MapMode::NotMapped; + QImage m_image; +}; + +#endif // QMOCKVIDEOBUFFER_H diff --git a/tests/auto/unit/mockbackend/qmockvideosink.h b/tests/auto/unit/mockbackend/qmockvideosink.h index 0a8baeef0..d93178668 100644 --- a/tests/auto/unit/mockbackend/qmockvideosink.h +++ b/tests/auto/unit/mockbackend/qmockvideosink.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef QMOCKVIDEOSINK_H #define QMOCKVIDEOSINK_H @@ -57,8 +21,6 @@ QT_BEGIN_NAMESPACE class QMockVideoSink : public QPlatformVideoSink { - Q_OBJECT - public: explicit QMockVideoSink(QVideoSink *parent) : QPlatformVideoSink(parent) diff --git a/tests/auto/unit/multimedia/CMakeLists.txt b/tests/auto/unit/multimedia/CMakeLists.txt index d95cab75a..151577d14 100644 --- a/tests/auto/unit/multimedia/CMakeLists.txt +++ b/tests/auto/unit/multimedia/CMakeLists.txt @@ -1,19 +1,41 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from multimedia.pro. add_subdirectory(qabstractvideobuffer) add_subdirectory(qaudiorecorder) add_subdirectory(qaudioformat) add_subdirectory(qaudionamespace) +add_subdirectory(qaudiostatemachine) add_subdirectory(qcamera) add_subdirectory(qcameradevice) add_subdirectory(qimagecapture) add_subdirectory(qmediaformat) +add_subdirectory(qmediametadata) add_subdirectory(qmediaplayer) #add_subdirectory(qmediaplaylist) add_subdirectory(qmediarecorder) add_subdirectory(qmediatimerange) +add_subdirectory(qmultimediautils) add_subdirectory(qvideoframe) add_subdirectory(qvideoframeformat) +if(QT_FEATURE_ffmpeg) + add_subdirectory(qvideoframecolormanagement) +endif() add_subdirectory(qaudiobuffer) add_subdirectory(qaudiodecoder) add_subdirectory(qsamplecache) +add_subdirectory(qscreencapture) +add_subdirectory(qvideotexturehelper) +add_subdirectory(qmaybe) +add_subdirectory(qmediadevices) +add_subdirectory(qerrorinfo) +add_subdirectory(qvideobuffers) +add_subdirectory(qwavedecoder) + +if(QT_FEATURE_gstreamer) + add_subdirectory(gstreamer_backend) + add_subdirectory(qmediacapture_gstreamer) + add_subdirectory(qmediaplayer_gstreamer) +endif() diff --git a/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt b/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt new file mode 100644 index 000000000..6d52d09e1 --- /dev/null +++ b/tests/auto/unit/multimedia/gstreamer_backend/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_gstreamer_backend Test: +##################################################################### + +qt_internal_add_test(tst_gstreamer_backend + SOURCES + tst_gstreamer_backend.cpp + tst_gstreamer_backend.h + LIBRARIES + Qt::MultimediaPrivate + Qt::QGstreamerMediaPluginPrivate +) diff --git a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp new file mode 100644 index 000000000..929c46b3e --- /dev/null +++ b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.cpp @@ -0,0 +1,309 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_gstreamer_backend.h" + +#include <QtTest/QtTest> +#include <QtMultimedia/qmediaformat.h> + +#include <QtQGstreamerMediaPlugin/private/qgst_handle_types_p.h> +#include <QtQGstreamerMediaPlugin/private/qgst_p.h> +#include <QtQGstreamerMediaPlugin/private/qgst_debug_p.h> +#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h> +#include <QtQGstreamerMediaPlugin/private/qgstreamermetadata_p.h> + +QT_USE_NAMESPACE + +// NOLINTBEGIN(readability-convert-member-functions-to-static) + +using namespace Qt::Literals; + +namespace { + +template <typename... Pairs> +QMediaMetaData makeQMediaMetaData(Pairs &&...pairs) +{ + QMediaMetaData metadata; + + auto addKeyValuePair = [&](auto &&pair) { + metadata.insert(pair.first, pair.second); + return; + }; + + (addKeyValuePair(pairs), ...); + + return metadata; +} + +} // namespace + +QGstTagListHandle tst_GStreamer::parseTagList(const char *str) +{ + QGstTagListHandle tagList{ + gst_tag_list_new_from_string(str), + QGstTagListHandle::NeedsRef, + }; + return tagList; +} + +QGstTagListHandle tst_GStreamer::parseTagList(const QByteArray &ba) +{ + return parseTagList(ba.constData()); +} + +void tst_GStreamer::qGstCasts_withElement() +{ + QGstElement element = QGstElement::createFromFactory("identity", "myPipeline"); + QVERIFY(element); + + QVERIFY(!qIsGstObjectOfType<GstPipeline>(element.element())); + QVERIFY(!qIsGstObjectOfType<GstBin>(element.element())); +} + +void tst_GStreamer::qGstCasts_withBin() +{ + QGstBin bin = QGstBin::create("bin"); + QVERIFY(bin); + + QVERIFY(!qIsGstObjectOfType<GstPipeline>(bin.element())); + QVERIFY(qIsGstObjectOfType<GstBin>(bin.element())); +} + +void tst_GStreamer::qGstCasts_withPipeline() +{ + QGstPipeline pipeline = QGstPipeline::create("myPipeline"); + + QGstElement element{ + qGstSafeCast<GstElement>(pipeline.pipeline()), + QGstElement::NeedsRef, + }; + + QVERIFY(element); + QVERIFY(qIsGstObjectOfType<GstPipeline>(element.element())); + QVERIFY(qIsGstObjectOfType<GstBin>(element.element())); +} + +void tst_GStreamer::metadata_taglistToMetaData() +{ + QGstTagListHandle tagList = parseTagList(R"(taglist, title="My Video", comment="yada")"); + + QMediaMetaData parsed = taglistToMetaData(tagList); + + QCOMPARE(parsed.stringValue(QMediaMetaData::Title), u"My Video"_s); + QCOMPARE(parsed.stringValue(QMediaMetaData::Comment), u"yada"_s); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsOrientation() +{ + QFETCH(QByteArray, taglist); + QFETCH(QtVideo::Rotation, rotation); + + QGstTagListHandle tagList = parseTagList(taglist); + QMediaMetaData parsed = taglistToMetaData(tagList); + QCOMPARE(parsed[QMediaMetaData::Orientation].value<QtVideo::Rotation>(), rotation); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsOrientation_data() +{ + QTest::addColumn<QByteArray>("taglist"); + QTest::addColumn<QtVideo::Rotation>("rotation"); + + QTest::newRow("no rotation") << R"(taglist, title="My Video", comment="yada")"_ba + << QtVideo::Rotation::None; + QTest::newRow("90 degree") + << R"(taglist, title="My Video", comment="yada", image-orientation=(string)rotate-90)"_ba + << QtVideo::Rotation::Clockwise90; + QTest::newRow("180 degree") + << R"(taglist, title="My Video", comment="yada", image-orientation=(string)rotate-180)"_ba + << QtVideo::Rotation::Clockwise180; + QTest::newRow("270 degree") + << R"(taglist, title="My Video", comment="yada", image-orientation=(string)rotate-270)"_ba + << QtVideo::Rotation::Clockwise270; +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsDuration() +{ + QGstTagListHandle tagList = parseTagList( + R"__(taglist, video-codec=(string)"On2\ VP9", container-specific-track-id=(string)1, extended-comment=(string){ "ALPHA_MODE\=1", "HANDLER_NAME\=Apple\ Video\ Media\ Handler", "VENDOR_ID\=appl", "TIMECODE\=00:00:00:00", "DURATION\=00:00:00.400000000" }, encoder=(string)"Lavc59.37.100\ libvpx-vp9")__"); + + QMediaMetaData parsed = taglistToMetaData(tagList); + QCOMPARE(parsed[QMediaMetaData::Duration].value<int>(), 400); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsLanguage() +{ + QFETCH(QByteArray, tagListString); + QFETCH(QLocale::Language, language); + + QGstTagListHandle tagList = parseTagList(tagListString); + QVERIFY(tagList); + + QMediaMetaData parsed = taglistToMetaData(tagList); + QCOMPARE(parsed[QMediaMetaData::Language].value<QLocale::Language>(), language); +} + +void tst_GStreamer::metadata_taglistToMetaData_extractsLanguage_data() +{ + QTest::addColumn<QByteArray>("tagListString"); + QTest::addColumn<QLocale::Language>("language"); + + QTest::newRow("english, en") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)en, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::English; + QTest::newRow("spanish, es") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)es, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::Spanish; + QTest::newRow("english, eng") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)eng, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::English; + QTest::newRow("spanish, spa") + << R"__(taglist, container-format=(string)Matroska, audio-codec=(string)"MPEG-4\ AAC", language-code=(string)spa, container-specific-track-id=(string)5, encoder=(string)Lavf60.16.100, extended-comment=(string)"DURATION\=00:00:05.055000000")__"_ba + << QLocale::Language::Spanish; +} + +void tst_GStreamer::metadata_capsToMetaData() +{ + QFETCH(QByteArray, capsString); + QFETCH(QMediaMetaData, expectedMetadata); + + QGstCaps caps{ + gst_caps_from_string(capsString.constData()), + QGstCaps::HasRef, + }; + + QMediaMetaData md = capsToMetaData(caps); + + QCOMPARE(md, expectedMetadata); +} + +void tst_GStreamer::metadata_capsToMetaData_data() +{ + using Key = QMediaMetaData::Key; + using KVPair = std::pair<QMediaMetaData::Key, QVariant>; + + auto makeKVPair = [](Key key, auto value) { + return KVPair{ + key, + QVariant::fromValue(value), + }; + }; + + QTest::addColumn<QByteArray>("capsString"); + QTest::addColumn<QMediaMetaData>("expectedMetadata"); + + QTest::newRow("container") << R"(video/quicktime, variant=(string)iso)"_ba + << makeQMediaMetaData(makeKVPair(Key::FileFormat, + QMediaFormat::FileFormat::MPEG4)); + + QTest::newRow("video") + << R"(video/x-h264, stream-format=(string)avc, alignment=(string)au, level=(string)3.1, profile=(string)main, codec_data=(buffer)014d401fffe10017674d401fda014016ec0440000003004000000c83c60ca801000468ef3c80, width=(int)1280, height=(int)720, framerate=(fraction)25/1, pixel-aspect-ratio=(fraction)1/1)"_ba + << makeQMediaMetaData(makeKVPair(Key::VideoCodec, QMediaFormat::VideoCodec::H264), + makeKVPair(Key::VideoFrameRate, 25), + makeKVPair(Key::Resolution, QSize(1280, 720))); + + QTest::newRow("audio") + << R"(audio/mpeg, mpegversion=(int)4, framed=(boolean)true, stream-format=(string)raw, level=(string)4, base-profile=(string)lc, profile=(string)lc, codec_data=(buffer)11b0, rate=(int)48000, channels=(int)6)"_ba + << makeQMediaMetaData(makeKVPair(Key::AudioCodec, QMediaFormat::AudioCodec::AAC)); +} + +void tst_GStreamer::QGstBin_createFromPipelineDescription() +{ + QGstBin bin = QGstBin::createFromPipelineDescription("identity name=foo ! identity name=bar"); + + QVERIFY(bin); + QVERIFY(bin.findByName("foo")); + QCOMPARE_EQ(bin.findByName("foo").getParent(), bin); + QVERIFY(bin.findByName("bar")); + QVERIFY(!bin.findByName("baz")); + bin.dumpGraph("QGstBin_createFromPipelineDescription"); +} + +void tst_GStreamer::QGstElement_createFromPipelineDescription() +{ + using namespace std::string_view_literals; + QGstElement element = QGstElement::createFromPipelineDescription("identity name=foo"); + QCOMPARE_EQ(element.name(), "foo"sv); + QCOMPARE_EQ(element.typeName(), "GstIdentity"sv); +} + +void tst_GStreamer::QGstElement_createFromPipelineDescription_multipleElementsCreatesBin() +{ + using namespace std::string_view_literals; + QGstElement element = + QGstElement::createFromPipelineDescription("identity name=foo ! identity name=bar"); + + QVERIFY(element); + QCOMPARE_EQ(element.typeName(), "GstPipeline"sv); + + QGstBin bin{ + qGstSafeCast<GstBin>(element.element()), + QGstBin::NeedsRef, + }; + + QVERIFY(bin); + QVERIFY(bin.findByName("foo")); + QCOMPARE_EQ(bin.findByName("foo").getParent(), bin); + QVERIFY(bin.findByName("bar")); + QVERIFY(!bin.findByName("baz")); + + bin.dumpGraph("QGstElement_createFromPipelineDescription_multipleElements"); +} + +void tst_GStreamer::QGstPad_inferTypeFromName() +{ + auto makePad = [](const char *name, GstPadDirection direction) { + return QGstPad{ + gst_pad_new(name, direction), + QGstPad::NeedsRef, + }; + }; + + QVERIFY(makePad("audio_0", GST_PAD_SRC).inferTrackTypeFromName() + == QPlatformMediaPlayer::AudioStream); + QVERIFY(makePad("video_0", GST_PAD_SRC).inferTrackTypeFromName() + == QPlatformMediaPlayer::VideoStream); + QVERIFY(makePad("text_0", GST_PAD_SRC).inferTrackTypeFromName() + == QPlatformMediaPlayer::SubtitleStream); + QVERIFY(makePad("src_0", GST_PAD_SRC).inferTrackTypeFromName() == std::nullopt); + QVERIFY(makePad("text", GST_PAD_SRC).inferTrackTypeFromName() == std::nullopt); +} + +void tst_GStreamer::qDebug_GstPadDirection() +{ + auto validate = [](GstPadDirection direction, QString expectedString) { + QString str; + QDebug dbg(&str); + + dbg << direction; + + QCOMPARE_EQ(str, expectedString); + }; + + validate(GST_PAD_UNKNOWN, u"GST_PAD_UNKNOWN "_s); + validate(GST_PAD_SRC, u"GST_PAD_SRC "_s); + validate(GST_PAD_SINK, u"GST_PAD_SINK "_s); +} + +void tst_GStreamer::qDebug_GstStreamStatusType() +{ + auto validate = [](GstStreamStatusType type, QString expectedString) { + QString str; + QDebug dbg(&str); + + dbg << type; + + QCOMPARE_EQ(str, expectedString); + }; + + validate(GST_STREAM_STATUS_TYPE_CREATE, u"GST_STREAM_STATUS_TYPE_CREATE "_s); + validate(GST_STREAM_STATUS_TYPE_ENTER, u"GST_STREAM_STATUS_TYPE_ENTER "_s); + validate(GST_STREAM_STATUS_TYPE_LEAVE, u"GST_STREAM_STATUS_TYPE_LEAVE "_s); + validate(GST_STREAM_STATUS_TYPE_DESTROY, u"GST_STREAM_STATUS_TYPE_DESTROY "_s); + validate(GST_STREAM_STATUS_TYPE_START, u"GST_STREAM_STATUS_TYPE_START "_s); + validate(GST_STREAM_STATUS_TYPE_PAUSE, u"GST_STREAM_STATUS_TYPE_PAUSE "_s); + validate(GST_STREAM_STATUS_TYPE_STOP, u"GST_STREAM_STATUS_TYPE_STOP "_s); +} + +QTEST_GUILESS_MAIN(tst_GStreamer) + +#include "moc_tst_gstreamer_backend.cpp" diff --git a/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h new file mode 100644 index 000000000..7252dffdd --- /dev/null +++ b/tests/auto/unit/multimedia/gstreamer_backend/tst_gstreamer_backend.h @@ -0,0 +1,49 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_GSTREAMER_BACKEND_H +#define TST_GSTREAMER_BACKEND_H + +#include <QtTest/QtTest> + +#include <QtQGstreamerMediaPlugin/private/qgstreamerintegration_p.h> +#include <QtQGstreamerMediaPlugin/private/qgst_handle_types_p.h> + +QT_USE_NAMESPACE + +class tst_GStreamer : public QObject +{ + Q_OBJECT + + QGstTagListHandle parseTagList(const char *); + QGstTagListHandle parseTagList(const QByteArray &); + +private slots: + void qGstCasts_withElement(); + void qGstCasts_withBin(); + void qGstCasts_withPipeline(); + + void metadata_taglistToMetaData(); + void metadata_taglistToMetaData_extractsOrientation(); + void metadata_taglistToMetaData_extractsOrientation_data(); + void metadata_taglistToMetaData_extractsDuration(); + void metadata_taglistToMetaData_extractsLanguage(); + void metadata_taglistToMetaData_extractsLanguage_data(); + + void metadata_capsToMetaData(); + void metadata_capsToMetaData_data(); + + void QGstBin_createFromPipelineDescription(); + void QGstElement_createFromPipelineDescription(); + void QGstElement_createFromPipelineDescription_multipleElementsCreatesBin(); + + void QGstPad_inferTypeFromName(); + + void qDebug_GstPadDirection(); + void qDebug_GstStreamStatusType(); + +private: + QGstreamerIntegration integration; +}; + +#endif // TST_GSTREAMER_BACKEND_H diff --git a/tests/auto/unit/multimedia/qabstractvideobuffer/CMakeLists.txt b/tests/auto/unit/multimedia/qabstractvideobuffer/CMakeLists.txt index bd7f67a19..8f59a0e40 100644 --- a/tests/auto/unit/multimedia/qabstractvideobuffer/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qabstractvideobuffer/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qabstractvideobuffer.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qabstractvideobuffer SOURCES tst_qabstractvideobuffer.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qabstractvideobuffer/tst_qabstractvideobuffer.cpp b/tests/auto/unit/multimedia/qabstractvideobuffer/tst_qabstractvideobuffer.cpp index 94594ba99..e75e1395a 100644 --- a/tests/auto/unit/multimedia/qabstractvideobuffer/tst_qabstractvideobuffer.cpp +++ b/tests/auto/unit/multimedia/qabstractvideobuffer/tst_qabstractvideobuffer.cpp @@ -1,42 +1,9 @@ -/**************************************************************************** -** -** 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 <private/qabstractvideobuffer_p.h> - -// Adds an enum, and the stringized version -#define ADD_ENUM_TEST(x) \ - QTest::newRow(#x) \ - << QVideoFrame::x \ - << QString(QLatin1String(#x)); +#include <private/qhwvideobuffer_p.h> class tst_QAbstractVideoBuffer : public QObject { @@ -55,19 +22,16 @@ private slots: void handleType_data(); void handleType(); void handle(); - void mapMode(); void mapModeDebug_data(); void mapModeDebug(); }; -class QtTestVideoBuffer : public QAbstractVideoBuffer +class QtTestVideoBuffer : public QHwVideoBuffer { public: - QtTestVideoBuffer(QVideoFrame::HandleType type) : QAbstractVideoBuffer(type) {} - - [[nodiscard]] QVideoFrame::MapMode mapMode() const override { return QVideoFrame::ReadWrite; } + QtTestVideoBuffer(QVideoFrame::HandleType type) : QHwVideoBuffer(type) { } - MapData map(QVideoFrame::MapMode) override { return {}; } + MapData map(QtVideo::MapMode) override { return {}; } void unmap() override {} }; @@ -100,8 +64,8 @@ void tst_QAbstractVideoBuffer::handleType_data() QTest::addColumn<QVideoFrame::HandleType>("type"); QTest::addColumn<QString>("stringized"); - ADD_ENUM_TEST(NoHandle); - ADD_ENUM_TEST(RhiTextureHandle); + QTest::newRow("NoHandle") << QVideoFrame::NoHandle << QStringLiteral("NoHandle"); + QTest::newRow("RhiTextureHandle") << QVideoFrame::RhiTextureHandle << QStringLiteral("RhiTextureHandle"); } void tst_QAbstractVideoBuffer::handleType() @@ -121,29 +85,27 @@ void tst_QAbstractVideoBuffer::handle() { QtTestVideoBuffer buffer(QVideoFrame::NoHandle); - QVERIFY(buffer.textureHandle(0) == 0); -} - -void tst_QAbstractVideoBuffer::mapMode() -{ - QtTestVideoBuffer maptest(QVideoFrame::NoHandle); - QVERIFY2(maptest.mapMode() == QVideoFrame::ReadWrite, "ReadWrite Failed"); + QVERIFY(buffer.textureHandle(nullptr, 0) == 0); } void tst_QAbstractVideoBuffer::mapModeDebug_data() { - QTest::addColumn<QVideoFrame::MapMode>("mapMode"); + QTest::addColumn<QtVideo::MapMode>("mapMode"); QTest::addColumn<QString>("stringized"); - ADD_ENUM_TEST(NotMapped); - ADD_ENUM_TEST(ReadOnly); - ADD_ENUM_TEST(WriteOnly); - ADD_ENUM_TEST(ReadWrite); + QTest::newRow("NotMapped") << QtVideo::MapMode::NotMapped + << QStringLiteral("QtVideo::MapMode::NotMapped"); + QTest::newRow("ReadOnly") << QtVideo::MapMode::ReadOnly + << QStringLiteral("QtVideo::MapMode::ReadOnly"); + QTest::newRow("WriteOnly") << QtVideo::MapMode::WriteOnly + << QStringLiteral("QtVideo::MapMode::WriteOnly"); + QTest::newRow("ReadWrite") << QtVideo::MapMode::ReadWrite + << QStringLiteral("QtVideo::MapMode::ReadWrite"); } void tst_QAbstractVideoBuffer::mapModeDebug() { - QFETCH(QVideoFrame::MapMode, mapMode); + QFETCH(QtVideo::MapMode, mapMode); QFETCH(QString, stringized); QTest::ignoreMessage(QtDebugMsg, stringized.toLatin1().constData()); diff --git a/tests/auto/unit/multimedia/qaudiobuffer/CMakeLists.txt b/tests/auto/unit/multimedia/qaudiobuffer/CMakeLists.txt index 6e9a99180..807f3acaa 100644 --- a/tests/auto/unit/multimedia/qaudiobuffer/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudiobuffer/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudiobuffer.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qaudiobuffer SOURCES tst_qaudiobuffer.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Multimedia ) diff --git a/tests/auto/unit/multimedia/qaudiobuffer/tst_qaudiobuffer.cpp b/tests/auto/unit/multimedia/qaudiobuffer/tst_qaudiobuffer.cpp index 875025559..7c74fe994 100644 --- a/tests/auto/unit/multimedia/qaudiobuffer/tst_qaudiobuffer.cpp +++ b/tests/auto/unit/multimedia/qaudiobuffer/tst_qaudiobuffer.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 <QtCore/QString> #include <QtTest/QtTest> diff --git a/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt b/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt index d56507756..cced66bda 100644 --- a/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudiodecoder/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudiodecoder.pro. ##################################################################### @@ -9,10 +12,10 @@ qt_internal_add_test(tst_qaudiodecoder tst_qaudiodecoder.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::Multimedia Qt::MultimediaPrivate - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp b/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp index 6049bd4a2..77e161fda 100644 --- a/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.cpp +++ b/tests/auto/unit/multimedia/qaudiodecoder/tst_qaudiodecoder.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 <QtCore/QString> @@ -32,13 +7,18 @@ #include "qaudiodecoder.h" #include "qmockaudiodecoder.h" -#include "qmockintegration_p.h" +#include "qmockintegration.h" + +QT_USE_NAMESPACE + +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN class tst_QAudioDecoder : public QObject { Q_OBJECT public: + tst_QAudioDecoder(); private Q_SLOTS: @@ -49,9 +29,6 @@ private Q_SLOTS: void source(); void readAll(); void nullControl(); - -private: - QMockIntegration mockIntegration; }; tst_QAudioDecoder::tst_QAudioDecoder() @@ -63,7 +40,7 @@ void tst_QAudioDecoder::ctors() QAudioDecoder d; QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.source(), QString("")); + QCOMPARE(d.source(), QStringLiteral("")); d.setSource(QUrl()); QVERIFY(!d.isDecoding()); @@ -77,8 +54,8 @@ void tst_QAudioDecoder::read() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - 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))); // Starting with empty source == error @@ -87,9 +64,9 @@ void tst_QAudioDecoder::read() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(readySpy.count(), 0); - QCOMPARE(bufferChangedSpy.count(), 0); - QCOMPARE(errorSpy.count(), 1); + QCOMPARE(readySpy.size(), 0); + QCOMPARE(bufferChangedSpy.size(), 0); + QCOMPARE(errorSpy.size(), 1); // Set the source to something d.setSource(QUrl::fromLocalFile("Blah")); @@ -122,13 +99,13 @@ void tst_QAudioDecoder::read() QVERIFY(b.format().channelCount() == 1); QVERIFY(b.sampleCount() == 4); - QVERIFY(readySpy.count() >= 1); - QVERIFY(errorSpy.count() == 0); + QVERIFY(readySpy.size() >= 1); + QVERIFY(errorSpy.size() == 0); if (d.bufferAvailable()) { - QVERIFY(bufferChangedSpy.count() == 1); + QVERIFY(bufferChangedSpy.size() == 1); } else { - QVERIFY(bufferChangedSpy.count() == 2); + QVERIFY(bufferChangedSpy.size() == 2); } } @@ -138,8 +115,8 @@ void tst_QAudioDecoder::stop() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - 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))); // Starting with empty source == error @@ -148,9 +125,9 @@ void tst_QAudioDecoder::stop() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(readySpy.count(), 0); - QCOMPARE(bufferChangedSpy.count(), 0); - QCOMPARE(errorSpy.count(), 1); + QCOMPARE(readySpy.size(), 0); + QCOMPARE(bufferChangedSpy.size(), 0); + QCOMPARE(errorSpy.size(), 1); // Set the source to something d.setSource(QUrl::fromLocalFile("Blah")); @@ -190,8 +167,8 @@ void tst_QAudioDecoder::format() QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - 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))); // Set the source to something @@ -278,19 +255,19 @@ void tst_QAudioDecoder::readAll() d.setSource(QUrl::fromLocalFile("Foo")); QVERIFY(!d.isDecoding()); - QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64))); - QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64))); - QSignalSpy isDecodingSpy(&d, SIGNAL(isDecodingChanged(bool))); - QSignalSpy finishedSpy(&d, SIGNAL(finished())); - QSignalSpy bufferAvailableSpy(&d, SIGNAL(bufferAvailableChanged(bool))); + QSignalSpy durationSpy(&d, &QAudioDecoder::durationChanged); + QSignalSpy positionSpy(&d, &QAudioDecoder::positionChanged); + QSignalSpy isDecodingSpy(&d, &QAudioDecoder::isDecodingChanged); + QSignalSpy finishedSpy(&d, &QAudioDecoder::finished); + QSignalSpy bufferAvailableSpy(&d, &QAudioDecoder::bufferAvailableChanged); d.start(); int i = 0; forever { QVERIFY(d.isDecoding()); - QCOMPARE(isDecodingSpy.count(), 1); - QCOMPARE(durationSpy.count(), 1); + QCOMPARE(isDecodingSpy.size(), 1); + QCOMPARE(durationSpy.size(), 1); QVERIFY(finishedSpy.isEmpty()); - QTRY_VERIFY(bufferAvailableSpy.count() >= 1); + QTRY_VERIFY(bufferAvailableSpy.size() >= 1); if (d.bufferAvailable()) { QAudioBuffer b = d.read(); QVERIFY(b.isValid()); @@ -301,8 +278,8 @@ void tst_QAudioDecoder::readAll() i++; if (i == MOCK_DECODER_MAX_BUFFERS) { - QCOMPARE(finishedSpy.count(), 1); - QCOMPARE(isDecodingSpy.count(), 2); + QCOMPARE(finishedSpy.size(), 1); + QCOMPARE(isDecodingSpy.size(), 2); QVERIFY(!d.isDecoding()); QList<QVariant> arguments = isDecodingSpy.takeLast(); QVERIFY(arguments.at(0).toBool() == false); @@ -319,7 +296,7 @@ void tst_QAudioDecoder::readAll() void tst_QAudioDecoder::nullControl() { - mockIntegration.setFlags(QMockIntegration::NoAudioDecoderInterface); + QMockIntegration::instance()->setFlags(QMockIntegration::NoAudioDecoderInterface); QAudioDecoder d; QVERIFY(d.error() == QAudioDecoder::NotSupportedError); diff --git a/tests/auto/unit/multimedia/qaudioformat/CMakeLists.txt b/tests/auto/unit/multimedia/qaudioformat/CMakeLists.txt index 2cd6fc9a7..5f4c47bd7 100644 --- a/tests/auto/unit/multimedia/qaudioformat/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudioformat/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudioformat.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qaudioformat SOURCES tst_qaudioformat.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qaudioformat/tst_qaudioformat.cpp b/tests/auto/unit/multimedia/qaudioformat/tst_qaudioformat.cpp index b2ee24808..c4f4e10cf 100644 --- a/tests/auto/unit/multimedia/qaudioformat/tst_qaudioformat.cpp +++ b/tests/auto/unit/multimedia/qaudioformat/tst_qaudioformat.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 <QtTest/QtTest> @@ -34,8 +9,6 @@ #include <QStringList> #include <QList> -//TESTED_COMPONENT=src/multimedia - class tst_QAudioFormat : public QObject { Q_OBJECT diff --git a/tests/auto/unit/multimedia/qaudionamespace/CMakeLists.txt b/tests/auto/unit/multimedia/qaudionamespace/CMakeLists.txt index 2c9bb0605..3d4ac7126 100644 --- a/tests/auto/unit/multimedia/qaudionamespace/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudionamespace/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudionamespace.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qaudionamespace SOURCES tst_qaudionamespace.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qaudionamespace/tst_qaudionamespace.cpp b/tests/auto/unit/multimedia/qaudionamespace/tst_qaudionamespace.cpp index e3e7e17bf..cae002306 100644 --- a/tests/auto/unit/multimedia/qaudionamespace/tst_qaudionamespace.cpp +++ b/tests/auto/unit/multimedia/qaudionamespace/tst_qaudionamespace.cpp @@ -1,32 +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$ -** -****************************************************************************/ - -//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> @@ -235,7 +208,7 @@ void tst_QAudioNamespace::convertVolume() qreal actualOutput = QAudio::convertVolume(input, from, to); QVERIFY2(qAbs(actualOutput - expectedOutput) < 0.01, - QString("actual: %1, expected: %2").arg(actualOutput).arg(expectedOutput).toLocal8Bit().constData()); + QStringLiteral("actual: %1, expected: %2").arg(actualOutput).arg(expectedOutput).toLocal8Bit().constData()); } QTEST_MAIN(tst_QAudioNamespace) diff --git a/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt b/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt index 3ee8b5692..b2dc16b69 100644 --- a/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qaudiorecorder/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qaudiorecorder.pro. ##################################################################### @@ -9,9 +12,9 @@ qt_internal_add_test(tst_qaudiorecorder tst_qaudiorecorder.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qaudiorecorder/tst_qaudiorecorder.cpp b/tests/auto/unit/multimedia/qaudiorecorder/tst_qaudiorecorder.cpp index 52f563bb2..7d6309976 100644 --- a/tests/auto/unit/multimedia/qaudiorecorder/tst_qaudiorecorder.cpp +++ b/tests/auto/unit/multimedia/qaudiorecorder/tst_qaudiorecorder.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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> @@ -37,13 +12,13 @@ #include <qaudiosource.h> #include <qmediacapturesession.h> -//TESTED_COMPONENT=src/multimedia - #include "qmockmediacapturesession.h" -#include "qmockintegration_p.h" +#include "qmockintegration.h" QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class tst_QAudioRecorder: public QObject { Q_OBJECT @@ -59,20 +34,16 @@ private slots: private: QMediaRecorder *encoder = nullptr; - QMockIntegration *mockIntegration; }; void tst_QAudioRecorder::init() { - mockIntegration = new QMockIntegration; encoder = nullptr; } void tst_QAudioRecorder::cleanup() { delete encoder; - delete mockIntegration; - mockIntegration = nullptr; encoder = nullptr; } diff --git a/tests/auto/unit/multimedia/qaudiostatemachine/CMakeLists.txt b/tests/auto/unit/multimedia/qaudiostatemachine/CMakeLists.txt new file mode 100644 index 000000000..715091dac --- /dev/null +++ b/tests/auto/unit/multimedia/qaudiostatemachine/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qaudiostatemachine + SOURCES + tst_qaudiostatemachine.cpp + LIBRARIES + Qt::Gui + Qt::MultimediaPrivate +) + diff --git a/tests/auto/unit/multimedia/qaudiostatemachine/tst_qaudiostatemachine.cpp b/tests/auto/unit/multimedia/qaudiostatemachine/tst_qaudiostatemachine.cpp new file mode 100644 index 000000000..433590d09 --- /dev/null +++ b/tests/auto/unit/multimedia/qaudiostatemachine/tst_qaudiostatemachine.cpp @@ -0,0 +1,661 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +// TESTED_COMPONENT=src/multimedia + +#include <QtTest/QtTest> +#include <private/qaudiostatemachine_p.h> +#include <private/qaudiosystem_p.h> +#include <QThread> + +QT_USE_NAMESPACE + +template<typename F> +static std::unique_ptr<QThread> createTestThread(std::vector<std::atomic_int> &counters, + size_t index, F &&functor, + int minAttemptsCount = 2000) +{ + return std::unique_ptr<QThread>(QThread::create([=, &counters]() { + auto checkCounter = [=](int counter) { return counter < minAttemptsCount; }; + for (; !QTest::currentTestFailed() + && std::any_of(counters.begin(), counters.end(), checkCounter); + ++counters[index]) + functor(); + })); +} + +class tst_QAudioStateMachine : public QObject +{ + Q_OBJECT + +private slots: + void constructor_setsStoppedStateWithNoError(); + + void start_changesState_whenStateIsStopped_data(); + void start_changesState_whenStateIsStopped(); + + void start_doesntChangeState_whenStateIsNotStopped_data(); + void start_doesntChangeState_whenStateIsNotStopped(); + + void stop_changesState_whenStateIsNotStopped_data(); + void stop_changesState_whenStateIsNotStopped(); + + void stop_doesntChangeState_whenStateIsStopped_data(); + void stop_doesntChangeState_whenStateIsStopped(); + + void stopWithDraining_changesState_whenStateIsNotStopped_data(); + void stopWithDraining_changesState_whenStateIsNotStopped(); + + void methods_dontChangeState_whenDraining(); + + void onDrained_finishesDraining(); + + void onDrained_getsFailed_whenDrainHasntBeenCalled_data(); + void onDrained_getsFailed_whenDrainHasntBeenCalled(); + + void updateActiveOrIdle_doesntChangeState_whenStateIsNotActiveOrIdle_data(); + void updateActiveOrIdle_doesntChangeState_whenStateIsNotActiveOrIdle(); + + void updateActiveOrIdle_changesState_whenStateIsActiveOrIdle_data(); + void updateActiveOrIdle_changesState_whenStateIsActiveOrIdle(); + + void suspendAndResume_saveAndRestoreState_whenStateIsActiveOrIdle_data(); + void suspendAndResume_saveAndRestoreState_whenStateIsActiveOrIdle(); + + void suspend_doesntChangeState_whenStateIsNotActiveOrIdle_data(); + void suspend_doesntChangeState_whenStateIsNotActiveOrIdle(); + + void resume_doesntChangeState_whenStateIsNotSuspended_data(); + void resume_doesntChangeState_whenStateIsNotSuspended(); + + void deleteNotifierInSlot_suppressesAdjacentSignal(); + + void twoThreadsToggleSuspendResumeAndIdleActive_statesAreConsistent(); + + void twoThreadsToggleStartStop_statesAreConsistent(); + +private: + void generateNotStoppedPrevStates() + { + QTest::addColumn<QAudio::State>("prevState"); + QTest::addColumn<QAudio::Error>("prevError"); + + QTest::newRow("from IdleState") << QAudio::IdleState << QAudio::UnderrunError; + QTest::newRow("from ActiveState") << QAudio::ActiveState << QAudio::NoError; + QTest::newRow("from SuspendedState") << QAudio::SuspendedState << QAudio::NoError; + } + + void generateStoppedAndSuspendedPrevStates() + { + QTest::addColumn<QAudio::State>("prevState"); + + QTest::newRow("from StoppedState") << QAudio::StoppedState; + QTest::newRow("from SuspendedState") << QAudio::SuspendedState; + } +}; + +void tst_QAudioStateMachine::constructor_setsStoppedStateWithNoError() +{ + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), QAudio::NoError); + QVERIFY(!stateMachine.isActiveOrIdle()); +} + +void tst_QAudioStateMachine::start_changesState_whenStateIsStopped_data() +{ + QTest::addColumn<bool>("active"); + QTest::addColumn<QAudio::State>("expectedState"); + + QTest::newRow("to active") << true << QAudio::ActiveState; + QTest::newRow("to not active") << false << QAudio::IdleState; +} + +void tst_QAudioStateMachine::start_changesState_whenStateIsStopped() +{ + QFETCH(bool, active); + QFETCH(QAudio::State, expectedState); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(stateMachine.start(active)); + + QCOMPARE(stateSpy.size(), 1); + QCOMPARE(stateSpy.front().front().value<QAudio::State>(), expectedState); + QCOMPARE(errorSpy.size(), 0); + QCOMPARE(stateMachine.state(), expectedState); + QCOMPARE(stateMachine.error(), QAudio::NoError); +} + +void tst_QAudioStateMachine::start_doesntChangeState_whenStateIsNotStopped_data() +{ + generateNotStoppedPrevStates(); +} + +void tst_QAudioStateMachine::start_doesntChangeState_whenStateIsNotStopped() +{ + QFETCH(QAudio::State, prevState); + QFETCH(QAudio::Error, prevError); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + stateMachine.forceSetState(prevState, prevError); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY2(!stateMachine.start(), "Cannot start (active)"); + QVERIFY2(!stateMachine.start(false), "Cannot start (not active)"); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + + QCOMPARE(stateMachine.state(), prevState); + QCOMPARE(stateMachine.error(), prevError); +} + +void tst_QAudioStateMachine::stop_changesState_whenStateIsNotStopped_data() +{ + generateNotStoppedPrevStates(); +} + +void tst_QAudioStateMachine::stop_changesState_whenStateIsNotStopped() +{ + QFETCH(QAudio::State, prevState); + QFETCH(QAudio::Error, prevError); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState, prevError); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + auto notifier = stateMachine.stop(); + QVERIFY(notifier); + + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), QAudio::NoError); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + QVERIFY(!notifier.isDraining()); + + notifier.reset(); + + QCOMPARE(stateSpy.size(), 1); + QCOMPARE(stateSpy.front().front().value<QAudio::State>(), QAudio::StoppedState); + QCOMPARE(errorSpy.size(), prevError == QAudio::NoError ? 0 : 1); + if (!errorSpy.empty()) + QCOMPARE(errorSpy.front().front().value<QAudio::Error>(), QAudio::NoError); + + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), QAudio::NoError); + QVERIFY(!stateMachine.isDraining()); +} + +void tst_QAudioStateMachine::stop_doesntChangeState_whenStateIsStopped_data() +{ + QTest::addColumn<QAudio::Error>("error"); + + QTest::newRow("from NoError") << QAudio::NoError; + QTest::newRow("from IOError") << QAudio::IOError; +} + +void tst_QAudioStateMachine::stop_doesntChangeState_whenStateIsStopped() +{ + QFETCH(QAudio::Error, error); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.setError(error); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY2(!stateMachine.stop(), "should return false if already stopped"); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), error); + QVERIFY(!stateMachine.isDraining()); +} + +void tst_QAudioStateMachine::stopWithDraining_changesState_whenStateIsNotStopped_data() +{ + generateNotStoppedPrevStates(); +} + +void tst_QAudioStateMachine::stopWithDraining_changesState_whenStateIsNotStopped() +{ + QFETCH(QAudio::State, prevState); + QFETCH(QAudio::Error, prevError); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState, prevError); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + + auto notifier = stateMachine.stop(QAudio::NoError, true); + QVERIFY(notifier); + QCOMPARE(notifier.isDraining(), prevState == QAudio::ActiveState); + notifier.reset(); + + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), QAudio::NoError); + QCOMPARE(stateMachine.isDraining(), prevState == QAudio::ActiveState); + + QCOMPARE(stateSpy.size(), 1); +} + +void tst_QAudioStateMachine::methods_dontChangeState_whenDraining() +{ + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(QAudio::ActiveState); + stateMachine.stop(QAudio::IOError, true); + QVERIFY(stateMachine.isDraining()); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(!stateMachine.start()); + QVERIFY(!stateMachine.stop()); + QVERIFY(!stateMachine.stop(QAudio::NoError, true)); + QVERIFY(!stateMachine.suspend()); + QVERIFY(!stateMachine.resume()); + QVERIFY(!stateMachine.updateActiveOrIdle(false)); + QVERIFY(!stateMachine.updateActiveOrIdle(true)); + + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), QAudio::IOError); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + + QVERIFY(stateMachine.isDraining()); +} + +void tst_QAudioStateMachine::onDrained_finishesDraining() +{ + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(QAudio::ActiveState); + stateMachine.stop(QAudio::IOError, true); + QVERIFY(stateMachine.isDraining()); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(stateMachine.onDrained()); + QVERIFY(!stateMachine.isDraining()); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + + QCOMPARE(stateMachine.state(), QAudio::StoppedState); + QCOMPARE(stateMachine.error(), QAudio::IOError); + + QVERIFY(stateMachine.start()); +} + +void tst_QAudioStateMachine::onDrained_getsFailed_whenDrainHasntBeenCalled_data() +{ + generateNotStoppedPrevStates(); + QTest::newRow("from Stopped State") << QAudio::StoppedState << QAudio::IOError; +} + +void tst_QAudioStateMachine::onDrained_getsFailed_whenDrainHasntBeenCalled() +{ + QFETCH(QAudio::State, prevState); + QFETCH(QAudio::Error, prevError); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState, prevError); + + QVERIFY(!stateMachine.onDrained()); + + QCOMPARE(stateMachine.state(), prevState); + QCOMPARE(stateMachine.error(), prevError); +} + +void tst_QAudioStateMachine::updateActiveOrIdle_doesntChangeState_whenStateIsNotActiveOrIdle_data() +{ + generateStoppedAndSuspendedPrevStates(); +} + +void tst_QAudioStateMachine::updateActiveOrIdle_doesntChangeState_whenStateIsNotActiveOrIdle() +{ + QFETCH(QAudio::State, prevState); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(!stateMachine.updateActiveOrIdle(true)); + QVERIFY(!stateMachine.updateActiveOrIdle(false)); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); +} + +void tst_QAudioStateMachine::updateActiveOrIdle_changesState_whenStateIsActiveOrIdle_data() +{ + QTest::addColumn<QAudio::State>("prevState"); + QTest::addColumn<QAudio::Error>("prevError"); + QTest::addColumn<bool>("active"); + QTest::addColumn<QAudio::Error>("error"); + + QTest::newRow("from ActiveState+NoError -> not active+NoError") + << QAudio::ActiveState << QAudio::NoError << false << QAudio::NoError; + QTest::newRow("from Idle(UnderrunError) -> active+NoError") + << QAudio::IdleState << QAudio::UnderrunError << true << QAudio::NoError; + QTest::newRow("from Idle(UnderrunError) -> not active+UnderrunError") + << QAudio::IdleState << QAudio::UnderrunError << false << QAudio::UnderrunError; +} + +void tst_QAudioStateMachine::updateActiveOrIdle_changesState_whenStateIsActiveOrIdle() +{ + QFETCH(QAudio::State, prevState); + QFETCH(QAudio::Error, prevError); + QFETCH(bool, active); + QFETCH(QAudio::Error, error); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState, prevError); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + const auto expectedState = active ? QAudio::ActiveState : QAudio::IdleState; + + auto notifier = stateMachine.updateActiveOrIdle(active, error); + QVERIFY(notifier); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + + QCOMPARE(stateMachine.state(), expectedState); + QCOMPARE(stateMachine.error(), error); + + notifier.reset(); + + QCOMPARE(stateSpy.size(), expectedState == prevState ? 0 : 1); + if (!stateSpy.empty()) + QCOMPARE(stateSpy.front().front().value<QAudio::State>(), expectedState); + + QCOMPARE(errorSpy.size(), prevError == error ? 0 : 1); + if (!errorSpy.empty()) + QCOMPARE(errorSpy.front().front().value<QAudio::Error>(), error); + + QCOMPARE(stateMachine.state(), expectedState); + QCOMPARE(stateMachine.error(), error); +} + +void tst_QAudioStateMachine::suspendAndResume_saveAndRestoreState_whenStateIsActiveOrIdle_data() +{ + QTest::addColumn<QAudio::State>("prevState"); + QTest::addColumn<QAudio::Error>("prevError"); + + QTest::newRow("from Active+NoError") << QAudio::ActiveState << QAudio::NoError; + QTest::newRow("from Idle+UnderrunError") << QAudio::IdleState << QAudio::UnderrunError; +} + +void tst_QAudioStateMachine::suspendAndResume_saveAndRestoreState_whenStateIsActiveOrIdle() +{ + QFETCH(QAudio::State, prevState); + QFETCH(QAudio::Error, prevError); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState, prevError); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(stateMachine.suspend()); + + QCOMPARE(stateSpy.size(), 1); + QCOMPARE(stateSpy.front().front().value<QAudio::State>(), QAudio::SuspendedState); + QCOMPARE(errorSpy.size(), prevError == QAudio::NoError ? 0 : 1); + + QCOMPARE(stateMachine.state(), QAudio::SuspendedState); + QCOMPARE(stateMachine.error(), QAudio::NoError); + + stateSpy.clear(); + errorSpy.clear(); + + QVERIFY(!stateMachine.suspend()); + QVERIFY(stateMachine.resume()); + + QCOMPARE(stateSpy.size(), 1); + QCOMPARE(stateSpy.front().front().value<QAudio::State>(), prevState); + QCOMPARE(errorSpy.size(), 0); + + QCOMPARE(stateMachine.state(), prevState); + QCOMPARE(stateMachine.error(), QAudio::NoError); +} + +void tst_QAudioStateMachine::suspend_doesntChangeState_whenStateIsNotActiveOrIdle_data() +{ + generateStoppedAndSuspendedPrevStates(); +} + +void tst_QAudioStateMachine::suspend_doesntChangeState_whenStateIsNotActiveOrIdle() +{ + QFETCH(QAudio::State, prevState); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(!stateMachine.suspend()); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + + QCOMPARE(stateMachine.state(), prevState); + QCOMPARE(stateMachine.error(), QAudio::NoError); +} + +void tst_QAudioStateMachine::resume_doesntChangeState_whenStateIsNotSuspended_data() +{ + QTest::addColumn<QAudio::State>("prevState"); + + QTest::newRow("from StoppedState") << QAudio::StoppedState; + QTest::newRow("from ActiveState") << QAudio::ActiveState; + QTest::newRow("from IdleState") << QAudio::IdleState; +} + +void tst_QAudioStateMachine::resume_doesntChangeState_whenStateIsNotSuspended() +{ + QFETCH(QAudio::State, prevState); + + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + stateMachine.forceSetState(prevState); + + QSignalSpy stateSpy(&changeNotifier, &QAudioStateChangeNotifier::stateChanged); + QSignalSpy errorSpy(&changeNotifier, &QAudioStateChangeNotifier::errorChanged); + + QVERIFY(!stateMachine.resume()); + + QCOMPARE(stateSpy.size(), 0); + QCOMPARE(errorSpy.size(), 0); + + QCOMPARE(stateMachine.state(), prevState); + QCOMPARE(stateMachine.error(), QAudio::NoError); +} + +void tst_QAudioStateMachine::deleteNotifierInSlot_suppressesAdjacentSignal() +{ + auto changeNotifier = std::make_unique<QAudioStateChangeNotifier>(); + QAudioStateMachine stateMachine(*changeNotifier); + stateMachine.start(); + + auto onSignal = [&]() { + QVERIFY2(changeNotifier, "The 2nd signal shouldn't be emitted"); + changeNotifier.reset(); + }; + + connect(changeNotifier.get(), &QAudioStateChangeNotifier::errorChanged, + this, onSignal, Qt::DirectConnection); + connect(changeNotifier.get(), &QAudioStateChangeNotifier::stateChanged, + this, onSignal, Qt::DirectConnection); + + stateMachine.stop(QAudio::IOError); +} + +void tst_QAudioStateMachine::twoThreadsToggleSuspendResumeAndIdleActive_statesAreConsistent() +{ + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + QVERIFY(stateMachine.start()); + QCOMPARE(stateMachine.state(), QAudio::ActiveState); + + std::atomic<int> signalsCount = 0; + std::atomic<int> changesCount = 0; + + connect(&changeNotifier, &QAudioStateChangeNotifier::stateChanged, + this, [&](QAudio::State) { ++signalsCount; }, Qt::DirectConnection); + + std::vector<std::atomic_int> counters(2); + + auto threadSuspendResume = createTestThread(counters, 0, [&]() { + { + auto notifier = stateMachine.suspend(); + QVERIFY(notifier); + QVERIFY(notifier.isStateChanged()); + QCOMPARE(notifier.audioState(), QAudio::SuspendedState); + ++changesCount; + } + + { + auto notifier = stateMachine.resume(); + QVERIFY(notifier); + QVERIFY(notifier.isStateChanged()); + QCOMPARE_NE(notifier.audioState(), QAudio::SuspendedState); + ++changesCount; + } + }); + + auto threadIdleActive = createTestThread(counters, 1, [&]() { + if (auto notifier = stateMachine.updateActiveOrIdle(false)) { + if (notifier.isStateChanged()) + ++changesCount; + + QCOMPARE(notifier.audioState(), QAudio::IdleState); + } + + if (auto notifier = stateMachine.updateActiveOrIdle(true)) { + if (notifier.isStateChanged()) + ++changesCount; + + QCOMPARE(notifier.audioState(), QAudio::ActiveState); + } + }); + + threadSuspendResume->start(); + threadIdleActive->start(); + + threadSuspendResume->wait(); + threadIdleActive->wait(); + + if (QTest::currentTestFailed()) { + qDebug() << "counterSuspendResume:" << counters[0]; + qDebug() << "counterIdleActive:" << counters[1]; + } + + QCOMPARE(signalsCount, changesCount); +} + +void tst_QAudioStateMachine::twoThreadsToggleStartStop_statesAreConsistent() +{ + QAudioStateChangeNotifier changeNotifier; + QAudioStateMachine stateMachine(changeNotifier); + + QVERIFY(stateMachine.start()); + QCOMPARE(stateMachine.state(), QAudio::ActiveState); + + std::atomic<int> signalsCount = 0; + std::atomic<int> changesCount = 0; + + connect(&changeNotifier, &QAudioStateChangeNotifier::stateChanged, + this, [&](QAudio::State) { ++signalsCount; }, Qt::DirectConnection); + + std::vector<std::atomic_int> counters(2); + + auto threadStartActive = createTestThread(counters, 0, [&]() { + if (auto startNotifier = stateMachine.start()) { + QCOMPARE(startNotifier.prevAudioState(), QAudio::StoppedState); + QCOMPARE(startNotifier.audioState(), QAudio::ActiveState); + ++changesCount; + startNotifier.reset(); + + auto stopNotifier = stateMachine.stop(); + ++changesCount; + QVERIFY(stopNotifier); + QCOMPARE(stopNotifier.prevAudioState(), QAudio::ActiveState); + } + }); + + auto threadStartIdle = createTestThread(counters, 1, [&]() { + if (auto startNotifier = stateMachine.start(false)) { + QCOMPARE(startNotifier.prevAudioState(), QAudio::StoppedState); + QCOMPARE(startNotifier.audioState(), QAudio::IdleState); + ++changesCount; + startNotifier.reset(); + + auto stopNotifier = stateMachine.stop(); + ++changesCount; + QVERIFY(stopNotifier); + QCOMPARE(stopNotifier.audioState(), QAudio::StoppedState); + QCOMPARE(stopNotifier.prevAudioState(), QAudio::IdleState); + } + }); + + threadStartActive->start(); + threadStartIdle->start(); + + threadStartActive->wait(); + threadStartIdle->wait(); + + if (QTest::currentTestFailed()) { + qDebug() << "counterSuspendResume:" << counters[0]; + qDebug() << "counterIdleActive:" << counters[1]; + } + + QCOMPARE(signalsCount, changesCount); +} + +QTEST_GUILESS_MAIN(tst_QAudioStateMachine) + +#include "tst_qaudiostatemachine.moc" diff --git a/tests/auto/unit/multimedia/qcamera/CMakeLists.txt b/tests/auto/unit/multimedia/qcamera/CMakeLists.txt index 8413c6de4..484793923 100644 --- a/tests/auto/unit/multimedia/qcamera/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qcamera/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qcamera.pro. ##################################################################### @@ -9,9 +12,9 @@ qt_internal_add_test(tst_multimedia_qcamera tst_qcamera.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp b/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp index 52a34d4f4..bd1972550 100644 --- a/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp +++ b/tests/auto/unit/multimedia/qcamera/tst_qcamera.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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 <QDebug> @@ -41,12 +14,13 @@ #include <qobject.h> #include <qmediadevices.h> -#include "qmockintegration_p.h" +#include "qmockintegration.h" #include "qmockmediacapturesession.h" #include "qmockcamera.h" QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN class tst_QCamera: public QObject { @@ -97,13 +71,14 @@ private slots: void testSignalIsoSensitivityChanged(); void testSignalShutterSpeedChanged(); void testSignalFlashReady(); - -private: - QMockIntegration integration; }; void tst_QCamera::initTestCase() { +#ifdef Q_OS_MACOS + if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") + QSKIP("Flakiness on macOS CI, to be investigated, QTBUG-111812"); +#endif } void tst_QCamera::init() @@ -222,14 +197,14 @@ void tst_QCamera::testSimpleCameraCapture() QCOMPARE(imageCapture.error(), QImageCapture::NoError); QVERIFY(imageCapture.errorString().isEmpty()); - QSignalSpy errorSignal(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,QString))); - imageCapture.captureToFile(QString::fromLatin1("/dev/null")); + QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred); + imageCapture.captureToFile(QStringLiteral("/dev/null")); QCOMPARE(errorSignal.size(), 1); QCOMPARE(imageCapture.error(), QImageCapture::NotReadyError); QVERIFY(!imageCapture.errorString().isEmpty()); camera.start(); - imageCapture.captureToFile(QString::fromLatin1("/dev/null")); + imageCapture.captureToFile(QStringLiteral("/dev/null")); QCOMPARE(errorSignal.size(), 1); QCOMPARE(imageCapture.error(), QImageCapture::NoError); QVERIFY(imageCapture.errorString().isEmpty()); @@ -245,10 +220,10 @@ void tst_QCamera::testCameraCapture() QVERIFY(!imageCapture.isReadyForCapture()); - QSignalSpy capturedSignal(&imageCapture, SIGNAL(imageCaptured(int,QImage))); - QSignalSpy errorSignal(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,QString))); + QSignalSpy capturedSignal(&imageCapture, &QImageCapture::imageCaptured); + QSignalSpy errorSignal(&imageCapture, &QImageCapture::errorOccurred); - imageCapture.captureToFile(QString::fromLatin1("/dev/null")); + imageCapture.captureToFile(QStringLiteral("/dev/null")); QCOMPARE(capturedSignal.size(), 0); QCOMPARE(errorSignal.size(), 1); QCOMPARE(imageCapture.error(), QImageCapture::NotReadyError); @@ -259,7 +234,7 @@ void tst_QCamera::testCameraCapture() QVERIFY(imageCapture.isReadyForCapture()); QCOMPARE(errorSignal.size(), 0); - imageCapture.captureToFile(QString::fromLatin1("/dev/null")); + imageCapture.captureToFile(QStringLiteral("/dev/null")); QTRY_COMPARE(capturedSignal.size(), 1); QCOMPARE(errorSignal.size(), 0); @@ -274,11 +249,11 @@ void tst_QCamera::testCameraCaptureMetadata() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy metadataSignal(&imageCapture, SIGNAL(imageMetadataAvailable(int,const QMediaMetaData&))); - QSignalSpy savedSignal(&imageCapture, SIGNAL(imageSaved(int,QString))); + QSignalSpy metadataSignal(&imageCapture, &QImageCapture::imageMetadataAvailable); + QSignalSpy savedSignal(&imageCapture, &QImageCapture::imageSaved); camera.start(); - int id = imageCapture.captureToFile(QString::fromLatin1("/dev/null")); + int id = imageCapture.captureToFile(QStringLiteral("/dev/null")); QTRY_COMPARE(savedSignal.size(), 1); @@ -287,7 +262,7 @@ void tst_QCamera::testCameraCaptureMetadata() QVariantList metadata = metadataSignal[0]; QCOMPARE(metadata[0].toInt(), id); QMediaMetaData data = metadata[1].value<QMediaMetaData>(); - QCOMPARE(data.keys().length(), 2); + QCOMPARE(data.keys().size(), 2); QCOMPARE(data[QMediaMetaData::Author].toString(), "Author"); QCOMPARE(data[QMediaMetaData::Date].toDateTime().date().year(), 2021); } @@ -444,11 +419,11 @@ void tst_QCamera::testCameraEncodingProperyChange() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy activeChangedSignal(&camera, SIGNAL(activeChanged(bool))); + QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged); camera.start(); QCOMPARE(camera.isActive(), true); - QCOMPARE(activeChangedSignal.count(), 1); + QCOMPARE(activeChangedSignal.size(), 1); } void tst_QCamera::testSetVideoOutput() @@ -625,32 +600,34 @@ void tst_QCamera::testErrorSignal() QMediaCaptureSession session; QCamera camera; session.setCamera(&camera); - auto *service = integration.lastCaptureService(); + auto *service = QMockIntegration::instance()->lastCaptureService(); Q_ASSERT(service); Q_ASSERT(service->mockCameraControl); - QSignalSpy spyError(&camera, SIGNAL(errorOccurred(QCamera::Error,const QString&))); + QSignalSpy spyError(&camera, &QCamera::errorOccurred); /* Set the QPlatformCamera error and verify if the signal is emitted correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("Camera Error")); + service->mockCameraControl->updateError(QCamera::CameraError, QStringLiteral("Camera Error")); - QVERIFY(spyError.count() == 1); + QVERIFY(spyError.size() == 1); QCamera::Error err = qvariant_cast<QCamera::Error >(spyError.at(0).at(0)); QVERIFY(err == QCamera::CameraError); spyError.clear(); /* Set the QPlatformCamera error and verify if the signal is emitted correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("InvalidRequestError Error")); - QVERIFY(spyError.count() == 1); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("InvalidRequestError Error")); + QVERIFY(spyError.size() == 1); err = qvariant_cast<QCamera::Error >(spyError.at(0).at(0)); QVERIFY(err == QCamera::CameraError); spyError.clear(); /* Set the QPlatformCamera error and verify if the signal is emitted correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("NotSupportedFeatureError Error")); - QVERIFY(spyError.count() == 1); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("NotSupportedFeatureError Error")); + QVERIFY(spyError.size() == 1); err = qvariant_cast<QCamera::Error >(spyError.at(0).at(0)); QVERIFY(err == QCamera::CameraError); @@ -662,18 +639,20 @@ void tst_QCamera::testError() QMediaCaptureSession session; QCamera camera; session.setCamera(&camera); - auto *service = integration.lastCaptureService(); + auto *service = QMockIntegration::instance()->lastCaptureService(); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("Camera Error")); + service->mockCameraControl->updateError(QCamera::CameraError, QStringLiteral("Camera Error")); QVERIFY(camera.error() == QCamera::CameraError); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("InvalidRequestError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("InvalidRequestError Error")); QVERIFY(camera.error() == QCamera::CameraError); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("CameraError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("CameraError Error")); QVERIFY(camera.error() == QCamera::CameraError); } @@ -684,19 +663,21 @@ void tst_QCamera::testErrorString() QMediaCaptureSession session; QCamera camera; session.setCamera(&camera); - auto *service = integration.lastCaptureService(); + auto *service = QMockIntegration::instance()->lastCaptureService(); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("Camera Error")); - QVERIFY(camera.errorString() == QString("Camera Error")); + service->mockCameraControl->updateError(QCamera::CameraError, QStringLiteral("Camera Error")); + QVERIFY(camera.errorString() == QStringLiteral("Camera Error")); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("InvalidRequestError Error")); - QVERIFY(camera.errorString() == QString("InvalidRequestError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("InvalidRequestError Error")); + QVERIFY(camera.errorString() == QStringLiteral("InvalidRequestError Error")); /* Set the QPlatformCamera error and verify if it is set correctly in QCamera */ - service->mockCameraControl->setError(QCamera::CameraError,QString("CameraError Error")); - QVERIFY(camera.errorString() == QString("CameraError Error")); + service->mockCameraControl->updateError(QCamera::CameraError, + QStringLiteral("CameraError Error")); + QVERIFY(camera.errorString() == QStringLiteral("CameraError Error")); } void tst_QCamera::testSetCameraFormat() @@ -704,34 +685,34 @@ void tst_QCamera::testSetCameraFormat() QCamera camera; QCameraDevice device = camera.cameraDevice(); auto videoFormats = device.videoFormats(); - QVERIFY(videoFormats.count()); + QVERIFY(videoFormats.size()); QCameraFormat cameraFormat = videoFormats.first(); - QSignalSpy spy(&camera, SIGNAL(cameraFormatChanged())); - QVERIFY(spy.count() == 0); + QSignalSpy spy(&camera, &QCamera::cameraFormatChanged); + QVERIFY(spy.size() == 0); camera.setCameraFormat(cameraFormat); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(camera.cameraFormat(), cameraFormat); QCOMPARE(camera.cameraFormat().pixelFormat(), QVideoFrameFormat::Format_ARGB8888); QCOMPARE(camera.cameraFormat().resolution(), QSize(640, 480)); QCOMPARE(camera.cameraFormat().minFrameRate(), 0); QCOMPARE(camera.cameraFormat().maxFrameRate(), 30); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); spy.clear(); camera.setCameraFormat({}); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(camera.cameraFormat(), QCameraFormat()); spy.clear(); camera.setCameraDevice(QMediaDevices::videoInputs().at(1)); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(camera.cameraFormat(), QCameraFormat()); camera.setCameraFormat(camera.cameraDevice().videoFormats().first()); QCOMPARE(camera.cameraFormat().pixelFormat(), QVideoFrameFormat::Format_XRGB8888); QCOMPARE(camera.cameraFormat().resolution(), QSize(1280, 720)); QCOMPARE(camera.cameraFormat().minFrameRate(), 0); QCOMPARE(camera.cameraFormat().maxFrameRate(), 30); - QCOMPARE(spy.count(), 2); + QCOMPARE(spy.size(), 2); } //Added this code to cover QCamera::FocusModeHyperfocal and QCamera::FocusModeAutoNear @@ -758,14 +739,14 @@ void tst_QCamera::testZoomChanged() QCamera camera; session.setCamera(&camera); - QSignalSpy spy(&camera, SIGNAL(zoomFactorChanged(float))); - QVERIFY(spy.count() == 0); + QSignalSpy spy(&camera, &QCamera::zoomFactorChanged); + QVERIFY(spy.size() == 0); camera.setZoomFactor(2.0); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); camera.zoomTo(3.0, 1); - QVERIFY(spy.count() == 2); + QVERIFY(spy.size() == 2); camera.zoomTo(1.0, 0); - QVERIFY(spy.count() == 3); + QVERIFY(spy.size() == 3); } void tst_QCamera::testMaxZoomChangedSignal() @@ -773,12 +754,12 @@ void tst_QCamera::testMaxZoomChangedSignal() QMediaCaptureSession session; QCamera camera; session.setCamera(&camera); - QMockCamera *mock = integration.lastCamera(); + QMockCamera *mock = QMockIntegration::instance()->lastCamera(); // ### change max zoom factor on backend, e.g. by changing camera - QSignalSpy spy(&camera, SIGNAL(maximumZoomFactorChanged(float))); + QSignalSpy spy(&camera, &QCamera::maximumZoomFactorChanged); mock->maximumZoomFactorChanged(55); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); QCOMPARE(camera.maximumZoomFactor(), 55); } @@ -788,9 +769,9 @@ void tst_QCamera::testSignalExposureCompensationChanged() QCamera camera; session.setCamera(&camera); - QSignalSpy spyExposureCompensationChanged(&camera, SIGNAL(exposureCompensationChanged(float))); + QSignalSpy spyExposureCompensationChanged(&camera, &QCamera::exposureCompensationChanged); - QVERIFY(spyExposureCompensationChanged.count() ==0); + QVERIFY(spyExposureCompensationChanged.size() == 0); QVERIFY(camera.exposureCompensation() != 800); camera.setExposureCompensation(2.0); @@ -799,14 +780,14 @@ void tst_QCamera::testSignalExposureCompensationChanged() QVERIFY(camera.exposureCompensation() == 2.0); - QCOMPARE(spyExposureCompensationChanged.count(),1); + QCOMPARE(spyExposureCompensationChanged.size(),1); // Setting the same should not result in a signal camera.setExposureCompensation(2.0); QTest::qWait(100); QVERIFY(camera.exposureCompensation() == 2.0); - QCOMPARE(spyExposureCompensationChanged.count(),1); + QCOMPARE(spyExposureCompensationChanged.size(),1); } void tst_QCamera::testSignalIsoSensitivityChanged() @@ -815,13 +796,13 @@ void tst_QCamera::testSignalIsoSensitivityChanged() QCamera camera; session.setCamera(&camera); - QSignalSpy spyisoSensitivityChanged(&camera, SIGNAL(isoSensitivityChanged(int))); + QSignalSpy spyisoSensitivityChanged(&camera, &QCamera::isoSensitivityChanged); - QVERIFY(spyisoSensitivityChanged.count() ==0); + QVERIFY(spyisoSensitivityChanged.size() ==0); camera.setManualIsoSensitivity(800); //set the manualiso sentivity to 800 QTest::qWait(100); - QVERIFY(spyisoSensitivityChanged.count() ==1); + QVERIFY(spyisoSensitivityChanged.size() ==1); } void tst_QCamera::testSignalShutterSpeedChanged() @@ -830,14 +811,14 @@ void tst_QCamera::testSignalShutterSpeedChanged() QCamera camera; session.setCamera(&camera); - QSignalSpy spySignalExposureTimeChanged(&camera, SIGNAL(exposureTimeChanged(float))); + QSignalSpy spySignalExposureTimeChanged(&camera, &QCamera::exposureTimeChanged); - QVERIFY(spySignalExposureTimeChanged.count() ==0); + QVERIFY(spySignalExposureTimeChanged.size() == 0); camera.setManualExposureTime(2.0);//set the ManualShutterSpeed to 2.0 QTest::qWait(100); - QVERIFY(spySignalExposureTimeChanged.count() ==1); + QVERIFY(spySignalExposureTimeChanged.size() ==1); } void tst_QCamera::testSignalFlashReady() @@ -846,9 +827,9 @@ void tst_QCamera::testSignalFlashReady() QCamera camera; session.setCamera(&camera); - QSignalSpy spyflashReady(&camera,SIGNAL(flashReady(bool))); + QSignalSpy spyflashReady(&camera, &QCamera::flashReady); - QVERIFY(spyflashReady.count() == 0); + QVERIFY(spyflashReady.size() == 0); QVERIFY(camera.flashMode() == QCamera::FlashAuto); @@ -856,7 +837,7 @@ void tst_QCamera::testSignalFlashReady() QVERIFY(camera.flashMode() == QCamera::FlashOff); - QCOMPARE(spyflashReady.count(), 1); + QCOMPARE(spyflashReady.size(), 1); } QTEST_MAIN(tst_QCamera) diff --git a/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt b/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt index 150307fea..4d9977e8e 100644 --- a/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qcameradevice/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qcameradevice.pro. ##################################################################### @@ -9,9 +12,9 @@ qt_internal_add_test(tst_qcameradevice tst_qcameradevice.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp b/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp index c8f93330f..455586243 100644 --- a/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp +++ b/tests/auto/unit/multimedia/qcameradevice/tst_qcameradevice.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** 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> @@ -33,42 +8,26 @@ #include <qcameradevice.h> #include <qmediadevices.h> -#include "qmockintegration_p.h" -#include "qmockmediacapturesession.h" +#include "qmockintegration.h" QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + +using namespace Qt::Literals; + class tst_QCameraDevice: public QObject { Q_OBJECT -public slots: - void initTestCase(); - void init(); - void cleanup(); - private slots: void constructor(); void defaultCamera(); void availableCameras(); void equality_operators(); - -private: - QMockIntegration integration; + void qDebug_operator(); }; -void tst_QCameraDevice::initTestCase() -{ -} - -void tst_QCameraDevice::init() -{ -} - -void tst_QCameraDevice::cleanup() -{ -} - void tst_QCameraDevice::constructor() { { @@ -76,8 +35,8 @@ void tst_QCameraDevice::constructor() QCamera camera; QCameraDevice info(camera.cameraDevice()); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("default")); - QCOMPARE(info.description(), QStringLiteral("defaultCamera")); + QCOMPARE(info.id(), u"default"_s); + QCOMPARE(info.description(), u"defaultCamera"_s); QCOMPARE(info.position(), QCameraDevice::UnspecifiedPosition); } @@ -92,14 +51,14 @@ void tst_QCameraDevice::constructor() QCamera camera(info); QCOMPARE(info, camera.cameraDevice()); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("back")); - QCOMPARE(info.description(), QStringLiteral("backCamera")); + QCOMPARE(info.id(), u"back"_s); + QCOMPARE(info.description(), u"backCamera"_s); QCOMPARE(info.position(), QCameraDevice::BackFace); QCameraDevice info2(info); QVERIFY(!info2.isNull()); - QCOMPARE(info2.id(), QStringLiteral("back")); - QCOMPARE(info2.description(), QStringLiteral("backCamera")); + QCOMPARE(info2.id(), u"back"_s); + QCOMPARE(info2.description(), u"backCamera"_s); QCOMPARE(info2.position(), QCameraDevice::BackFace); } @@ -108,8 +67,8 @@ void tst_QCameraDevice::defaultCamera() QCameraDevice info = QMediaDevices::defaultVideoInput(); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("default")); - QCOMPARE(info.description(), QStringLiteral("defaultCamera")); + QCOMPARE(info.id(), u"default"_s); + QCOMPARE(info.description(), u"defaultCamera"_s); QCOMPARE(info.position(), QCameraDevice::UnspecifiedPosition); QCamera camera(info); @@ -119,12 +78,12 @@ void tst_QCameraDevice::defaultCamera() void tst_QCameraDevice::availableCameras() { QList<QCameraDevice> cameras = QMediaDevices::videoInputs(); - QCOMPARE(cameras.count(), 3); + QCOMPARE(cameras.size(), 3); QCameraDevice info = cameras.at(0); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("default")); - QCOMPARE(info.description(), QStringLiteral("defaultCamera")); + QCOMPARE(info.id(), u"default"_s); + QCOMPARE(info.description(), u"defaultCamera"_s); QCOMPARE(info.position(), QCameraDevice::UnspecifiedPosition); info = cameras.at(1); @@ -133,11 +92,11 @@ void tst_QCameraDevice::availableCameras() QCOMPARE(info.description(), QStringLiteral("frontCamera")); QCOMPARE(info.position(), QCameraDevice::FrontFace); - QCOMPARE(cameras.count(), 3); + QCOMPARE(cameras.size(), 3); info = cameras.at(2); QVERIFY(!info.isNull()); - QCOMPARE(info.id(), QStringLiteral("back")); - QCOMPARE(info.description(), QStringLiteral("backCamera")); + QCOMPARE(info.id(), u"back"_s); + QCOMPARE(info.description(), u"backCamera"_s); QCOMPARE(info.position(), QCameraDevice::BackFace); } @@ -162,6 +121,18 @@ void tst_QCameraDevice::equality_operators() } } +void tst_QCameraDevice::qDebug_operator() +{ + QString outputString; + QDebug debug(&outputString); + debug.nospace(); + + QCameraDevice defaultCamera = QMediaDevices::defaultVideoInput(); + debug << defaultCamera; + + QCOMPARE(outputString, + u"\"QCameraDevice(name=defaultCamera, id=default, position=UnspecifiedPosition)\" "_s); +} QTEST_MAIN(tst_QCameraDevice) diff --git a/tests/auto/unit/multimedia/qerrorinfo/CMakeLists.txt b/tests/auto/unit/multimedia/qerrorinfo/CMakeLists.txt new file mode 100644 index 000000000..ea6ac5690 --- /dev/null +++ b/tests/auto/unit/multimedia/qerrorinfo/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qerrorinfo + SOURCES + tst_qerrorinfo.cpp + LIBRARIES + Qt::Gui + Qt::MultimediaPrivate +) diff --git a/tests/auto/unit/multimedia/qerrorinfo/tst_qerrorinfo.cpp b/tests/auto/unit/multimedia/qerrorinfo/tst_qerrorinfo.cpp new file mode 100644 index 000000000..0d266705d --- /dev/null +++ b/tests/auto/unit/multimedia/qerrorinfo/tst_qerrorinfo.cpp @@ -0,0 +1,117 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +// TESTED_COMPONENT=src/multimedia + +#include <QtTest/QtTest> +#include <QSignalSpy> +#include <private/qerrorinfo_p.h> + +QT_USE_NAMESPACE + +enum class TestError { ErrorA, ErrorB, NoError }; + +using TestErrorInfo = QErrorInfo<TestError>; + +class TestNotifier : public QObject +{ + Q_OBJECT +public: +signals: + void errorOccurred(TestError error, QString errorDescription); + void errorChanged(); +}; + +class tst_QErrorInfo : public QObject +{ + Q_OBJECT + +private slots: + void defaultConstructor_setsNoError(); + void constructor_setsPassedError(); + + void setAndNotify_setsErrorAndNotifes_data(); + void setAndNotify_setsErrorAndNotifes(); +}; + +void tst_QErrorInfo::defaultConstructor_setsNoError() +{ + TestErrorInfo errorInfo; + QCOMPARE(errorInfo.code(), TestError::NoError); + QCOMPARE(errorInfo.description(), QString()); +} + +void tst_QErrorInfo::constructor_setsPassedError() +{ + TestErrorInfo errorInfo(TestError::ErrorB, "test error"); + QCOMPARE(errorInfo.code(), TestError::ErrorB); + QCOMPARE(errorInfo.description(), "test error"); +} + +void tst_QErrorInfo::setAndNotify_setsErrorAndNotifes_data() +{ + QTest::addColumn<TestError>("initialError"); + QTest::addColumn<QString>("initialErrorDescription"); + QTest::addColumn<TestError>("error"); + QTest::addColumn<QString>("errorDescription"); + QTest::addColumn<bool>("errorChangedEmitted"); + QTest::addColumn<bool>("errorOccurredEmitted"); + + QTest::newRow("No error -> No error") + << TestError::NoError << QString() << TestError::NoError << QString() << false << false; + QTest::newRow("No error -> An error with empty string") + << TestError::NoError << QString() << TestError::ErrorA << QString() << true << true; + QTest::newRow("No error -> An error with non-empty string") + << TestError::NoError << QString() << TestError::ErrorB << QStringLiteral("error") << true + << true; + QTest::newRow("An error with empty string -> No error") + << TestError::ErrorA << QString() << TestError::NoError << QString() << true << false; + QTest::newRow("An error with non-empty string -> No Error") + << TestError::ErrorA << QStringLiteral("error") << TestError::NoError << QString() << true + << false; + QTest::newRow("An error -> Another error") + << TestError::ErrorA << QStringLiteral("error A") << TestError::ErrorB << QStringLiteral("error B") + << true << true; + QTest::newRow("An error -> Another error with empty string") + << TestError::ErrorA << QStringLiteral("error A") << TestError::ErrorB << QString() << true + << true; + QTest::newRow("An error -> The same error with changed string") + << TestError::ErrorA << QStringLiteral("error") << TestError::ErrorA + << QStringLiteral("another error") << true << true; +} + +void tst_QErrorInfo::setAndNotify_setsErrorAndNotifes() +{ + QFETCH(TestError, initialError); + QFETCH(QString, initialErrorDescription); + QFETCH(TestError, error); + QFETCH(QString, errorDescription); + QFETCH(bool, errorChangedEmitted); + QFETCH(bool, errorOccurredEmitted); + + TestErrorInfo errorInfo(initialError, initialErrorDescription); + + TestNotifier notifier; + QSignalSpy errorOccurredSpy(¬ifier, &TestNotifier::errorOccurred); + QSignalSpy errorChangedSpy(¬ifier, &TestNotifier::errorChanged); + + errorInfo.setAndNotify(error, errorDescription, notifier); + + QCOMPARE(errorInfo.code(), error); + QCOMPARE(errorInfo.description(), errorDescription); + + QList<QList<QVariant>> expectedErrorChanged; + if (errorChangedEmitted) + expectedErrorChanged.push_back({}); + + QList<QList<QVariant>> expectedErrorOccured; + if (errorOccurredEmitted) + expectedErrorOccured.push_back({ QVariant::fromValue(error), errorDescription }); + + QCOMPARE(errorOccurredSpy, expectedErrorOccured); + QCOMPARE(errorChangedSpy, expectedErrorChanged); +} + +QTEST_GUILESS_MAIN(tst_QErrorInfo) + +#include "tst_qerrorinfo.moc" diff --git a/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt b/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt index 11a2c79bc..908154cf9 100644 --- a/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qimagecapture/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qimagecapture.pro. ##################################################################### @@ -9,9 +12,9 @@ qt_internal_add_test(tst_qimagecapture tst_qimagecapture.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp b/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp index b81f989db..3267b6f40 100644 --- a/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp +++ b/tests/auto/unit/multimedia/qimagecapture/tst_qimagecapture.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QDebug> @@ -36,20 +11,16 @@ #include <qmediacapturesession.h> #include "qmockmediacapturesession.h" -#include "qmockintegration_p.h" +#include "qmockintegration.h" QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class tst_QImageCapture: public QObject { Q_OBJECT -public slots: - void initTestCase(); - void init(); - void cleanup(); - void cleanupTestCase(); - private slots: void constructor(); void isAvailable(); @@ -63,29 +34,8 @@ private slots: void imageExposed(); void imageSaved(); void readyForCaptureChanged(); - -private: - QMockIntegration *mockIntegration; }; -void tst_QImageCapture::initTestCase() -{ - mockIntegration = new QMockIntegration; -} - -void tst_QImageCapture::init() -{ -} - -void tst_QImageCapture::cleanup() -{ -} - -void tst_QImageCapture::cleanupTestCase() -{ - delete mockIntegration; -} - void tst_QImageCapture::constructor() { QMediaCaptureSession session; @@ -199,7 +149,7 @@ void tst_QImageCapture::errors() session.setImageCapture(&imageCapture); QVERIFY(imageCapture.isAvailable() == true); - imageCapture.captureToFile(QString::fromLatin1("/dev/null")); + imageCapture.captureToFile(QStringLiteral("/dev/null")); QCOMPARE(imageCapture.error(), QImageCapture::NotReadyError); QVERIFY(!imageCapture.errorString().isEmpty()); } @@ -228,10 +178,10 @@ void tst_QImageCapture::error() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy spy(&imageCapture, SIGNAL(errorOccurred(int,QImageCapture::Error,QString))); + QSignalSpy spy(&imageCapture, &QImageCapture::errorOccurred); imageCapture.captureToFile(); QTest::qWait(30); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); QVERIFY(qvariant_cast<int>(spy.at(0).at(0)) == -1); QVERIFY(qvariant_cast<QImageCapture::Error>(spy.at(0).at(1)) == QImageCapture::NotReadyError); QVERIFY(qvariant_cast<QString>(spy.at(0).at(2)) == "Could not capture in stopped state"); @@ -246,14 +196,14 @@ void tst_QImageCapture::imageCaptured() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy spy(&imageCapture, SIGNAL(imageCaptured(int,QImage))); + QSignalSpy spy(&imageCapture, &QImageCapture::imageCaptured); QVERIFY(imageCapture.isAvailable() == true); QVERIFY(imageCapture.isReadyForCapture() == false); camera.start(); imageCapture.captureToFile(); QTRY_VERIFY(imageCapture.isReadyForCapture()); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); QVERIFY(qvariant_cast<int>(spy.at(0).at(0)) > 0); QImage image = qvariant_cast<QImage>(spy.at(0).at(1)); QVERIFY(image.isNull() == true); @@ -269,14 +219,14 @@ void tst_QImageCapture::imageExposed() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy spy(&imageCapture, SIGNAL(imageExposed(int))); + QSignalSpy spy(&imageCapture, &QImageCapture::imageExposed); QVERIFY(imageCapture.isAvailable() == true); QVERIFY(imageCapture.isReadyForCapture() == false); camera.start(); imageCapture.captureToFile(); QTRY_VERIFY(imageCapture.isReadyForCapture()); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); QVERIFY(qvariant_cast<int>(spy.at(0).at(0)) > 0); spy.clear(); camera.stop(); @@ -290,14 +240,14 @@ void tst_QImageCapture::imageSaved() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy spy(&imageCapture, SIGNAL(imageSaved(int,QString))); + QSignalSpy spy(&imageCapture, &QImageCapture::imageSaved); QVERIFY(imageCapture.isAvailable() == true); QVERIFY(imageCapture.isReadyForCapture() == false); camera.start(); - imageCapture.captureToFile(QString::fromLatin1("/usr/share")); + imageCapture.captureToFile(QStringLiteral("/usr/share")); QTRY_VERIFY(imageCapture.isReadyForCapture()); - QVERIFY(spy.count() == 1); + QVERIFY(spy.size() == 1); QVERIFY(qvariant_cast<int>(spy.at(0).at(0)) > 0); QVERIFY(qvariant_cast<QString>(spy.at(0).at(1)) == "/usr/share"); spy.clear(); @@ -312,17 +262,17 @@ void tst_QImageCapture::readyForCaptureChanged() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy spy(&imageCapture, SIGNAL(readyForCaptureChanged(bool))); + QSignalSpy spy(&imageCapture, &QImageCapture::readyForCaptureChanged); QVERIFY(imageCapture.isReadyForCapture() == false); imageCapture.captureToFile(); QTest::qWait(100); - QVERIFY(spy.count() == 0); + QVERIFY(spy.size() == 0); QVERIFY2(!imageCapture.errorString().isEmpty(),"Could not capture in stopped state" ); camera.start(); QTest::qWait(100); imageCapture.captureToFile(); QTest::qWait(100); - QVERIFY(spy.count() == 2); + QVERIFY(spy.size() == 2); QVERIFY(spy.at(0).at(0).toBool() == false); QVERIFY(spy.at(1).at(0).toBool() == true); camera.stop(); diff --git a/tests/auto/unit/multimedia/qmaybe/CMakeLists.txt b/tests/auto/unit/multimedia/qmaybe/CMakeLists.txt new file mode 100644 index 000000000..a9053e87c --- /dev/null +++ b/tests/auto/unit/multimedia/qmaybe/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qmaybe + SOURCES + tst_qmaybe.cpp + LIBRARIES + Qt::MultimediaPrivate +) diff --git a/tests/auto/unit/multimedia/qmaybe/tst_qmaybe.cpp b/tests/auto/unit/multimedia/qmaybe/tst_qmaybe.cpp new file mode 100644 index 000000000..fbf21a26d --- /dev/null +++ b/tests/auto/unit/multimedia/qmaybe/tst_qmaybe.cpp @@ -0,0 +1,124 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> +#include <private/qmaybe_p.h> +#include <QtCore/private/quniquehandle_p.h> +#ifdef Q_OS_WINDOWS +#include <private/qcomptr_p.h> +#include <private/qcomobject_p.h> +#endif + +QT_USE_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + +// Helpers used to verify interop with QUniqueHandle and ComPtr which +// overloads operator& +struct DummyHandleTraits +{ + using Type = int; + static Type invalidValue() { return -1; } + static bool close(Type /*handle*/) { return true; } +}; + +using DummyHandle = QUniqueHandle<DummyHandleTraits>; + +#ifdef Q_OS_WINDOWS +struct DummyComObject : QComObject<IUnknown> +{ +}; +#endif + +} // namespace + +//clang-format off + +class tst_QMaybe : public QObject +{ + Q_OBJECT + +private slots: + void operatorBool_returnsFalse_onlyWhenErrorSet() + { + { + const QMaybe<QString, int> error{ -1 }; // TOOD: Is it safe to deduce expected/unexpected only based on type? + QVERIFY(!static_cast<bool>(error)); + } + + { + const QMaybe<QString, int> success{ "It worked!"_L1 }; + QVERIFY(static_cast<bool>(success)); + } + } + + void value_returnsReferenceToValue_whenValueSet() + { + { + QMaybe mutableVal{ 1 }; + mutableVal.value() = 2; + QCOMPARE_EQ(*mutableVal, 2); // value() must have returned a mutable reference + } + + { + const QMaybe immutableVal{ 2 }; + QCOMPARE_EQ(*std::addressof(immutableVal.value()), 2); // value() must have returned a reference + static_assert(std::is_const_v<std::remove_reference_t<decltype(immutableVal.value())>>); // And it is const + } + } + + void dereferenceOperator_returnsPointerToValue_whenValueTypeOverloadsAddressOfOperator() + { + { + QMaybe<DummyHandle, int> mutableValue{ DummyHandle{ 1 } }; + QCOMPARE_EQ(mutableValue->get(), 1); + QVERIFY(mutableValue->isValid()); // We did not accidentally call operator& that resets + // QUniqueHandle + } + + { + const QMaybe<DummyHandle, int> immutableValue{ DummyHandle{ 2 } }; + QCOMPARE_EQ(immutableValue->get(), 2); + QVERIFY(immutableValue->isValid()); // We did not accidentally call operator& that + // resets QUniqueHandle + } + } + +#ifdef Q_OS_WINDOWS + void dereferenceOperator_returnsPointerToValue_whenValueIsComPtr() + { + // Similar test as with QUniqueHandle, but with ComPtr that is used + // frequently on Windows and may behave slightly differently + + { + QMaybe<ComPtr<DummyComObject>, HRESULT> mutableObject{ + makeComObject<DummyComObject>() + }; + QCOMPARE_NE(mutableObject->Get(), nullptr); + + const ComPtr<IUnknown> unknownFromMutable = mutableObject.value(); + QVERIFY(unknownFromMutable); // We did not accidentally call operator& that resets + // QUniqueHandle + } + + { + QMaybe<ComPtr<DummyComObject>, HRESULT> immutableObject{ + makeComObject<DummyComObject>() + }; + QCOMPARE_NE(immutableObject->Get(), nullptr); + + const ComPtr<IUnknown> unknownFromImmutable = immutableObject.value(); + QVERIFY(unknownFromImmutable); // We did not accidentally call operator& that resets + // QUniqueHandle + } + } +#endif +}; + +QTEST_APPLESS_MAIN(tst_QMaybe) + +#include "tst_qmaybe.moc" + +//clang-format on diff --git a/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt new file mode 100644 index 000000000..209be5883 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediacapture_gstreamer/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qmediacapture_gstreamer Test: +##################################################################### + +qt_internal_add_test(tst_qmediacapture_gstreamer + SOURCES + tst_qmediacapture_gstreamer.cpp + ../../../shared/qscopedenvironmentvariable.h + INCLUDE_DIRECTORIES + ../../../shared + LIBRARIES + Qt::Multimedia + Qt::MultimediaPrivate + Qt::QGstreamerMediaPluginPrivate +) diff --git a/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp b/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp new file mode 100644 index 000000000..7fe6a06ac --- /dev/null +++ b/tests/auto/unit/multimedia/qmediacapture_gstreamer/tst_qmediacapture_gstreamer.cpp @@ -0,0 +1,212 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> +#include <QtMultimedia/QAudioDevice> +#include <QtMultimedia/QAudioInput> +#include <QtMultimedia/QAudioOutput> +#include <QtMultimedia/QCamera> +#include <QtMultimedia/QMediaCaptureSession> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> +#include <QtMultimedia/private/qplatformmediacapture_p.h> +#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h> + +#include <qscopedenvironmentvariable.h> + +#include <memory> + +// NOLINTBEGIN(readability-convert-member-functions-to-static) + +QT_USE_NAMESPACE + +using namespace Qt::Literals; + +class tst_QMediaCaptureGStreamer : public QObject +{ + Q_OBJECT + +public: + tst_QMediaCaptureGStreamer(); + +public slots: + void init(); + void cleanup(); + +private slots: + void mediaIntegration_hasPlatformSpecificInterface(); + void constructor_preparesGstPipeline(); + void audioInput_makeCustomGStreamerAudioInput_fromPipelineDescription(); + void audioOutput_makeCustomGStreamerAudioOutput_fromPipelineDescription(); + + void makeCustomGStreamerCamera_fromPipelineDescription(); + void makeCustomGStreamerCamera_fromPipelineDescription_multipleItems(); + void makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement(); + +private: + std::unique_ptr<QMediaCaptureSession> session; + + QGStreamerPlatformSpecificInterface *gstInterface() + { + return QGStreamerPlatformSpecificInterface::instance(); + } + + GstPipeline *getGstPipeline() + { + auto *iface = QGStreamerPlatformSpecificInterface::instance(); + return iface ? iface->gstPipeline(session.get()) : nullptr; + } + + QGstPipeline getPipeline() + { + return QGstPipeline{ + getGstPipeline(), + QGstPipeline::NeedsRef, + }; + } + + void dumpGraph(const char *fileNamePrefix) + { + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(getGstPipeline()), + GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_VERBOSE), + fileNamePrefix); + } +}; + +tst_QMediaCaptureGStreamer::tst_QMediaCaptureGStreamer() +{ + qputenv("QT_MEDIA_BACKEND", "gstreamer"); +} + +void tst_QMediaCaptureGStreamer::init() +{ + session = std::make_unique<QMediaCaptureSession>(); +} + +void tst_QMediaCaptureGStreamer::cleanup() +{ + session.reset(); +} + +void tst_QMediaCaptureGStreamer::mediaIntegration_hasPlatformSpecificInterface() +{ + QVERIFY(QGStreamerPlatformSpecificInterface::instance()); +} + +void tst_QMediaCaptureGStreamer::constructor_preparesGstPipeline() +{ + auto *rawPipeline = getGstPipeline(); + QVERIFY(rawPipeline); + + QGstPipeline pipeline{ + rawPipeline, + QGstPipeline::NeedsRef, + }; + QVERIFY(pipeline); + + dumpGraph("constructor_preparesGstPipeline"); +} + +void tst_QMediaCaptureGStreamer::audioInput_makeCustomGStreamerAudioInput_fromPipelineDescription() +{ + auto pipelineString = + "audiotestsrc wave=2 freq=200 name=myOscillator ! identity name=myConverter"_ba; + + QAudioInput input{ + gstInterface()->makeCustomGStreamerAudioInput(pipelineString), + }; + + session->setAudioInput(&input); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + pipeline.finishStateChange(); + + QVERIFY(pipeline.findByName("myOscillator")); + QVERIFY(pipeline.findByName("myConverter")); + + dumpGraph("audioInput_customAudioDevice"); +} + +void tst_QMediaCaptureGStreamer:: + audioOutput_makeCustomGStreamerAudioOutput_fromPipelineDescription() +{ + auto pipelineStringInput = + "audiotestsrc wave=2 freq=200 name=myOscillator ! identity name=myConverter"_ba; + QAudioInput input{ + gstInterface()->makeCustomGStreamerAudioInput(pipelineStringInput), + }; + session->setAudioInput(&input); + + auto pipelineStringOutput = "identity name=myConverter ! fakesink name=mySink"_ba; + QAudioOutput output{ + gstInterface()->makeCustomGStreamerAudioOutput(pipelineStringOutput), + }; + session->setAudioOutput(&output); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + pipeline.finishStateChange(); + + QVERIFY(pipeline.findByName("mySink")); + QVERIFY(pipeline.findByName("myConverter")); + + dumpGraph("audioOutput_customAudioDevice"); +} + +void tst_QMediaCaptureGStreamer::makeCustomGStreamerCamera_fromPipelineDescription() +{ + auto pipelineString = "videotestsrc name=mySrc"_ba; + QCamera *cam = + gstInterface()->makeCustomGStreamerCamera(pipelineString, /*parent=*/session.get()); + + session->setCamera(cam); + cam->start(); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + QVERIFY(pipeline.findByName("mySrc")); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription"); +} + +void tst_QMediaCaptureGStreamer::makeCustomGStreamerCamera_fromPipelineDescription_multipleItems() +{ + auto pipelineString = "videotestsrc name=mySrc ! gamma gamma=2.0 name=myFilter"_ba; + QCamera *cam = + gstInterface()->makeCustomGStreamerCamera(pipelineString, /*parent=*/session.get()); + + session->setCamera(cam); + cam->start(); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + QVERIFY(pipeline.findByName("mySrc")); + QVERIFY(pipeline.findByName("myFilter")); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription_multipleItems"); +} + +void tst_QMediaCaptureGStreamer:: + makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement() +{ + QGstElement element = QGstElement::createFromPipelineDescription("videotestsrc"); + gst_element_set_name(element.element(), "mySrc"); + + QCamera *cam = + gstInterface()->makeCustomGStreamerCamera(element.element(), /*parent=*/session.get()); + + session->setCamera(cam); + cam->start(); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + QCOMPARE(pipeline.findByName("mySrc"), element); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement"); + + element.set("foreground-color", 0xff0000); + dumpGraph("makeCustomGStreamerCamera_fromPipelineDescription_userProvidedGstElement2"); +} + +QTEST_GUILESS_MAIN(tst_QMediaCaptureGStreamer) + +#include "tst_qmediacapture_gstreamer.moc" diff --git a/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt b/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt new file mode 100644 index 000000000..e8360f8b5 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediadevices/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qmediadevices + SOURCES + tst_qmediadevices.cpp + INCLUDE_DIRECTORIES + ../../mockbackend + LIBRARIES + Qt::Gui + Qt::MultimediaPrivate + MockMultimediaPlugin +) diff --git a/tests/auto/unit/multimedia/qmediadevices/tst_qmediadevices.cpp b/tests/auto/unit/multimedia/qmediadevices/tst_qmediadevices.cpp new file mode 100644 index 000000000..9767c0e73 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediadevices/tst_qmediadevices.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> +#include <QDebug> + +#include <qmediadevices.h> + +#include "qmockintegration.h" + +QT_USE_NAMESPACE + +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + +class tst_QMediaDevices : public QObject +{ + Q_OBJECT + +private slots: + void videoInputsChangedEmitted_whenCamerasChanged(); + void onlyVideoInputsChangedEmitted_when2MediaDevicesCreated_andCamerasChanged(); +}; + +void tst_QMediaDevices::videoInputsChangedEmitted_whenCamerasChanged() +{ + QMediaDevices mediaDevices; + QSignalSpy videoInputsSpy(&mediaDevices, &QMediaDevices::videoInputsChanged); + + QCOMPARE(videoInputsSpy.size(), 0); + + QMockIntegration::instance()->addNewCamera(); + QTRY_COMPARE(videoInputsSpy.size(), 1); + + QMockIntegration::instance()->addNewCamera(); + QCOMPARE(videoInputsSpy.size(), 2); +} + +void tst_QMediaDevices::onlyVideoInputsChangedEmitted_when2MediaDevicesCreated_andCamerasChanged() +{ + QMediaDevices mediaDevicesA; + QMediaDevices mediaDevicesB; + + QSignalSpy videoInputsSpyA(&mediaDevicesA, &QMediaDevices::videoInputsChanged); + QSignalSpy videoInputsSpyB(&mediaDevicesB, &QMediaDevices::videoInputsChanged); + QSignalSpy audioInputsSpy(&mediaDevicesA, &QMediaDevices::audioInputsChanged); + QSignalSpy audioOutputsSpy(&mediaDevicesA, &QMediaDevices::audioOutputsChanged); + + QMockIntegration::instance()->addNewCamera(); + QCOMPARE(videoInputsSpyA.size(), 1); + QCOMPARE(videoInputsSpyB.size(), 1); + + QCOMPARE(audioInputsSpy.size(), 0); + QCOMPARE(audioOutputsSpy.size(), 0); +} + +QTEST_MAIN(tst_QMediaDevices) + +#include "tst_qmediadevices.moc" diff --git a/tests/auto/unit/multimedia/qmediaformat/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaformat/CMakeLists.txt index 23c4ad18c..4ad36e7bb 100644 --- a/tests/auto/unit/multimedia/qmediaformat/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediaformat/CMakeLists.txt @@ -1,7 +1,10 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + qt_internal_add_test(tst_qmediaformat SOURCES tst_qmediaformat.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp b/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp index b3d45e727..9f1345db6 100644 --- a/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.cpp +++ b/tests/auto/unit/multimedia/qmediaformat/tst_qmediaformat.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 <QtTest/QtTest> #include <QDebug> diff --git a/tests/auto/unit/multimedia/qmediametadata/CMakeLists.txt b/tests/auto/unit/multimedia/qmediametadata/CMakeLists.txt new file mode 100644 index 000000000..7b7b8f174 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediametadata/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qmediametadata Test: +##################################################################### + +qt_internal_add_test(tst_qmediametadata + SOURCES + tst_qmediametadata.cpp + LIBRARIES + Qt::MultimediaPrivate +) diff --git a/tests/auto/unit/multimedia/qmediametadata/tst_qmediametadata.cpp b/tests/auto/unit/multimedia/qmediametadata/tst_qmediametadata.cpp new file mode 100644 index 000000000..a3763399d --- /dev/null +++ b/tests/auto/unit/multimedia/qmediametadata/tst_qmediametadata.cpp @@ -0,0 +1,96 @@ +// 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/QString> + +#include <QtMultimedia/qmediametadata.h> + +QT_USE_NAMESPACE + +using namespace Qt::Literals; + +class tst_QMediaMetaData : public QObject +{ + Q_OBJECT + +private slots: + void insertAndRemove(); + void qdebug_empty(); + void qdebug_printContent(); +}; + +void tst_QMediaMetaData::insertAndRemove() +{ + QMediaMetaData dut; + QVERIFY(dut.isEmpty()); + + // fill + dut.insert(QMediaMetaData::Author, "yada"); + dut.insert(QMediaMetaData::Title, "title"); + QVERIFY(!dut.isEmpty()); + + // validate + { + auto range = dut.asKeyValueRange(); + QCOMPARE_EQ(std::distance(range.begin(), range.end()), 2u); + + QSet expectedKeys{ + QMediaMetaData::Author, + QMediaMetaData::Title, + }; + + QList keyList = dut.keys(); + QSet<QMediaMetaData::Key> keys{ keyList.begin(), keyList.end() }; + + QCOMPARE_EQ(keys, expectedKeys); + + QCOMPARE(dut.value(QMediaMetaData::Author), u"yada"_s); + QCOMPARE(dut.stringValue(QMediaMetaData::Author), u"yada"_s); + }; + + // remove missing key + QMediaMetaData reference = dut; + dut.remove(QMediaMetaData::AlbumArtist); + QCOMPARE_EQ(dut, reference); + + // clear + dut.clear(); + QVERIFY(dut.isEmpty()); +} + +void tst_QMediaMetaData::qdebug_empty() +{ + QMediaMetaData dut; + + QString str; + QDebug dbg(&str); + dbg << dut; + + auto expected = u"QMediaMetaData{} "; + + QCOMPARE_EQ(str, expected); +} + +void tst_QMediaMetaData::qdebug_printContent() +{ + QMediaMetaData dut; + dut.insert(QMediaMetaData::Author, "yada"); + dut.insert(QMediaMetaData::Title, "title"); + + QString str; + QDebug dbg(&str); + dbg << dut; + + auto expected = u"QMediaMetaData{QMediaMetaData::Title: QVariant(QString, \"title\"), " + u"QMediaMetaData::Author: QVariant(QString, \"yada\")} "; + auto expected2 = u"QMediaMetaData{QMediaMetaData::Author: QVariant(QString, \"yada\"), " + u"QMediaMetaData::Title: QVariant(QString, \"title\")} "; + + QVERIFY(str == expected || str == expected2); +} + +QTEST_GUILESS_MAIN(tst_QMediaMetaData) +#include "tst_qmediametadata.moc" diff --git a/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt index cc9e39503..4d3f2f865 100644 --- a/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediaplayer/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmediaplayer.pro. ##################################################################### @@ -9,12 +12,12 @@ qt_internal_add_test(tst_qmediaplayer tst_qmediaplayer.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate Qt::Network - QtMultimediaMockBackend + MockMultimediaPlugin ) # Resources: diff --git a/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp b/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp index bf48b7f9a..3fb77ca2d 100644 --- a/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp +++ b/tests/auto/unit/multimedia/qmediaplayer/tst_qmediaplayer.cpp @@ -1,32 +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$ -** -****************************************************************************/ - -//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> @@ -38,7 +11,7 @@ #include <private/qplatformmediaplayer_p.h> #include <qobject.h> -#include "qmockintegration_p.h" +#include "qmockintegration.h" #include "qmockmediaplayer.h" #include "qmockaudiooutput.h" #include "qvideosink.h" @@ -46,6 +19,8 @@ QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class AutoConnection { public: @@ -123,7 +98,6 @@ private slots: private: void setupCommonTestData(); - QMockIntegration *mockIntegration; QMockMediaPlayer *mockPlayer; QAudioOutput *audioOutput = nullptr; QMediaPlayer *player; @@ -200,7 +174,7 @@ void tst_QMediaPlayer::setupCommonTestData() qreal(1) << QMediaPlayer::NoError << QString(); QTest::newRow("valid+error") << true << QMediaPlayer::StoppedState << QMediaPlayer::InvalidMedia << QUrl(QUrl("http://example.com/stream")) << qint64(0) << qint64(0) << false << 50 << false << false << 0 << - qreal(0) << QMediaPlayer::ResourceError << QString("Resource unavailable"); + qreal(0) << QMediaPlayer::ResourceError << QStringLiteral("Resource unavailable"); } void tst_QMediaPlayer::initTestCase() @@ -213,9 +187,8 @@ void tst_QMediaPlayer::cleanupTestCase() void tst_QMediaPlayer::init() { - mockIntegration = new QMockIntegration; player = new QMediaPlayer; - mockPlayer = mockIntegration->lastPlayer(); + mockPlayer = QMockIntegration::instance()->lastPlayer(); Q_ASSERT(mockPlayer); audioOutput = new QAudioOutput; player->setAudioOutput(audioOutput); @@ -225,7 +198,6 @@ void tst_QMediaPlayer::init() void tst_QMediaPlayer::cleanup() { delete player; - delete mockIntegration; } void tst_QMediaPlayer::testValid() @@ -322,36 +294,44 @@ void tst_QMediaPlayer::testPosition() QVERIFY(player->duration() == duration); if (seekable) { - { QSignalSpy spy(player, SIGNAL(positionChanged(qint64))); - player->setPosition(position); - QCOMPARE(player->position(), position); - QCOMPARE(spy.count(), 0); } + { + QSignalSpy spy(player, &QMediaPlayer::positionChanged); + player->setPosition(position); + QCOMPARE(player->position(), position); + QCOMPARE(spy.size(), 0); + } mockPlayer->setPosition(position); - { QSignalSpy spy(player, SIGNAL(positionChanged(qint64))); - player->setPosition(0); - QCOMPARE(player->position(), qint64(0)); - QCOMPARE(spy.count(), position == 0 ? 0 : 1); } + { + QSignalSpy spy(player, &QMediaPlayer::positionChanged); + player->setPosition(0); + QCOMPARE(player->position(), qint64(0)); + QCOMPARE(spy.size(), position == 0 ? 0 : 1); + } mockPlayer->setPosition(position); - { QSignalSpy spy(player, SIGNAL(positionChanged(qint64))); - player->setPosition(duration); - QCOMPARE(player->position(), duration); - QCOMPARE(spy.count(), position == duration ? 0 : 1); } + { + QSignalSpy spy(player, &QMediaPlayer::positionChanged); + player->setPosition(duration); + QCOMPARE(player->position(), duration); + QCOMPARE(spy.size(), position == duration ? 0 : 1); + } mockPlayer->setPosition(position); - { QSignalSpy spy(player, SIGNAL(positionChanged(qint64))); - player->setPosition(-1); - QCOMPARE(player->position(), qint64(0)); - QCOMPARE(spy.count(), position == 0 ? 0 : 1); } + { + QSignalSpy spy(player, &QMediaPlayer::positionChanged); + player->setPosition(-1); + QCOMPARE(player->position(), qint64(0)); + QCOMPARE(spy.size(), position == 0 ? 0 : 1); + } } else { - QSignalSpy spy(player, SIGNAL(positionChanged(qint64))); + QSignalSpy spy(player, &QMediaPlayer::positionChanged); player->setPosition(position); QCOMPARE(player->position(), position); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); } } @@ -370,25 +350,33 @@ void tst_QMediaPlayer::testVolume() QVERIFY(audioOutput->volume() == vol); if (valid) { - { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float))); - audioOutput->setVolume(.1f); - QCOMPARE(audioOutput->volume(), .1f); - QCOMPARE(spy.count(), 1); } - - { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float))); - audioOutput->setVolume(-1000.f); - QCOMPARE(audioOutput->volume(), 0.f); - QCOMPARE(spy.count(), 1); } - - { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float))); - audioOutput->setVolume(1.f); - QCOMPARE(audioOutput->volume(), 1.f); - QCOMPARE(spy.count(), 1); } - - { QSignalSpy spy(audioOutput, SIGNAL(volumeChanged(float))); - audioOutput->setVolume(1000.f); - QCOMPARE(audioOutput->volume(), 1.f); - QCOMPARE(spy.count(), 0); } + { + QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged); + audioOutput->setVolume(.1f); + QCOMPARE(audioOutput->volume(), .1f); + QCOMPARE(spy.size(), 1); + } + + { + QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged); + audioOutput->setVolume(-1000.f); + QCOMPARE(audioOutput->volume(), 0.f); + QCOMPARE(spy.size(), 1); + } + + { + QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged); + audioOutput->setVolume(1.f); + QCOMPARE(audioOutput->volume(), 1.f); + QCOMPARE(spy.size(), 1); + } + + { + QSignalSpy spy(audioOutput, &QAudioOutput::volumeChanged); + audioOutput->setVolume(1000.f); + QCOMPARE(audioOutput->volume(), 1.f); + QCOMPARE(spy.size(), 0); + } } } @@ -409,11 +397,11 @@ void tst_QMediaPlayer::testMuted() audioOutput->setVolume(vol); QVERIFY(audioOutput->isMuted() == muted); - QSignalSpy spy(audioOutput, SIGNAL(mutedChanged(bool))); + QSignalSpy spy(audioOutput, &QAudioOutput::mutedChanged); audioOutput->setMuted(!muted); QCOMPARE(audioOutput->isMuted(), !muted); QCOMPARE(audioOutput->volume(), vol); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); } } @@ -470,10 +458,10 @@ void tst_QMediaPlayer::testPlaybackRate() mockPlayer->setPlaybackRate(playbackRate); QVERIFY(player->playbackRate() == playbackRate); - QSignalSpy spy(player, SIGNAL(playbackRateChanged(qreal))); + QSignalSpy spy(player, &QMediaPlayer::playbackRateChanged); player->setPlaybackRate(playbackRate + 0.5f); QCOMPARE(player->playbackRate(), playbackRate + 0.5f); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); } } @@ -537,19 +525,25 @@ void tst_QMediaPlayer::testPlay() player->setSource(mediaContent); mockPlayer->setState(state); QCOMPARE(player->playbackState(), state); + QCOMPARE(player->isPlaying(), state == QMediaPlayer::PlayingState); QCOMPARE(player->source(), mediaContent); - QSignalSpy spy(player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); + QSignalSpy spy(player, &QMediaPlayer::playbackStateChanged); + QSignalSpy playingChanged(player, &QMediaPlayer::playingChanged); player->play(); if (!valid || mediaContent.isEmpty()) { QCOMPARE(player->playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(spy.count(), 0); + QVERIFY(!player->isPlaying()); + QCOMPARE(spy.size(), 0); + QVERIFY(playingChanged.empty()); } else { QCOMPARE(player->playbackState(), QMediaPlayer::PlayingState); - QCOMPARE(spy.count(), state == QMediaPlayer::PlayingState ? 0 : 1); + QVERIFY(player->isPlaying()); + QCOMPARE(spy.size(), state == QMediaPlayer::PlayingState ? 0 : 1); + QCOMPARE_EQ(playingChanged.size(), state == QMediaPlayer::PlayingState ? 0 : 1); } } @@ -568,19 +562,25 @@ void tst_QMediaPlayer::testPause() player->setSource(mediaContent); mockPlayer->setState(state); QVERIFY(player->playbackState() == state); + QCOMPARE(player->isPlaying(), state == QMediaPlayer::PlayingState); QVERIFY(player->source() == mediaContent); - QSignalSpy spy(player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); + QSignalSpy spy(player, &QMediaPlayer::playbackStateChanged); + QSignalSpy playingChanged(player, &QMediaPlayer::playingChanged); player->pause(); + QVERIFY(!player->isPlaying()); + if (!valid || mediaContent.isEmpty()) { QCOMPARE(player->playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); + QCOMPARE(playingChanged.size(), 0); } else { QCOMPARE(player->playbackState(), QMediaPlayer::PausedState); - QCOMPARE(spy.count(), state == QMediaPlayer::PausedState ? 0 : 1); + QCOMPARE(spy.size(), state == QMediaPlayer::PausedState ? 0 : 1); + QCOMPARE(playingChanged.size(), state == QMediaPlayer::PlayingState ? 1 : 0); } } @@ -597,19 +597,25 @@ void tst_QMediaPlayer::testStop() player->setSource(mediaContent); mockPlayer->setState(state); QVERIFY(player->playbackState() == state); + QCOMPARE(player->isPlaying(), state == QMediaPlayer::PlayingState); QVERIFY(player->source() == mediaContent); - QSignalSpy spy(player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState))); + QSignalSpy spy(player, &QMediaPlayer::playbackStateChanged); + QSignalSpy playingChanged(player, &QMediaPlayer::playingChanged); player->stop(); + QVERIFY(!player->isPlaying()); + if (mediaContent.isEmpty() || state == QMediaPlayer::StoppedState) { QCOMPARE(player->playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); + QCOMPARE(playingChanged.size(), 0); } else { QCOMPARE(player->playbackState(), QMediaPlayer::StoppedState); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); + QCOMPARE(playingChanged.size(), state == QMediaPlayer::PlayingState ? 1 : 0); } } @@ -626,65 +632,65 @@ void tst_QMediaPlayer::testMediaStatus() mockPlayer->setMediaStatus(QMediaPlayer::NoMedia); mockPlayer->setBufferStatus(bufferProgress); - QSignalSpy statusSpy(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); - QSignalSpy bufferSpy(player, SIGNAL(bufferProgressChanged(float))); + QSignalSpy statusSpy(player, &QMediaPlayer::mediaStatusChanged); + QSignalSpy bufferSpy(player, &QMediaPlayer::bufferProgressChanged); QCOMPARE(player->mediaStatus(), QMediaPlayer::NoMedia); mockPlayer->setMediaStatus(QMediaPlayer::LoadingMedia); QCOMPARE(player->mediaStatus(), QMediaPlayer::LoadingMedia); - QCOMPARE(statusSpy.count(), 1); + QCOMPARE(statusSpy.size(), 1); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), QMediaPlayer::LoadingMedia); mockPlayer->setMediaStatus(QMediaPlayer::LoadedMedia); QCOMPARE(player->mediaStatus(), QMediaPlayer::LoadedMedia); - QCOMPARE(statusSpy.count(), 2); + QCOMPARE(statusSpy.size(), 2); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), QMediaPlayer::LoadedMedia); // Verify the bufferProgressChanged() signal isn't being emitted. - QCOMPARE(bufferSpy.count(), 0); + QCOMPARE(bufferSpy.size(), 0); mockPlayer->setMediaStatus(QMediaPlayer::StalledMedia); QCOMPARE(player->mediaStatus(), QMediaPlayer::StalledMedia); - QCOMPARE(statusSpy.count(), 3); + QCOMPARE(statusSpy.size(), 3); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), QMediaPlayer::StalledMedia); // Verify the bufferProgressChanged() signal is being emitted. - QVERIFY(bufferSpy.count() > bufferSignals); + QVERIFY(bufferSpy.size() > bufferSignals); QCOMPARE(bufferSpy.last().value(0).toInt(), bufferProgress); - bufferSignals = bufferSpy.count(); + bufferSignals = bufferSpy.size(); mockPlayer->setMediaStatus(QMediaPlayer::BufferingMedia); QCOMPARE(player->mediaStatus(), QMediaPlayer::BufferingMedia); - QCOMPARE(statusSpy.count(), 4); + QCOMPARE(statusSpy.size(), 4); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), QMediaPlayer::BufferingMedia); // Verify the bufferProgressChanged() signal is being emitted. - QVERIFY(bufferSpy.count() > bufferSignals); + QVERIFY(bufferSpy.size() > bufferSignals); QCOMPARE(bufferSpy.last().value(0).toInt(), bufferProgress); - bufferSignals = bufferSpy.count(); + bufferSignals = bufferSpy.size(); mockPlayer->setMediaStatus(QMediaPlayer::BufferedMedia); QCOMPARE(player->mediaStatus(), QMediaPlayer::BufferedMedia); - QCOMPARE(statusSpy.count(), 5); + QCOMPARE(statusSpy.size(), 5); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), QMediaPlayer::BufferedMedia); // Verify the bufferProgressChanged() signal isn't being emitted. - QCOMPARE(bufferSpy.count(), bufferSignals); + QCOMPARE(bufferSpy.size(), bufferSignals); mockPlayer->setMediaStatus(QMediaPlayer::EndOfMedia); QCOMPARE(player->mediaStatus(), QMediaPlayer::EndOfMedia); - QCOMPARE(statusSpy.count(), 6); + QCOMPARE(statusSpy.size(), 6); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), QMediaPlayer::EndOfMedia); @@ -796,22 +802,22 @@ void tst_QMediaPlayer::testQrc() mockPlayer->setState(QMediaPlayer::PlayingState, QMediaPlayer::NoMedia); mockPlayer->setStreamPlaybackSupported(backendHasStream); - QSignalSpy mediaSpy(player, SIGNAL(sourceChanged(QUrl))); - QSignalSpy statusSpy(player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus))); - QSignalSpy errorSpy(player, SIGNAL(errorOccurred(QMediaPlayer::Error,const QString&))); + QSignalSpy mediaSpy(player, &QMediaPlayer::sourceChanged); + QSignalSpy statusSpy(player, &QMediaPlayer::mediaStatusChanged); + QSignalSpy errorSpy(player, &QMediaPlayer::errorOccurred); player->setSource(mediaContent); QTRY_COMPARE(player->mediaStatus(), status); - QCOMPARE(statusSpy.count(), 1); + QCOMPARE(statusSpy.size(), 1); QCOMPARE(qvariant_cast<QMediaPlayer::MediaStatus>(statusSpy.last().value(0)), status); QCOMPARE(player->source(), mediaContent); - QCOMPARE(mediaSpy.count(), 1); + QCOMPARE(mediaSpy.size(), 1); QCOMPARE(qvariant_cast<QUrl>(mediaSpy.last().value(0)), mediaContent); QCOMPARE(player->error(), error); - QCOMPARE(errorSpy.count(), errorCount); + QCOMPARE(errorSpy.size(), errorCount); if (errorCount > 0) { QCOMPARE(qvariant_cast<QMediaPlayer::Error>(errorSpy.last().value(0)), error); QVERIFY(!player->errorString().isEmpty()); diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt new file mode 100644 index 000000000..dd54afd25 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qmediaplayer_gstreamer Test: +##################################################################### + +qt_internal_add_test(tst_qmediaplayer_gstreamer + SOURCES + tst_qmediaplayer_gstreamer.cpp + tst_qmediaplayer_gstreamer.h + ../../../shared/qscopedenvironmentvariable.h + INCLUDE_DIRECTORIES + ../../../shared + LIBRARIES + Qt::MultimediaPrivate + Qt::QGstreamerMediaPluginPrivate +) + + +# Resources: +set(testdata_resource_files + "testdata/color_matrix.mp4" +) + +qt_internal_add_resource(tst_qmediaplayer_gstreamer "testdata" + PREFIX + "/" + FILES + ${testdata_resource_files} +) + diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/testdata/color_matrix.mp4 b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/testdata/color_matrix.mp4 Binary files differnew file mode 100644 index 000000000..a3661b9d2 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/testdata/color_matrix.mp4 diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp new file mode 100644 index 000000000..3bb0b626e --- /dev/null +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include "tst_qmediaplayer_gstreamer.h" + +#include <QtTest/QtTest> +#include <QtMultimedia/private/qmediaplayer_p.h> + +#include <qscopedenvironmentvariable.h> + +QT_USE_NAMESPACE + +using namespace Qt::Literals; + +QGStreamerPlatformSpecificInterface *tst_QMediaPlayerGStreamer::gstInterface() +{ + return dynamic_cast<QGStreamerPlatformSpecificInterface *>( + QPlatformMediaIntegration::instance()->platformSpecificInterface()); +} + +GstPipeline *tst_QMediaPlayerGStreamer::getGstPipeline() +{ + QGStreamerPlatformSpecificInterface *iface = gstInterface(); + return iface ? iface->gstPipeline(player.get()) : nullptr; +} + +QGstPipeline tst_QMediaPlayerGStreamer::getPipeline() +{ + return QGstPipeline{ + getGstPipeline(), + QGstPipeline::NeedsRef, + }; +} + +void tst_QMediaPlayerGStreamer::dumpGraph(const char *fileNamePrefix) +{ + GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(getGstPipeline()), + GstDebugGraphDetails(GST_DEBUG_GRAPH_SHOW_VERBOSE), fileNamePrefix); +} + +tst_QMediaPlayerGStreamer::tst_QMediaPlayerGStreamer() +{ + qputenv("QT_MEDIA_BACKEND", "gstreamer"); +} + +void tst_QMediaPlayerGStreamer::initTestCase() +{ + using namespace std::chrono_literals; + + QMediaPlayer player; + + QVideoSink sink; + player.setVideoSink(&sink); + player.setSource(QUrl("qrc:/testdata/color_matrix.mp4")); + + for (;;) { + QMediaPlayer::MediaStatus status = player.mediaStatus(); + switch (status) { + case QMediaPlayer::MediaStatus::InvalidMedia: { + mediaSupported = false; + return; + } + case QMediaPlayer::MediaStatus::NoMedia: + case QMediaPlayer::MediaStatus::StalledMedia: + case QMediaPlayer::MediaStatus::LoadingMedia: + QTest::qWait(20ms); + continue; + + default: { + mediaSupported = true; + return; + } + } + } +} + +void tst_QMediaPlayerGStreamer::init() +{ + player = std::make_unique<QMediaPlayer>(); +} + +void tst_QMediaPlayerGStreamer::cleanup() +{ + player.reset(); +} + +void tst_QMediaPlayerGStreamer::constructor_preparesGstPipeline() +{ + auto *rawPipeline = getGstPipeline(); + QVERIFY(rawPipeline); + + QGstPipeline pipeline{ + rawPipeline, + QGstPipeline::NeedsRef, + }; + QVERIFY(pipeline); + + QVERIFY(pipeline.findByName("videoInputSelector")); + QVERIFY(pipeline.findByName("audioInputSelector")); + QVERIFY(pipeline.findByName("subTitleInputSelector")); + + dumpGraph("constructor_preparesGstPipeline"); +} + +void tst_QMediaPlayerGStreamer::videoSink_constructor_overridesConversionElement() +{ + if (!mediaSupported) + QSKIP("Media playback not supported"); + + QScopedEnvironmentVariable convOverride{ + "QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT", + "identity name=myConverter", + }; + + QVideoSink sink; + player->setVideoSink(&sink); + player->setSource(QUrl("qrc:/testdata/color_matrix.mp4")); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + QTRY_VERIFY(pipeline.findByName("myConverter")); + + dumpGraph("videoSink_constructor_overridesConversionElement"); +} + +void tst_QMediaPlayerGStreamer:: + videoSink_constructor_overridesConversionElement_withMultipleElements() +{ + if (!mediaSupported) + QSKIP("Media playback not supported"); + + QScopedEnvironmentVariable convOverride{ + "QT_GSTREAMER_OVERRIDE_VIDEO_CONVERSION_ELEMENT", + "identity name=myConverter ! identity name=myConverter2", + }; + + QVideoSink sink; + player->setVideoSink(&sink); + player->setSource(QUrl("qrc:/testdata/color_matrix.mp4")); + + QGstPipeline pipeline = getPipeline(); + QTEST_ASSERT(pipeline); + + QTRY_VERIFY(pipeline.findByName("myConverter")); + QTRY_VERIFY(pipeline.findByName("myConverter2")); + + dumpGraph("videoSink_constructer_overridesConversionElement_withMultipleElements"); +} + +QTEST_GUILESS_MAIN(tst_QMediaPlayerGStreamer) + +#include "moc_tst_qmediaplayer_gstreamer.cpp" diff --git a/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.h b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.h new file mode 100644 index 000000000..08e958404 --- /dev/null +++ b/tests/auto/unit/multimedia/qmediaplayer_gstreamer/tst_qmediaplayer_gstreamer.h @@ -0,0 +1,46 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#ifndef TST_GMEDIAPLAYER_GSTREAMER_H +#define TST_GMEDIAPLAYER_GSTREAMER_H + +#include <QtCore/qtemporaryfile.h> +#include <QtCore/qstandardpaths.h> +#include <QtMultimedia/qmediaplayer.h> +#include <QtQGstreamerMediaPlugin/private/qgstpipeline_p.h> +#include <QtMultimedia/private/qgstreamer_platformspecificinterface_p.h> + +#include <memory> + +QT_USE_NAMESPACE + +class tst_QMediaPlayerGStreamer : public QObject +{ + Q_OBJECT + +public: + tst_QMediaPlayerGStreamer(); + +public slots: + void initTestCase(); + void init(); + void cleanup(); + +private slots: + void constructor_preparesGstPipeline(); + void videoSink_constructor_overridesConversionElement(); + void videoSink_constructor_overridesConversionElement_withMultipleElements(); + +private: + std::unique_ptr<QMediaPlayer> player; + + static QGStreamerPlatformSpecificInterface *gstInterface(); + + GstPipeline *getGstPipeline(); + QGstPipeline getPipeline(); + void dumpGraph(const char *fileNamePrefix); + + bool mediaSupported; +}; + +#endif // TST_GMEDIAPLAYER_GSTREAMER_H diff --git a/tests/auto/unit/multimedia/qmediaplaylist/CMakeLists.txt b/tests/auto/unit/multimedia/qmediaplaylist/CMakeLists.txt index 33d4d6d14..9300394b1 100644 --- a/tests/auto/unit/multimedia/qmediaplaylist/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediaplaylist/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmediaplaylist.pro. ##################################################################### @@ -13,7 +16,7 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qmediaplaylist SOURCES tst_qmediaplaylist.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate TESTDATA ${test_data} diff --git a/tests/auto/unit/multimedia/qmediaplaylist/tst_qmediaplaylist.cpp b/tests/auto/unit/multimedia/qmediaplaylist/tst_qmediaplaylist.cpp index 1c641f713..8ef882bbf 100644 --- a/tests/auto/unit/multimedia/qmediaplaylist/tst_qmediaplaylist.cpp +++ b/tests/auto/unit/multimedia/qmediaplaylist/tst_qmediaplaylist.cpp @@ -1,37 +1,10 @@ -/**************************************************************************** -** -** 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 "qmediaplaylist.h" -//TESTED_COMPONENT=src/multimedia - QT_USE_NAMESPACE class tst_QMediaPlaylist : public QObject diff --git a/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt b/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt index 68234d7ab..83e40012a 100644 --- a/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediarecorder/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from QMediaRecorder.pro. ##################################################################### @@ -9,9 +12,9 @@ qt_internal_add_test(tst_qmediarecorder tst_qmediarecorder.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp b/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp index 9808d065d..a11f39207 100644 --- a/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp +++ b/tests/auto/unit/multimedia/qmediarecorder/tst_qmediarecorder.cpp @@ -1,32 +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$ -** -****************************************************************************/ - -//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 <QDebug> @@ -35,8 +8,10 @@ #include "private/qguiapplication_p.h" #include <qmediarecorder.h> #include <qaudioformat.h> -#include <qmockintegration_p.h> +#include <qmockintegration.h> #include <qmediacapturesession.h> +#include <qscreencapture.h> +#include <qwindowcapture.h> #include "qguiapplication_platform.h" #include "qmockmediacapturesession.h" @@ -44,6 +19,8 @@ QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class tst_QMediaRecorder : public QObject { Q_OBJECT @@ -55,7 +32,7 @@ public slots: private slots: void testBasicSession(); void testNullControls(); - void testDeleteMediaSource(); + void testDeleteMediaCapture(); void testError(); void testSink(); void testRecord(); @@ -75,7 +52,6 @@ private slots: void testApplicationInative(); private: - QMockIntegration *mockIntegration = nullptr; QMediaCaptureSession *captureSession; QCamera *object = nullptr; QMockMediaCaptureSession *service = nullptr; @@ -85,13 +61,12 @@ private: void tst_QMediaRecorder::initTestCase() { - mockIntegration = new QMockIntegration; captureSession = new QMediaCaptureSession; object = new QCamera; encoder = new QMediaRecorder; captureSession->setCamera(object); captureSession->setRecorder(encoder); - service = mockIntegration->lastCaptureService(); + service = QMockIntegration::instance()->lastCaptureService(); mock = service->mockControl; } @@ -100,7 +75,6 @@ void tst_QMediaRecorder::cleanupTestCase() delete encoder; delete object; delete captureSession; - delete mockIntegration; } void tst_QMediaRecorder::testBasicSession() @@ -151,56 +125,80 @@ void tst_QMediaRecorder::testNullControls() QCOMPARE(recorder.mediaFormat().videoCodec(), QMediaFormat::VideoCodec::VP9); QCOMPARE(recorder.mediaFormat().fileFormat(), QMediaFormat::MPEG4); - QSignalSpy spy(&recorder, SIGNAL(recorderStateChanged(RecorderState))); + QSignalSpy spy(&recorder, &QMediaRecorder::recorderStateChanged); recorder.record(); QCOMPARE(recorder.recorderState(), QMediaRecorder::StoppedState); QCOMPARE(recorder.error(), QMediaRecorder::NoError); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); recorder.pause(); QCOMPARE(recorder.recorderState(), QMediaRecorder::StoppedState); QCOMPARE(recorder.error(), QMediaRecorder::NoError); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); recorder.stop(); QCOMPARE(recorder.recorderState(), QMediaRecorder::StoppedState); QCOMPARE(recorder.error(), QMediaRecorder::NoError); - QCOMPARE(spy.count(), 0); + QCOMPARE(spy.size(), 0); } -void tst_QMediaRecorder::testDeleteMediaSource() +void tst_QMediaRecorder::testDeleteMediaCapture() { QMediaCaptureSession session; - QCamera *camera = new QCamera; - QMediaRecorder *recorder = new QMediaRecorder; - session.setCamera(camera); - session.setRecorder(recorder); + QMediaRecorder recorder; + + session.setRecorder(&recorder); + + auto checkSourceDeleting = [&](auto setter, auto getter, auto signal) { + using Type = std::remove_pointer_t<decltype((session.*getter)())>; + + auto errorPrinter = qScopeGuard( + []() { qDebug() << QMetaType::fromType<Type>().name() << "deleting failed"; }); + + auto capture = std::make_unique<Type>(); + + (session.*setter)(capture.get()); + + QVERIFY((session.*getter)() == capture.get()); + + QSignalSpy spy(&session, signal); + capture.reset(); - QVERIFY(session.camera() == camera); - QVERIFY(recorder->isAvailable()); + QCOMPARE(spy.size(), 1); + QCOMPARE((session.*getter)(), nullptr); - delete camera; + QVERIFY(recorder.isAvailable()); - QVERIFY(session.camera() == nullptr); - QVERIFY(recorder->isAvailable()); + errorPrinter.dismiss(); + }; - delete recorder; + checkSourceDeleting(&QMediaCaptureSession::setImageCapture, + &QMediaCaptureSession::imageCapture, + &QMediaCaptureSession::imageCaptureChanged); + checkSourceDeleting(&QMediaCaptureSession::setCamera, &QMediaCaptureSession::camera, + &QMediaCaptureSession::cameraChanged); + checkSourceDeleting(&QMediaCaptureSession::setScreenCapture, + &QMediaCaptureSession::screenCapture, + &QMediaCaptureSession::screenCaptureChanged); + checkSourceDeleting(&QMediaCaptureSession::setWindowCapture, + &QMediaCaptureSession::windowCapture, + &QMediaCaptureSession::windowCaptureChanged); } void tst_QMediaRecorder::testError() { const QString errorString(QLatin1String("format error")); - QSignalSpy spy(encoder, SIGNAL(errorOccurred(Error, const QString&))); + QSignalSpy spy(encoder, &QMediaRecorder::errorOccurred); QCOMPARE(encoder->error(), QMediaRecorder::NoError); QCOMPARE(encoder->errorString(), QString()); - mock->error(QMediaRecorder::FormatError, errorString); + mock->updateError(QMediaRecorder::FormatError, errorString); QCOMPARE(encoder->error(), QMediaRecorder::FormatError); QCOMPARE(encoder->errorString(), errorString); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.last()[0].value<QMediaRecorder::Error>(), QMediaRecorder::FormatError); } @@ -209,14 +207,14 @@ void tst_QMediaRecorder::testSink() { encoder->setOutputLocation(QUrl("test.tmp")); QUrl s = encoder->outputLocation(); - QCOMPARE(s.toString(), QString("test.tmp")); + QCOMPARE(s.toString(), QStringLiteral("test.tmp")); QCOMPARE(encoder->actualLocation(), QUrl()); //the actual location is available after record encoder->record(); - QCOMPARE(encoder->actualLocation().toString(), QString("test.tmp")); + QCOMPARE(encoder->actualLocation().toString(), QStringLiteral("test.tmp")); encoder->stop(); - QCOMPARE(encoder->actualLocation().toString(), QString("test.tmp")); + QCOMPARE(encoder->actualLocation().toString(), QStringLiteral("test.tmp")); //setOutputLocation resets the actual location encoder->setOutputLocation(QUrl()); @@ -232,37 +230,37 @@ void tst_QMediaRecorder::testSink() void tst_QMediaRecorder::testRecord() { - QSignalSpy stateSignal(encoder,SIGNAL(recorderStateChanged(RecorderState))); - QSignalSpy progressSignal(encoder, SIGNAL(durationChanged(qint64))); + QSignalSpy stateSignal(encoder, &QMediaRecorder::recorderStateChanged); + QSignalSpy progressSignal(encoder, &QMediaRecorder::durationChanged); encoder->record(); QCOMPARE(encoder->recorderState(), QMediaRecorder::RecordingState); QCOMPARE(encoder->error(), QMediaRecorder::NoError); QCOMPARE(encoder->errorString(), QString()); - QCOMPARE(stateSignal.count(), 1); + QCOMPARE(stateSignal.size(), 1); QCOMPARE(stateSignal.last()[0].value<QMediaRecorder::RecorderState>(), QMediaRecorder::RecordingState); QTestEventLoop::instance().enterLoop(1); - QVERIFY(progressSignal.count() > 0); + QVERIFY(progressSignal.size() > 0); encoder->pause(); QCOMPARE(encoder->recorderState(), QMediaRecorder::PausedState); - QCOMPARE(stateSignal.count(), 2); + QCOMPARE(stateSignal.size(), 2); QTestEventLoop::instance().enterLoop(1); encoder->stop(); QCOMPARE(encoder->recorderState(), QMediaRecorder::StoppedState); - QCOMPARE(stateSignal.count(), 3); + QCOMPARE(stateSignal.size(), 3); QTestEventLoop::instance().enterLoop(1); mock->stop(); - QCOMPARE(stateSignal.count(), 3); + QCOMPARE(stateSignal.size(), 3); mock->reset(); } @@ -388,10 +386,10 @@ void tst_QMediaRecorder::metaData() QVERIFY(recorder.metaData().isEmpty()); QMediaMetaData data; - data.insert(QMediaMetaData::Author, QString::fromUtf8("John Doe")); + data.insert(QMediaMetaData::Author, QStringLiteral("John Doe")); recorder.setMetaData(data); - QCOMPARE(recorder.metaData().value(QMediaMetaData::Author).toString(), QString::fromUtf8("John Doe")); + QCOMPARE(recorder.metaData().value(QMediaMetaData::Author).toString(), QStringLiteral("John Doe")); } void tst_QMediaRecorder::testIsAvailable() @@ -415,15 +413,15 @@ void tst_QMediaRecorder::testEnum() { const QString errorString(QLatin1String("resource error")); - QSignalSpy spy(encoder, SIGNAL(errorOccurred(Error, const QString&))); + QSignalSpy spy(encoder, &QMediaRecorder::errorOccurred); QCOMPARE(encoder->error(), QMediaRecorder::NoError); QCOMPARE(encoder->errorString(), QString()); - emit mock->error(QMediaRecorder::ResourceError, errorString); + mock->updateError(QMediaRecorder::ResourceError, errorString); QCOMPARE(encoder->error(), QMediaRecorder::ResourceError); QCOMPARE(encoder->errorString(), errorString); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.last()[0].value<QMediaRecorder::Error>(), QMediaRecorder::ResourceError); } @@ -473,7 +471,7 @@ void tst_QMediaRecorder::testApplicationInative() encoder.setQuality(QMediaRecorder::VeryHighQuality); encoder.setOutputLocation(QUrl("test.tmp")); - QCOMPARE(encoder.outputLocation().toString(), QString("test.tmp")); + QCOMPARE(encoder.outputLocation().toString(), QStringLiteral("test.tmp")); QCOMPARE(encoder.actualLocation(), QUrl()); encoder.record(); @@ -487,7 +485,7 @@ void tst_QMediaRecorder::testApplicationInative() encoder.stop(); // the actual location is available after record - QCOMPARE(encoder.actualLocation().toString(), QString("test.tmp")); + QCOMPARE(encoder.actualLocation().toString(), QStringLiteral("test.tmp")); } QTEST_GUILESS_MAIN(tst_QMediaRecorder) diff --git a/tests/auto/unit/multimedia/qmediatimerange/CMakeLists.txt b/tests/auto/unit/multimedia/qmediatimerange/CMakeLists.txt index a414e2635..507eb16fa 100644 --- a/tests/auto/unit/multimedia/qmediatimerange/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qmediatimerange/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmediatimerange.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qmediatimerange SOURCES tst_qmediatimerange.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qmediatimerange/tst_qmediatimerange.cpp b/tests/auto/unit/multimedia/qmediatimerange/tst_qmediatimerange.cpp index acccdea08..750b72108 100644 --- a/tests/auto/unit/multimedia/qmediatimerange/tst_qmediatimerange.cpp +++ b/tests/auto/unit/multimedia/qmediatimerange/tst_qmediatimerange.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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 <QtCore/qdebug.h> @@ -170,7 +143,7 @@ void tst_QMediaTimeRange::testGetters() // isEmpty + isContinuous + intervals + start + end QVERIFY(!x.isEmpty()); QVERIFY(!x.isContinuous()); - QVERIFY(x.intervals().count() == 2); + QVERIFY(x.intervals().size() == 2); QVERIFY(x.intervals()[0].start() == 10); QVERIFY(x.intervals()[0].end() == 20); QVERIFY(x.intervals()[1].start() == 30); @@ -316,7 +289,7 @@ void tst_QMediaTimeRange::testAddInterval() QVERIFY(!x.isEmpty()); QVERIFY(!x.isContinuous()); - QVERIFY(x.intervals().count() == 2); + QVERIFY(x.intervals().size() == 2); QVERIFY(x.intervals()[0].start() == 10); QVERIFY(x.intervals()[0].end() == 40); QVERIFY(x.intervals()[1].start() == 50); @@ -466,7 +439,7 @@ void tst_QMediaTimeRange::testRemoveInterval() QVERIFY(!x.isEmpty()); QVERIFY(!x.isContinuous()); - QVERIFY(x.intervals().count() == 2); + QVERIFY(x.intervals().size() == 2); QVERIFY(x.intervals()[0].start() == 10); QVERIFY(x.intervals()[0].end() == 19); QVERIFY(x.intervals()[1].start() == 41); @@ -514,7 +487,7 @@ void tst_QMediaTimeRange::testRemoveInterval() QVERIFY(!x.isEmpty()); QVERIFY(!x.isContinuous()); - QVERIFY(x.intervals().count() == 2); + QVERIFY(x.intervals().size() == 2); QVERIFY(x.intervals()[0].start() == 10); QVERIFY(x.intervals()[0].end() == 14); QVERIFY(x.intervals()[1].start() == 36); @@ -536,7 +509,7 @@ void tst_QMediaTimeRange::testRemoveInterval() QVERIFY(!x.isEmpty()); QVERIFY(!x.isContinuous()); - QVERIFY(x.intervals().count() == 2); + QVERIFY(x.intervals().size() == 2); QVERIFY(x.intervals()[0].start() == 10); QVERIFY(x.intervals()[0].end() == 19); QVERIFY(x.intervals()[1].start() == 41); @@ -597,7 +570,7 @@ void tst_QMediaTimeRange::testRemoveTimeRange() QVERIFY(!b.isEmpty()); QVERIFY(!b.isContinuous()); - QVERIFY(b.intervals().count() == 2); + QVERIFY(b.intervals().size() == 2); QVERIFY(b.intervals()[0].start() == 10); QVERIFY(b.intervals()[0].end() == 19); QVERIFY(b.intervals()[1].start() == 51); @@ -618,7 +591,7 @@ void tst_QMediaTimeRange::testRemoveTimeRange() QVERIFY(!b.isEmpty()); QVERIFY(!b.isContinuous()); - QVERIFY(b.intervals().count() == 2); + QVERIFY(b.intervals().size() == 2); QVERIFY(b.intervals()[0].start() == 10); QVERIFY(b.intervals()[0].end() == 19); QVERIFY(b.intervals()[1].start() == 91); diff --git a/tests/auto/unit/multimedia/qmultimediautils/CMakeLists.txt b/tests/auto/unit/multimedia/qmultimediautils/CMakeLists.txt new file mode 100644 index 000000000..2dd805271 --- /dev/null +++ b/tests/auto/unit/multimedia/qmultimediautils/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qmultimediautils + SOURCES + tst_qmultimediautils.cpp + LIBRARIES + Qt::MultimediaPrivate +) diff --git a/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp b/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp new file mode 100644 index 000000000..8ed54ac64 --- /dev/null +++ b/tests/auto/unit/multimedia/qmultimediautils/tst_qmultimediautils.cpp @@ -0,0 +1,184 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> +#include <QDebug> +#include <private/qmultimediautils_p.h> +#include <qvideoframeformat.h> + +class tst_QMultimediaUtils : public QObject +{ + Q_OBJECT + +private slots: + void fraction_of_0(); + void fraction_of_negative_1_5(); + void fraction_of_1_5(); + void fraction_of_30(); + void fraction_of_29_97(); + void fraction_of_lower_boundary(); + void fraction_of_upper_boundary(); + + void qRotatedFrameSize_returnsSizeAccordinglyToRotation(); + + void qMediaFromUserInput_addsFilePrefix_whenCalledWithLocalFile(); + + void qGetRequiredSwapChainFormat_returnsSdr_whenMaxLuminanceIsBelowSdrThreshold_data(); + void qGetRequiredSwapChainFormat_returnsSdr_whenMaxLuminanceIsBelowSdrThreshold(); + void qGetRequiredSwapChainFormat_returnsHdr_whenMaxLuminanceIsBelowHdrThreshold_data(); + void qGetRequiredSwapChainFormat_returnsHdr_whenMaxLuminanceIsBelowHdrThreshold(); + + void qShouldUpdateSwapChainFormat_returnsFalse_whenSwapChainIsNullPointer(); +}; + +void tst_QMultimediaUtils::fraction_of_0() +{ + auto [n, d] = qRealToFraction(0.); + QCOMPARE(n, 0); + QCOMPARE(d, 1); +} + +void tst_QMultimediaUtils::fraction_of_negative_1_5() +{ + auto [n, d] = qRealToFraction(-1.5); + QCOMPARE(double(n) / double(d), -1.5); + QCOMPARE(n, -3); + QCOMPARE(d, 2); +} + +void tst_QMultimediaUtils::fraction_of_1_5() +{ + auto [n, d] = qRealToFraction(1.5); + QCOMPARE(double(n) / double(d), 1.5); + QCOMPARE(n, 3); + QCOMPARE(d, 2); +} + +void tst_QMultimediaUtils::fraction_of_30() +{ + auto [n, d] = qRealToFraction(30.); + QCOMPARE(double(n) / double(d), 30.); + QCOMPARE(d, 1); +} + +void tst_QMultimediaUtils::fraction_of_29_97() +{ + auto [n, d] = qRealToFraction(29.97); + QCOMPARE(double(n) / double(d), 29.97); +} + +void tst_QMultimediaUtils::fraction_of_lower_boundary() +{ + double f = 0.000001; + auto [n, d] = qRealToFraction(f); + QVERIFY(double(n) / double(d) < f); + QVERIFY(double(n) / double(d) >= 0.); +} + +void tst_QMultimediaUtils::fraction_of_upper_boundary() +{ + double f = 0.999999; + auto [n, d] = qRealToFraction(f); + QVERIFY(double(n) / double(d) <= 1.); + QVERIFY(double(n) / double(d) > f); +} + +void tst_QMultimediaUtils::qRotatedFrameSize_returnsSizeAccordinglyToRotation() +{ + QCOMPARE(qRotatedFrameSize({ 10, 22 }, 0), QSize(10, 22)); + QCOMPARE(qRotatedFrameSize({ 10, 23 }, -180), QSize(10, 23)); + QCOMPARE(qRotatedFrameSize({ 10, 24 }, 180), QSize(10, 24)); + QCOMPARE(qRotatedFrameSize({ 10, 25 }, 360), QSize(10, 25)); + QCOMPARE(qRotatedFrameSize({ 11, 26 }, 540), QSize(11, 26)); + + QCOMPARE(qRotatedFrameSize({ 10, 22 }, -90), QSize(22, 10)); + QCOMPARE(qRotatedFrameSize({ 10, 23 }, 90), QSize(23, 10)); + QCOMPARE(qRotatedFrameSize({ 10, 24 }, 270), QSize(24, 10)); + QCOMPARE(qRotatedFrameSize({ 10, 25 }, 450), QSize(25, 10)); + + QCOMPARE(qRotatedFrameSize({ 10, 22 }, QtVideo::Rotation::None), QSize(10, 22)); + QCOMPARE(qRotatedFrameSize({ 10, 22 }, QtVideo::Rotation::Clockwise180), QSize(10, 22)); + + QCOMPARE(qRotatedFrameSize({ 11, 22 }, QtVideo::Rotation::Clockwise90), QSize(22, 11)); + QCOMPARE(qRotatedFrameSize({ 11, 22 }, QtVideo::Rotation::Clockwise270), QSize(22, 11)); +} + +void tst_QMultimediaUtils::qMediaFromUserInput_addsFilePrefix_whenCalledWithLocalFile() +{ + using namespace Qt::Literals; + + QCOMPARE(qMediaFromUserInput(QUrl(u"/foo/bar/baz"_s)), QUrl(u"file:///foo/bar/baz"_s)); + QCOMPARE(qMediaFromUserInput(QUrl::fromLocalFile(u"C:/foo/bar/baz"_s)), + QUrl(u"file:///C:/foo/bar/baz"_s)); + QCOMPARE(qMediaFromUserInput(QUrl(u"file:///foo/bar/baz"_s)), QUrl(u"file:///foo/bar/baz"_s)); + QCOMPARE(qMediaFromUserInput(QUrl(u"http://foo/bar/baz"_s)), QUrl(u"http://foo/bar/baz"_s)); + + QCOMPARE(qMediaFromUserInput(QUrl(u"foo/bar/baz"_s)), + QUrl::fromLocalFile(QDir::currentPath() + u"/foo/bar/baz"_s)); +} + +void tst_QMultimediaUtils:: + qGetRequiredSwapChainFormat_returnsSdr_whenMaxLuminanceIsBelowSdrThreshold_data() +{ + QTest::addColumn<float>("maxLuminance"); + + QTest::newRow("0") << 0.0f; + QTest::newRow("80") << 80.0f; + QTest::newRow("100") << 100.0f; +} + +void tst_QMultimediaUtils:: + qGetRequiredSwapChainFormat_returnsSdr_whenMaxLuminanceIsBelowSdrThreshold() +{ + // Arrange + QFETCH(float, maxLuminance); + + QVideoFrameFormat format; + format.setMaxLuminance(maxLuminance); + + // Act + QRhiSwapChain::Format requiredSwapChainFormat = qGetRequiredSwapChainFormat(format); + + // Assert + QCOMPARE(requiredSwapChainFormat, QRhiSwapChain::Format::SDR); +} + +void tst_QMultimediaUtils:: + qGetRequiredSwapChainFormat_returnsHdr_whenMaxLuminanceIsBelowHdrThreshold_data() +{ + QTest::addColumn<float>("maxLuminance"); + + QTest::newRow("101") << 101.0f; + QTest::newRow("300") << 300.0f; + QTest::newRow("1600") << 1600.0f; +} + +void tst_QMultimediaUtils:: + qGetRequiredSwapChainFormat_returnsHdr_whenMaxLuminanceIsBelowHdrThreshold() +{ + // Arrange + QVideoFrameFormat format; + format.setMaxLuminance(300.0f); + + // Act + QRhiSwapChain::Format requiredSwapChainFormat = qGetRequiredSwapChainFormat(format); + + // Assert + QCOMPARE(requiredSwapChainFormat, QRhiSwapChain::Format::HDRExtendedSrgbLinear); +} + +void tst_QMultimediaUtils::qShouldUpdateSwapChainFormat_returnsFalse_whenSwapChainIsNullPointer() +{ + // Arrange + QRhiSwapChain *swapChain = nullptr; + QRhiSwapChain::Format requiredSwapChainFormat = QRhiSwapChain::Format::SDR; + + // Act + bool shouldUpdate = qShouldUpdateSwapChainFormat(swapChain, requiredSwapChainFormat); + + // Assert + QCOMPARE(shouldUpdate, false); +} + +QTEST_MAIN(tst_QMultimediaUtils) +#include "tst_qmultimediautils.moc" diff --git a/tests/auto/unit/multimedia/qsamplecache/CMakeLists.txt b/tests/auto/unit/multimedia/qsamplecache/CMakeLists.txt index 279bd9800..2f58c1f6e 100644 --- a/tests/auto/unit/multimedia/qsamplecache/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qsamplecache/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qsamplecache.pro. ##################################################################### @@ -13,7 +16,7 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qsamplecache SOURCES tst_qsamplecache.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate TESTDATA ${test_data} diff --git a/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp b/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp index 5baea0278..311d05a5a 100644 --- a/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp +++ b/tests/auto/unit/multimedia/qsamplecache/tst_qsamplecache.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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 <private/qsamplecache_p.h> diff --git a/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt b/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt new file mode 100644 index 000000000..f5b152034 --- /dev/null +++ b/tests/auto/unit/multimedia/qscreencapture/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qscreencapture Test: +##################################################################### + +qt_internal_add_test(tst_qscreencapture + SOURCES + tst_qscreencapture.cpp + INCLUDE_DIRECTORIES + ../../mockbackend + LIBRARIES + # Remove: L${CMAKE_CURRENT_SOURCE_DIR} + Qt::Gui + Qt::MultimediaPrivate + MockMultimediaPlugin +) diff --git a/tests/auto/unit/multimedia/qscreencapture/tst_qscreencapture.cpp b/tests/auto/unit/multimedia/qscreencapture/tst_qscreencapture.cpp new file mode 100644 index 000000000..ad574ddf6 --- /dev/null +++ b/tests/auto/unit/multimedia/qscreencapture/tst_qscreencapture.cpp @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +// TESTED_COMPONENT=src/multimedia + +#include <QtTest/QtTest> +#include <QDebug> + +#include "qmockintegration.h" +#include "qscreencapture.h" +#include "qmocksurfacecapture.h" +#include "qatomic.h" + +QT_USE_NAMESPACE + +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + +class tst_QScreenCapture : public QObject +{ + Q_OBJECT + +private: + // Use custom waiting instead of QSignalSpy since the spy tries copying not sharable object + // QVideoFrame to QVariant and gets an assert + bool waitForFrame(QPlatformSurfaceCapture &psc) + { + QAtomicInteger<bool> newFrameReceived = false; + QObject o; + auto connection = connect(&psc, &QPlatformSurfaceCapture::newVideoFrame, &o, + [&newFrameReceived]() { newFrameReceived = true; }); + + return QTest::qWaitFor([&newFrameReceived]() { return newFrameReceived; }); + } + +private slots: + void destructionOfActiveCapture(); + +}; + +void tst_QScreenCapture::destructionOfActiveCapture() +{ + // Run a few times in order to catch random UB on deletion + for (int i = 0; i < 10; ++i) { + auto sc = std::make_unique<QScreenCapture>(); + QPointer<QPlatformSurfaceCapture> psc = QMockIntegration::instance()->lastScreenCapture(); + QVERIFY(psc); + + sc->setActive(true); + + QVERIFY(waitForFrame(*psc)); + + QSignalSpy spy(sc.get(), &QScreenCapture::activeChanged); + + psc->setParent(nullptr); + sc.reset(); + + QVERIFY2(spy.empty(), "No signals from QScreenCapture are expected on deletion"); + QVERIFY2(!psc, "Platform screen capture must be deleted whether or not it has a parent"); + } +} + +QTEST_MAIN(tst_QScreenCapture) + +#include "tst_qscreencapture.moc" diff --git a/tests/auto/unit/multimedia/qvideobuffers/CMakeLists.txt b/tests/auto/unit/multimedia/qvideobuffers/CMakeLists.txt new file mode 100644 index 000000000..765d02c96 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideobuffers/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qvideobuffers + SOURCES + tst_qvideobuffers.cpp + LIBRARIES + Qt::Multimedia + Qt::MultimediaPrivate +) diff --git a/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp b/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp new file mode 100644 index 000000000..162620fb6 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideobuffers/tst_qvideobuffers.cpp @@ -0,0 +1,256 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> + +#include <private/qmemoryvideobuffer_p.h> +#include <private/qimagevideobuffer_p.h> +#include "qvideoframeformat.h" + +using BufferPtr = std::shared_ptr<QAbstractVideoBuffer>; +using MapModes = std::vector<QtVideo::MapMode>; + +static const MapModes validMapModes = { QtVideo::MapMode::ReadOnly, QtVideo::MapMode::WriteOnly, QtVideo::MapMode::ReadWrite }; + +class tst_QVideoBuffers : public QObject +{ + Q_OBJECT + +public: + tst_QVideoBuffers() {} +public slots: + void initTestCase(); + +private slots: + void map_returnsProperMappings_whenBufferIsNotMapped_data(); + void map_returnsProperMappings_whenBufferIsNotMapped(); + + void map_returnsProperMappings_whenBufferIsMapped_data(); + void map_returnsProperMappings_whenBufferIsMapped(); + + void mapMemoryOrImageBuffer_detachesDataDependingOnMode_data(); + void mapMemoryOrImageBuffer_detachesDataDependingOnMode(); + + void unmap_resetsMappedState_whenBufferIsMapped_data(); + void unmap_resetsMappedState_whenBufferIsMapped(); + + void imageBuffer_fixesInputImage_data(); + void imageBuffer_fixesInputImage(); + +private: + QString mapModeToString(QtVideo::MapMode mapMode) const + { + switch (mapMode) { + case QtVideo::MapMode::NotMapped: + return QLatin1String("NotMapped"); + case QtVideo::MapMode::ReadOnly: + return QLatin1String("ReadOnly"); + case QtVideo::MapMode::WriteOnly: + return QLatin1String("WriteOnly"); + case QtVideo::MapMode::ReadWrite: + return QLatin1String("ReadWrite"); + default: + return QLatin1String("Unknown"); + } + } + + void generateImageAndMemoryBuffersWithAllModes(const MapModes& modes = validMapModes) const + { + QTest::addColumn<BufferPtr>("buffer"); + QTest::addColumn<QtVideo::MapMode>("mapMode"); + QTest::addColumn<const uint8_t *>("sourcePointer"); + + for (auto mode : modes) { + QTest::newRow(QStringLiteral("ImageBuffer, %1").arg(mapModeToString(mode)).toLocal8Bit().constData()) + << createImageBuffer() << mode << m_image.constBits(); + QTest::newRow(QStringLiteral("MemoryBuffer, %1").arg(mapModeToString(mode)).toLocal8Bit().constData()) + << createMemoryBuffer() << mode << reinterpret_cast<const uint8_t *>(m_byteArray.constData()); + } + } + + void generateMapModes(const MapModes &modes = validMapModes) const + { + QTest::addColumn<QtVideo::MapMode>("mapMode"); + + for (auto mode : modes) + QTest::newRow(mapModeToString(mode).toLocal8Bit().constData()) << mode; + } + + BufferPtr createImageBuffer() const + { + return std::make_shared<QImageVideoBuffer>(m_image); + } + + BufferPtr createMemoryBuffer() const + { + return std::make_shared<QMemoryVideoBuffer>(m_byteArray, m_byteArray.size() / m_image.height()); + } + + QImage m_image = { QSize(5, 4), QImage::Format_RGBA8888 }; + QByteArray m_byteArray; +}; + + +void tst_QVideoBuffers::initTestCase() +{ + m_image.fill(Qt::gray); + m_image.setPixelColor(0, 0, Qt::green); + m_image.setPixelColor(m_image.width() - 1, 0, Qt::blue); + m_image.setPixelColor(0, m_image.height() - 1, Qt::red); + + m_byteArray.assign(m_image.constBits(), m_image.constBits() + m_image.sizeInBytes()); +} + +void tst_QVideoBuffers::map_returnsProperMappings_whenBufferIsNotMapped_data() +{ + generateImageAndMemoryBuffersWithAllModes(); +} + +void tst_QVideoBuffers::map_returnsProperMappings_whenBufferIsNotMapped() +{ + QFETCH(BufferPtr, buffer); + QFETCH(QtVideo::MapMode, mapMode); + + auto mappedData = buffer->map(mapMode); + + QCOMPARE(mappedData.planeCount, 1); + QVERIFY(mappedData.data[0]); + QCOMPARE(mappedData.dataSize[0], 80); + QCOMPARE(mappedData.bytesPerLine[0], 20); + + const auto data = reinterpret_cast<const char*>(mappedData.data[0]); + QCOMPARE(QByteArray(data, mappedData.dataSize[0]), m_byteArray); +} + +void tst_QVideoBuffers::map_returnsProperMappings_whenBufferIsMapped_data() +{ + generateImageAndMemoryBuffersWithAllModes(); +} + +void tst_QVideoBuffers::map_returnsProperMappings_whenBufferIsMapped() +{ + QFETCH(BufferPtr, buffer); + QFETCH(QtVideo::MapMode, mapMode); + + auto mappedData1 = buffer->map(mapMode); + auto mappedData2 = buffer->map(mapMode); + + QCOMPARE(mappedData1.planeCount, mappedData2.planeCount); + QCOMPARE(mappedData1.data[0], mappedData2.data[0]); + QCOMPARE(mappedData1.dataSize[0], mappedData2.dataSize[0]); + QCOMPARE(mappedData1.bytesPerLine[0], mappedData2.bytesPerLine[0]); +} + +void tst_QVideoBuffers::mapMemoryOrImageBuffer_detachesDataDependingOnMode_data() +{ + generateImageAndMemoryBuffersWithAllModes(); +} + +void tst_QVideoBuffers::mapMemoryOrImageBuffer_detachesDataDependingOnMode() +{ + QFETCH(BufferPtr, buffer); + QFETCH(QtVideo::MapMode, mapMode); + QFETCH(const uint8_t *, sourcePointer); + + auto mappedData = buffer->map(mapMode); + QCOMPARE(mappedData.planeCount, 1); + + const bool isDetached = mappedData.data[0] != sourcePointer; + const bool isWriteMode = (mapMode & QtVideo::MapMode::WriteOnly) == QtVideo::MapMode::WriteOnly; + QCOMPARE(isDetached, isWriteMode); +} + +void tst_QVideoBuffers::unmap_resetsMappedState_whenBufferIsMapped_data() +{ + generateImageAndMemoryBuffersWithAllModes(); +} + +void tst_QVideoBuffers::unmap_resetsMappedState_whenBufferIsMapped() +{ + QFETCH(BufferPtr, buffer); + QFETCH(QtVideo::MapMode, mapMode); + + buffer->map(mapMode); + + buffer->unmap(); + + // Check buffer is valid and it's possible to map again + auto mappedData = buffer->map(QtVideo::MapMode::ReadOnly); + QCOMPARE(mappedData.planeCount, 1); + + const auto data = reinterpret_cast<const char*>(mappedData.data[0]); + QCOMPARE(QByteArray(data, mappedData.dataSize[0]), m_byteArray); +} + +void tst_QVideoBuffers::imageBuffer_fixesInputImage_data() +{ + QTest::addColumn<QImage::Format>("inputImageFormat"); + QTest::addColumn<QImage::Format>("underlyingImageFormat"); + + QTest::newRow("Format_RGB32 => Format_RGB32") << QImage::Format_RGB32 << QImage::Format_RGB32; + QTest::newRow("Format_ARGB32 => Format_ARGB32") + << QImage::Format_ARGB32 << QImage::Format_ARGB32; + QTest::newRow("Format_ARGB32_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_ARGB32_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_RGBA8888 => Format_RGBA8888") + << QImage::Format_RGBA8888 << QImage::Format_RGBA8888; + QTest::newRow("Format_RGBA8888_Premultiplied => Format_RGBA8888_Premultiplied") + << QImage::Format_RGBA8888_Premultiplied << QImage::Format_RGBA8888_Premultiplied; + QTest::newRow("Format_RGBX8888 => Format_RGBX8888") + << QImage::Format_RGBX8888 << QImage::Format_RGBX8888; + QTest::newRow("Format_Grayscale8 => Format_Grayscale8") + << QImage::Format_Grayscale8 << QImage::Format_Grayscale8; + QTest::newRow("Format_Grayscale16 => Format_Grayscale16") + << QImage::Format_Grayscale16 << QImage::Format_Grayscale16; + + QTest::newRow("Format_ARGB8565_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_ARGB8565_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_ARGB6666_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_ARGB6666_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_ARGB8555_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_ARGB8555_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_ARGB4444_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_ARGB4444_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_A2BGR30_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_A2BGR30_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_A2RGB30_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_A2RGB30_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_RGBA64_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_RGBA64_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_RGBA16FPx4_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_RGBA16FPx4_Premultiplied << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_RGBA32FPx4_Premultiplied => Format_ARGB32_Premultiplied") + << QImage::Format_RGBA32FPx4_Premultiplied << QImage::Format_ARGB32_Premultiplied; + + QTest::newRow("Format_Alpha8 => Format_ARGB32") + << QImage::Format_Alpha8 << QImage::Format_ARGB32; + QTest::newRow("Format_RGBA64 => Format_ARGB32") + << QImage::Format_RGBA64 << QImage::Format_ARGB32; + QTest::newRow("Format_RGBA16FPx4 => Format_ARGB32") + << QImage::Format_RGBA16FPx4 << QImage::Format_ARGB32; + QTest::newRow("Format_RGBA32FPx4 => Format_ARGB32") + << QImage::Format_RGBA32FPx4 << QImage::Format_ARGB32; +} + +void tst_QVideoBuffers::imageBuffer_fixesInputImage() +{ + QFETCH(QImage::Format, inputImageFormat); + QFETCH(QImage::Format, underlyingImageFormat); + + m_image.convertTo(inputImageFormat); + QImageVideoBuffer buffer(m_image); + + auto underlyingImage = buffer.underlyingImage(); + + QCOMPARE(underlyingImage.format(), underlyingImageFormat); + QCOMPARE_NE(QVideoFrameFormat::pixelFormatFromImageFormat(underlyingImage.format()), + QVideoFrameFormat::Format_Invalid); + QCOMPARE(m_image.convertedTo(underlyingImageFormat), underlyingImage); + + if (inputImageFormat == underlyingImageFormat) + QCOMPARE(m_image.constBits(), underlyingImage.constBits()); +} + +QTEST_APPLESS_MAIN(tst_QVideoBuffers); + +#include "tst_qvideobuffers.moc" diff --git a/tests/auto/unit/multimedia/qvideoframe/CMakeLists.txt b/tests/auto/unit/multimedia/qvideoframe/CMakeLists.txt index a46bbcc9b..29b8a413e 100644 --- a/tests/auto/unit/multimedia/qvideoframe/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qvideoframe/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qvideoframe.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qvideoframe SOURCES tst_qvideoframe.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp b/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp index cb94c102e..295484a92 100644 --- a/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp +++ b/tests/auto/unit/multimedia/qvideoframe/tst_qvideoframe.cpp @@ -1,41 +1,18 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <qvideoframe.h> #include <qvideoframeformat.h> +#include "QtTest/qtestcase.h" #include "private/qmemoryvideobuffer_p.h" +#include "private/qhwvideobuffer_p.h" +#include "private/qvideoframe_p.h" #include <QtGui/QImage> #include <QtCore/QPointer> #include <QtMultimedia/private/qtmultimedia-config_p.h> +#include "private/qvideoframeconverter_p.h" // Adds an enum, and the stringized version #define ADD_ENUM_TEST(x) \ @@ -44,6 +21,160 @@ << QString(QLatin1String(#x)); +// Image used for testing conversion from QImage to QVideoFrame +QImage createTestImage(QImage::Format format) +{ + // +---+---+---+ + // | r | g | b | + // | b | r | g | + // +---+---+---+ + QImage image{ { 3, 2 }, QImage::Format_ARGB32 }; + image.setPixelColor(0, 0, QColor(Qt::red)); + image.setPixelColor(1, 0, QColor(Qt::green)); + image.setPixelColor(2, 0, QColor(Qt::blue)); + image.setPixelColor(0, 1, QColor(Qt::blue)); + image.setPixelColor(1, 1, QColor(Qt::red)); + image.setPixelColor(2, 1, QColor(Qt::green)); + return image.convertToFormat(format); +} + +// clang-format off + +// Convert a QVideoFrame pixel value from raw format to QRgb +// Only works with little-endian byte ordering +QRgb swizzle(uint value, QVideoFrameFormat::PixelFormat format) +{ + switch (format) { + case QVideoFrameFormat::Format_ARGB8888: + case QVideoFrameFormat::Format_ARGB8888_Premultiplied: + case QVideoFrameFormat::Format_XRGB8888: + Q_ASSERT(false); // not implemented + return 0; + case QVideoFrameFormat::Format_BGRA8888: + case QVideoFrameFormat::Format_BGRA8888_Premultiplied: + case QVideoFrameFormat::Format_BGRX8888: + return value; + case QVideoFrameFormat::Format_ABGR8888: + case QVideoFrameFormat::Format_XBGR8888: + Q_ASSERT(false); // not implemented + return 0; + case QVideoFrameFormat::Format_RGBA8888: + case QVideoFrameFormat::Format_RGBX8888: + return (((value >> 24) & 0xff) << 24) // a -> a + | ((value & 0xff) << 16) // b -> r + | (((value >> 8) & 0xff) << 8) // g -> g + | ((value >> 16) & 0xff); // r -> b + default: + qWarning() << "Unsupported format"; + return 0; + } +} + +std::vector<QRgb> swizzle(const std::vector<uint> &pixels, QVideoFrameFormat::PixelFormat format) +{ + std::vector<QRgb> rgba(pixels.size()); + std::transform(pixels.begin(), pixels.end(), rgba.begin(), + [format](uint value) { + return swizzle(value, format); + }); + return rgba; +} + +// clang-format on + +std::optional<std::vector<QRgb>> getPixels(QVideoFrame &frame) +{ + if (!frame.map(QtVideo::MapMode::ReadOnly)) + return std::nullopt; + + const uint *mappedPixels = reinterpret_cast<const uint *>(frame.bits(0)); + const unsigned long long stride = frame.bytesPerLine(0) / sizeof(QRgb); + + std::vector<uint> pixels; + for (int j = 0; j < frame.size().height(); ++j) { + for (int i = 0; i < frame.size().width(); ++i) { + pixels.push_back(mappedPixels[i + j * stride]); + } + } + + frame.unmap(); + + return swizzle(pixels, frame.pixelFormat()); +} + +bool compareEq(QVideoFrame &frame, const QImage &image) +{ + if (frame.size() != image.size()) { + qDebug() << "Size mismatch"; + return false; + } + + const std::vector<QRgb> expectedPixels = { image.pixel(0, 0), image.pixel(1, 0), image.pixel(2, 0), + image.pixel(0, 1), image.pixel(1, 1), image.pixel(2, 1) }; + + const std::optional<std::vector<QRgb>> actualPixels = getPixels(frame); + if (!actualPixels) { + qDebug() << "Failed to read pixels from frame"; + return false; + } + + for (size_t i = 0; i < expectedPixels.size(); ++i) { + if (expectedPixels[i] != actualPixels->at(i)) { + qDebug() << "Pixel difference at element" << i << ":" << Qt::hex << expectedPixels[i] + << "vs" << actualPixels->at(i); + return false; + } + } + return true; +} + +QSet s_pixelFormats{ QVideoFrameFormat::Format_ARGB8888, + QVideoFrameFormat::Format_ARGB8888_Premultiplied, + QVideoFrameFormat::Format_XRGB8888, + QVideoFrameFormat::Format_BGRA8888, + QVideoFrameFormat::Format_BGRA8888_Premultiplied, + QVideoFrameFormat::Format_BGRX8888, + QVideoFrameFormat::Format_ABGR8888, + QVideoFrameFormat::Format_XBGR8888, + QVideoFrameFormat::Format_RGBA8888, + QVideoFrameFormat::Format_RGBX8888, + QVideoFrameFormat::Format_NV12, + QVideoFrameFormat::Format_NV21, + QVideoFrameFormat::Format_IMC1, + QVideoFrameFormat::Format_IMC2, + QVideoFrameFormat::Format_IMC3, + QVideoFrameFormat::Format_IMC4, + QVideoFrameFormat::Format_AYUV, + QVideoFrameFormat::Format_AYUV_Premultiplied, + QVideoFrameFormat::Format_YV12, + QVideoFrameFormat::Format_YUV420P, + QVideoFrameFormat::Format_YUV422P, + QVideoFrameFormat::Format_UYVY, + QVideoFrameFormat::Format_YUYV, + QVideoFrameFormat::Format_Y8, + QVideoFrameFormat::Format_Y16, + QVideoFrameFormat::Format_P010, + QVideoFrameFormat::Format_P016, + QVideoFrameFormat::Format_YUV420P10 }; + +bool isSupportedPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat) +{ +#ifdef Q_OS_ANDROID + // TODO: QTBUG-125238 + switch (pixelFormat) { + case QVideoFrameFormat::Format_Y16: + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: + case QVideoFrameFormat::Format_YUV420P10: + return false; + default: + return true; + } +#else + return true; +#endif +} + class tst_QVideoFrame : public QObject { Q_OBJECT @@ -66,7 +197,8 @@ private slots: void createFromBuffer(); void createFromImage_data(); void createNull(); - void destructor(); + void destructor_deletesVideoBuffer(); + void destructorOfPrivateData_unmapsAndDeletesVideoBuffer_whenNoMoreFramesCopies(); void copy_data(); void copy(); void assign_data(); @@ -78,6 +210,9 @@ private slots: void formatConversion_data(); void formatConversion(); + void qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames_data(); + void qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames(); + void isMapped(); void isReadable(); void isWritable(); @@ -86,59 +221,53 @@ private slots: void image(); void emptyData(); -}; - -class QtTestDummyVideoBuffer : public QObject, public QAbstractVideoBuffer -{ - Q_OBJECT -public: - QtTestDummyVideoBuffer() - : QAbstractVideoBuffer(QVideoFrame::NoHandle) {} - explicit QtTestDummyVideoBuffer(QVideoFrame::HandleType type) - : QAbstractVideoBuffer(type) {} - - [[nodiscard]] QVideoFrame::MapMode mapMode() const override { return QVideoFrame::NotMapped; } - MapData map(QVideoFrame::MapMode) override { return {}; } - void unmap() override {} + void mirrored_takesValue_fromVideoFrameFormat(); + void rotation_takesValue_fromVideoFrameFormat(); + void streamFrameRate_takesValue_fromVideoFrameFormat(); + + void constructor_createsInvalidFrame_whenCalledWithNullImage(); + void constructor_createsInvalidFrame_whenCalledWithEmptyImage(); + void constructor_createsInvalidFrame_whenCalledWithInvalidImageFormat(); + void constructor_createsFrameWithCorrectFormat_whenCalledWithSupportedImageFormats_data(); + void constructor_createsFrameWithCorrectFormat_whenCalledWithSupportedImageFormats(); + void constructor_copiesImageData_whenCalledWithRGBFormats_data(); + void constructor_copiesImageData_whenCalledWithRGBFormats(); }; -class QtTestVideoBuffer : public QAbstractVideoBuffer +class QtTestVideoBuffer : public QObject, public QHwVideoBuffer { + Q_OBJECT public: - QtTestVideoBuffer() - : QAbstractVideoBuffer(QVideoFrame::NoHandle) - {} - explicit QtTestVideoBuffer(QVideoFrame::HandleType type) - : QAbstractVideoBuffer(type) - {} - - [[nodiscard]] QVideoFrame::MapMode mapMode() const override { return m_mapMode; } + QtTestVideoBuffer() : QHwVideoBuffer(QVideoFrame::NoHandle) { } + explicit QtTestVideoBuffer(QVideoFrame::HandleType type) : QHwVideoBuffer(type) { } + ~QtTestVideoBuffer() override { QTEST_ASSERT(!m_mapObject); } - MapData map(QVideoFrame::MapMode mode) override + MapData map(QtVideo::MapMode) override { - m_mapMode = mode; + m_mapObject = std::make_unique<QObject>(); MapData mapData; int nBytes = m_numBytes; - mapData.nPlanes = m_planeCount; + mapData.planeCount = m_planeCount; for (int i = 0; i < m_planeCount; ++i) { mapData.data[i] = m_data[i]; mapData.bytesPerLine[i] = m_bytesPerLine[i]; if (i) { - mapData.size[i-1] = m_data[i] - m_data[i-1]; - nBytes -= mapData.size[i-1]; + mapData.dataSize[i-1] = m_data[i] - m_data[i-1]; + nBytes -= mapData.dataSize[i-1]; } - mapData.size[i] = nBytes; + mapData.dataSize[i] = nBytes; } return mapData; } - void unmap() override { m_mapMode = QVideoFrame::NotMapped; } + + void unmap() override { m_mapObject.reset(); } uchar *m_data[4]; int m_bytesPerLine[4]; - int m_planeCount = 0; + int m_planeCount = 1; int m_numBytes; - QVideoFrame::MapMode m_mapMode = QVideoFrame::NotMapped; + std::unique_ptr<QObject> m_mapObject; }; tst_QVideoFrame::tst_QVideoFrame() @@ -169,33 +298,43 @@ void tst_QVideoFrame::create_data() { QTest::addColumn<QSize>("size"); QTest::addColumn<QVideoFrameFormat::PixelFormat>("pixelFormat"); - QTest::addColumn<int>("bytes"); QTest::addColumn<int>("bytesPerLine"); QTest::newRow("64x64 ARGB32") << QSize(64, 64) - << QVideoFrameFormat::Format_ARGB8888; + << QVideoFrameFormat::Format_ARGB8888 + << 64*4; QTest::newRow("32x256 YUV420P") << QSize(32, 256) - << QVideoFrameFormat::Format_YUV420P; + << QVideoFrameFormat::Format_YUV420P + << 32; + QTest::newRow("32x256 UYVY") + << QSize(32, 256) + << QVideoFrameFormat::Format_UYVY + << 32*2; } void tst_QVideoFrame::create() { QFETCH(QSize, size); QFETCH(QVideoFrameFormat::PixelFormat, pixelFormat); + QFETCH(int, bytesPerLine); QVideoFrame frame(QVideoFrameFormat(size, pixelFormat)); QVERIFY(frame.isValid()); QCOMPARE(frame.handleType(), QVideoFrame::NoHandle); - QCOMPARE(frame.textureHandle(0), 0u); + QCOMPARE(QVideoFramePrivate::hwBuffer(frame), nullptr); + QCOMPARE_NE(QVideoFramePrivate::buffer(frame), nullptr); QCOMPARE(frame.pixelFormat(), pixelFormat); QCOMPARE(frame.size(), size); QCOMPARE(frame.width(), size.width()); QCOMPARE(frame.height(), size.height()); QCOMPARE(frame.startTime(), qint64(-1)); QCOMPARE(frame.endTime(), qint64(-1)); + frame.map(QtVideo::MapMode::ReadOnly); + QCOMPARE(frame.bytesPerLine(0), bytesPerLine); + frame.unmap(); } void tst_QVideoFrame::createInvalid_data() @@ -220,7 +359,7 @@ void tst_QVideoFrame::createInvalid() QVERIFY(!frame.isValid()); QCOMPARE(frame.handleType(), QVideoFrame::NoHandle); - QCOMPARE(frame.textureHandle(0), 0u); + QCOMPARE(QVideoFramePrivate::buffer(frame), nullptr); QCOMPARE(frame.pixelFormat(), pixelFormat); QCOMPARE(frame.size(), size); QCOMPARE(frame.width(), size.width()); @@ -251,7 +390,8 @@ void tst_QVideoFrame::createFromBuffer() QFETCH(QSize, size); QFETCH(QVideoFrameFormat::PixelFormat, pixelFormat); - QVideoFrame frame(new QtTestDummyVideoBuffer(handleType), QVideoFrameFormat(size, pixelFormat)); + QVideoFrame frame = QVideoFramePrivate::createFrame( + std::make_unique<QtTestVideoBuffer>(handleType), QVideoFrameFormat(size, pixelFormat)); QVERIFY(frame.isValid()); QCOMPARE(frame.handleType(), handleType); @@ -293,10 +433,10 @@ void tst_QVideoFrame::createNull() QCOMPARE(frame.height(), -1); QCOMPARE(frame.startTime(), qint64(-1)); QCOMPARE(frame.endTime(), qint64(-1)); - QCOMPARE(frame.mapMode(), QVideoFrame::NotMapped); - QVERIFY(!frame.map(QVideoFrame::ReadOnly)); - QVERIFY(!frame.map(QVideoFrame::ReadWrite)); - QVERIFY(!frame.map(QVideoFrame::WriteOnly)); + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), QtVideo::MapMode::NotMapped); + QVERIFY(!frame.map(QtVideo::MapMode::ReadOnly)); + QVERIFY(!frame.map(QtVideo::MapMode::ReadWrite)); + QVERIFY(!frame.map(QtVideo::MapMode::WriteOnly)); QCOMPARE(frame.isMapped(), false); frame.unmap(); // Shouldn't crash QCOMPARE(frame.isReadable(), false); @@ -305,7 +445,9 @@ void tst_QVideoFrame::createNull() // Null buffer (shouldn't crash) { - QVideoFrame frame(nullptr, QVideoFrameFormat(QSize(1024,768), QVideoFrameFormat::Format_ARGB8888)); + QVideoFrame frame = QVideoFramePrivate::createFrame( + std::unique_ptr<QHwVideoBuffer>(), + QVideoFrameFormat(QSize(1024, 768), QVideoFrameFormat::Format_ARGB8888)); QVERIFY(!frame.isValid()); QCOMPARE(frame.handleType(), QVideoFrame::NoHandle); QCOMPARE(frame.pixelFormat(), QVideoFrameFormat::Format_ARGB8888); @@ -314,10 +456,10 @@ void tst_QVideoFrame::createNull() QCOMPARE(frame.height(), 768); QCOMPARE(frame.startTime(), qint64(-1)); QCOMPARE(frame.endTime(), qint64(-1)); - QCOMPARE(frame.mapMode(), QVideoFrame::NotMapped); - QVERIFY(!frame.map(QVideoFrame::ReadOnly)); - QVERIFY(!frame.map(QVideoFrame::ReadWrite)); - QVERIFY(!frame.map(QVideoFrame::WriteOnly)); + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), QtVideo::MapMode::NotMapped); + QVERIFY(!frame.map(QtVideo::MapMode::ReadOnly)); + QVERIFY(!frame.map(QtVideo::MapMode::ReadWrite)); + QVERIFY(!frame.map(QtVideo::MapMode::WriteOnly)); QCOMPARE(frame.isMapped(), false); frame.unmap(); // Shouldn't crash QCOMPARE(frame.isReadable(), false); @@ -325,17 +467,55 @@ void tst_QVideoFrame::createNull() } } -void tst_QVideoFrame::destructor() +void tst_QVideoFrame::destructor_deletesVideoBuffer() { - QPointer<QtTestDummyVideoBuffer> buffer = new QtTestDummyVideoBuffer; + QPointer buffer(new QtTestVideoBuffer); { - QVideoFrame frame(buffer, QVideoFrameFormat(QSize(4, 1), QVideoFrameFormat::Format_ARGB8888)); + QVideoFrame frame = QVideoFramePrivate::createFrame( + std::unique_ptr<QHwVideoBuffer>(buffer), + QVideoFrameFormat(QSize(4, 1), QVideoFrameFormat::Format_ARGB8888)); } QVERIFY(buffer.isNull()); } +void tst_QVideoFrame::destructorOfPrivateData_unmapsAndDeletesVideoBuffer_whenNoMoreFramesCopies() +{ + uchar bufferData[16] = { + 0, + }; + + QPointer buffer(new QtTestVideoBuffer); + buffer->m_data[0] = bufferData; + buffer->m_bytesPerLine[0] = 16; + buffer->m_planeCount = 1; + buffer->m_numBytes = sizeof(bufferData); + + QVideoFrame frame1 = QVideoFramePrivate::createFrame( + std::unique_ptr<QHwVideoBuffer>(buffer), + QVideoFrameFormat(QSize(4, 1), QVideoFrameFormat::Format_ARGB8888)); + + frame1.map(QtVideo::MapMode::ReadOnly); + + QVERIFY(buffer); + QPointer mapObject(buffer->m_mapObject.get()); + + QVideoFrame frame2 = frame1; + + frame1 = {}; + + // check if the buffer and the map object are still alive + QVERIFY(mapObject); + QVERIFY(buffer); + + frame2 = {}; + + // check if the buffer and the map object have been deleted + QVERIFY(!mapObject); + QVERIFY(!buffer); +} + void tst_QVideoFrame::copy_data() { QTest::addColumn<QVideoFrame::HandleType>("handleType"); @@ -384,10 +564,11 @@ void tst_QVideoFrame::copy() QFETCH(qint64, startTime); QFETCH(qint64, endTime); - QPointer<QtTestDummyVideoBuffer> buffer = new QtTestDummyVideoBuffer(handleType); + QPointer<QtTestVideoBuffer> buffer = new QtTestVideoBuffer(handleType); { - QVideoFrame frame(buffer, QVideoFrameFormat(size, pixelFormat)); + QVideoFrame frame = QVideoFramePrivate::createFrame(std::unique_ptr<QHwVideoBuffer>(buffer), + QVideoFrameFormat(size, pixelFormat)); frame.setStartTime(startTime); frame.setEndTime(endTime); @@ -473,11 +654,12 @@ void tst_QVideoFrame::assign() QFETCH(qint64, startTime); QFETCH(qint64, endTime); - QPointer<QtTestDummyVideoBuffer> buffer = new QtTestDummyVideoBuffer(handleType); + QPointer<QtTestVideoBuffer> buffer = new QtTestVideoBuffer(handleType); QVideoFrame frame; { - QVideoFrame otherFrame(buffer, QVideoFrameFormat(size, pixelFormat)); + QVideoFrame otherFrame = QVideoFramePrivate::createFrame( + std::unique_ptr<QHwVideoBuffer>(buffer), QVideoFrameFormat(size, pixelFormat)); otherFrame.setStartTime(startTime); otherFrame.setEndTime(endTime); @@ -537,44 +719,44 @@ void tst_QVideoFrame::map_data() { QTest::addColumn<QSize>("size"); QTest::addColumn<QVideoFrameFormat::PixelFormat>("pixelFormat"); - QTest::addColumn<QVideoFrame::MapMode>("mode"); + QTest::addColumn<QtVideo::MapMode>("mode"); QTest::newRow("read-only") << QSize(64, 64) << QVideoFrameFormat::Format_ARGB8888 - << QVideoFrame::ReadOnly; + << QtVideo::MapMode::ReadOnly; QTest::newRow("write-only") << QSize(64, 64) << QVideoFrameFormat::Format_ARGB8888 - << QVideoFrame::WriteOnly; + << QtVideo::MapMode::WriteOnly; QTest::newRow("read-write") << QSize(64, 64) << QVideoFrameFormat::Format_ARGB8888 - << QVideoFrame::ReadWrite; + << QtVideo::MapMode::ReadWrite; } void tst_QVideoFrame::map() { QFETCH(QSize, size); QFETCH(QVideoFrameFormat::PixelFormat, pixelFormat); - QFETCH(QVideoFrame::MapMode, mode); + QFETCH(QtVideo::MapMode, mode); QVideoFrame frame(QVideoFrameFormat(size, pixelFormat)); QVERIFY(!frame.bits(0)); QCOMPARE(frame.mappedBytes(0), 0); QCOMPARE(frame.bytesPerLine(0), 0); - QCOMPARE(frame.mapMode(), QVideoFrame::NotMapped); + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), QtVideo::MapMode::NotMapped); QVERIFY(frame.map(mode)); // Mapping multiple times is allowed in ReadOnly mode - if (mode == QVideoFrame::ReadOnly) { + if (mode == QtVideo::MapMode::ReadOnly) { const uchar *bits = frame.bits(0); - QVERIFY(frame.map(QVideoFrame::ReadOnly)); + QVERIFY(frame.map(QtVideo::MapMode::ReadOnly)); QVERIFY(frame.isMapped()); QCOMPARE(frame.bits(0), bits); @@ -584,34 +766,39 @@ void tst_QVideoFrame::map() QCOMPARE(frame.bits(0), bits); //re-mapping in Write or ReadWrite modes should fail - QVERIFY(!frame.map(QVideoFrame::WriteOnly)); - QVERIFY(!frame.map(QVideoFrame::ReadWrite)); + QVERIFY(!frame.map(QtVideo::MapMode::WriteOnly)); + QVERIFY(!frame.map(QtVideo::MapMode::ReadWrite)); } else { // Mapping twice in ReadWrite or WriteOnly modes should fail, but leave it mapped (and the mode is ignored) QVERIFY(!frame.map(mode)); - QVERIFY(!frame.map(QVideoFrame::ReadOnly)); + QVERIFY(!frame.map(QtVideo::MapMode::ReadOnly)); } QVERIFY(frame.bits(0)); - QCOMPARE(frame.mapMode(), mode); + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), mode); frame.unmap(); QVERIFY(!frame.bits(0)); QCOMPARE(frame.mappedBytes(0), 0); QCOMPARE(frame.bytesPerLine(0), 0); - QCOMPARE(frame.mapMode(), QVideoFrame::NotMapped); + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), QtVideo::MapMode::NotMapped); } void tst_QVideoFrame::mapPlanes_data() { QTest::addColumn<QVideoFrame>("frame"); + + // Distance between subsequent lines within a color plane in bytes QTest::addColumn<QList<int> >("strides"); + + // Distance from first pixel of first color plane to first pixel + // of n'th plane in bytes QTest::addColumn<QList<int> >("offsets"); static uchar bufferData[1024]; - QtTestVideoBuffer *planarBuffer = new QtTestVideoBuffer; + auto planarBuffer = std::make_unique<QtTestVideoBuffer>(); planarBuffer->m_data[0] = bufferData; planarBuffer->m_data[1] = bufferData + 512; planarBuffer->m_data[2] = bufferData + 765; @@ -621,14 +808,18 @@ void tst_QVideoFrame::mapPlanes_data() planarBuffer->m_planeCount = 3; planarBuffer->m_numBytes = sizeof(bufferData); - QTest::newRow("Planar") - << QVideoFrame(planarBuffer, QVideoFrameFormat(QSize(64, 64), QVideoFrameFormat::Format_YUV420P)) - << (QList<int>() << 64 << 36 << 36) - << (QList<int>() << 512 << 765); + QTest::newRow("Planar") << QVideoFramePrivate::createFrame( + std::move(planarBuffer), + QVideoFrameFormat(QSize(64, 64), QVideoFrameFormat::Format_YUV420P)) + << (QList<int>() << 64 << 36 << 36) << (QList<int>() << 512 << 765); QTest::newRow("Format_YUV420P") << QVideoFrame(QVideoFrameFormat(QSize(60, 64), QVideoFrameFormat::Format_YUV420P)) << (QList<int>() << 64 << 32 << 32) << (QList<int>() << 4096 << 5120); + QTest::newRow("Format_YUV422P") + << QVideoFrame(QVideoFrameFormat(QSize(60, 64), QVideoFrameFormat::Format_YUV422P)) + << (QList<int>() << 64 << 64 / 2 << 64 / 2) + << (QList<int>() << 64 * 64 << 64 * 64 + 64 / 2 * 64); QTest::newRow("Format_YV12") << QVideoFrame(QVideoFrameFormat(QSize(60, 64), QVideoFrameFormat::Format_YV12)) << (QList<int>() << 64 << 32 << 32) @@ -669,24 +860,24 @@ void tst_QVideoFrame::mapPlanes() QFETCH(QList<int>, strides); QFETCH(QList<int>, offsets); - QCOMPARE(strides.count(), offsets.count() + 1); + QCOMPARE(strides.size(), offsets.size() + 1); - QCOMPARE(frame.map(QVideoFrame::ReadOnly), true); - QCOMPARE(frame.planeCount(), strides.count()); + QCOMPARE(frame.map(QtVideo::MapMode::ReadOnly), true); + QCOMPARE(frame.planeCount(), strides.size()); - QVERIFY(strides.count() > 0); + QVERIFY(strides.size() > 0); QCOMPARE(frame.bytesPerLine(0), strides.at(0)); QVERIFY(frame.bits(0)); - if (strides.count() > 1) { + if (strides.size() > 1) { QCOMPARE(frame.bytesPerLine(1), strides.at(1)); QCOMPARE(int(frame.bits(1) - frame.bits(0)), offsets.at(0)); } - if (strides.count() > 2) { + if (strides.size() > 2) { QCOMPARE(frame.bytesPerLine(2), strides.at(2)); QCOMPARE(int(frame.bits(2) - frame.bits(0)), offsets.at(1)); } - if (strides.count() > 3) { + if (strides.size() > 3) { QCOMPARE(frame.bytesPerLine(3), strides.at(3)); QCOMPARE(int(frame.bits(3) - frame.bits(0)), offsets.at(0)); } @@ -803,6 +994,11 @@ void tst_QVideoFrame::formatConversion_data() QTest::newRow("QVideoFrameFormat::Format_Jpeg") << QImage::Format_Invalid << QVideoFrameFormat::Format_Jpeg; + QTest::newRow("QVideoFrameFormat::Format_RGBX8888") + << QImage::Format_RGBX8888 << QVideoFrameFormat::Format_RGBX8888; + QTest::newRow("QImage::Format_RGBA8888_Premultiplied => QVideoFrameFormat::Format_RGBX8888 " + "(workaround)") + << QImage::Format_RGBA8888_Premultiplied << QVideoFrameFormat::Format_RGBX8888; } void tst_QVideoFrame::formatConversion() @@ -813,17 +1009,86 @@ void tst_QVideoFrame::formatConversion() if (imageFormat != QImage::Format_Invalid) QCOMPARE(QVideoFrameFormat::pixelFormatFromImageFormat(imageFormat), pixelFormat); + if (imageFormat == QImage::Format_RGBA8888_Premultiplied) { + qWarning() << "Workaround: convert QImage::Format_RGBA8888_Premultiplied to " + "QVideoFrameFormat::Format_RGBX8888; to be removed in 6.8"; + return; + } + if (pixelFormat != QVideoFrameFormat::Format_Invalid) QCOMPARE(QVideoFrameFormat::imageFormatFromPixelFormat(pixelFormat), imageFormat); } +void tst_QVideoFrame::qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames_data() { + QTest::addColumn<QSize>("size"); + QTest::addColumn<QVideoFrameFormat::PixelFormat>("pixelFormat"); + QTest::addColumn<bool>("forceCpuConversion"); + QTest::addColumn<bool>("supportedOnPlatform"); + + const std::vector<QSize> sizes{ + // Even sized + { 2, 2 }, + { 2, 10 }, + { 10, 2 }, + { 640, 480 }, + { 4096, 2160 }, + // Odd sized + { 0, 0 }, + { 3, 3 }, + { 2, 3 }, + { 3, 2 }, + { 641, 480 }, + { 640, 481 }, + // TODO: Crashes + // { 1, 1 } // TODO: Division by zero in QVideoFrame::map (Debug) + // { 1, 2 } // TODO: D3D validation error in QRhiD3D11::executeCommandBuffer + // { 2, 1 } // TODO: D3D validation error in QRhiD3D11::executeCommandBuffer + }; + + for (const QSize &size : sizes) { + for (const QVideoFrameFormat::PixelFormat pixelFormat : s_pixelFormats) { + for (const bool forceCpu : { false, true }) { + + if (pixelFormat == QVideoFrameFormat::Format_YUV420P10 && forceCpu) + continue; // TODO: Cpu conversion not implemented + + QString name = QStringLiteral("%1x%2_%3%4") + .arg(size.width()) + .arg(size.height()) + .arg(QVideoFrameFormat::pixelFormatToString(pixelFormat)) + .arg(forceCpu ? "_cpu" : ""); + + QTest::addRow("%s", name.toLatin1().data()) + << size << pixelFormat << forceCpu << isSupportedPixelFormat(pixelFormat); + } + } + } +} + +void tst_QVideoFrame::qImageFromVideoFrame_doesNotCrash_whenCalledWithEvenAndOddSizedFrames() { + QFETCH(const QSize, size); + QFETCH(const QVideoFrameFormat::PixelFormat, pixelFormat); + QFETCH(const bool, forceCpuConversion); + QFETCH(const bool, supportedOnPlatform); + + const QVideoFrameFormat format{ size, pixelFormat }; + const QVideoFrame frame{ format }; + const QImage actual = qImageFromVideoFrame(frame, QtVideo::Rotation::None, false, false, + forceCpuConversion); + + if (supportedOnPlatform) + QCOMPARE_EQ(actual.isNull(), size.isEmpty()); + // Otherwise, we don't expect an image being produced, although it might. + // TODO: Investigate why 16 bit formats fail on some Android flavors. +} + #define TEST_MAPPED(frame, mode) \ do { \ QVERIFY(frame.bits(0)); \ QVERIFY(frame.isMapped()); \ QCOMPARE(frame.mappedBytes(0), 16384); \ QCOMPARE(frame.bytesPerLine(0), 256); \ - QCOMPARE(frame.mapMode(), mode); \ + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), mode); \ } while (0) #define TEST_UNMAPPED(frame) \ @@ -832,7 +1097,7 @@ do { \ QVERIFY(!frame.isMapped()); \ QCOMPARE(frame.mappedBytes(0), 0); \ QCOMPARE(frame.bytesPerLine(0), 0); \ - QCOMPARE(frame.mapMode(), QVideoFrame::NotMapped); \ + QCOMPARE(static_cast<QtVideo::MapMode>(frame.mapMode()), QtVideo::MapMode::NotMapped); \ } while (0) void tst_QVideoFrame::isMapped() @@ -843,23 +1108,23 @@ void tst_QVideoFrame::isMapped() TEST_UNMAPPED(frame); TEST_UNMAPPED(constFrame); - QVERIFY(frame.map(QVideoFrame::ReadOnly)); - TEST_MAPPED(frame, QVideoFrame::ReadOnly); - TEST_MAPPED(constFrame, QVideoFrame::ReadOnly); + QVERIFY(frame.map(QtVideo::MapMode::ReadOnly)); + TEST_MAPPED(frame, QtVideo::MapMode::ReadOnly); + TEST_MAPPED(constFrame, QtVideo::MapMode::ReadOnly); frame.unmap(); TEST_UNMAPPED(frame); TEST_UNMAPPED(constFrame); - QVERIFY(frame.map(QVideoFrame::WriteOnly)); - TEST_MAPPED(frame, QVideoFrame::WriteOnly); - TEST_MAPPED(constFrame, QVideoFrame::WriteOnly); + QVERIFY(frame.map(QtVideo::MapMode::WriteOnly)); + TEST_MAPPED(frame, QtVideo::MapMode::WriteOnly); + TEST_MAPPED(constFrame, QtVideo::MapMode::WriteOnly); frame.unmap(); TEST_UNMAPPED(frame); TEST_UNMAPPED(constFrame); - QVERIFY(frame.map(QVideoFrame::ReadWrite)); - TEST_MAPPED(frame, QVideoFrame::ReadWrite); - TEST_MAPPED(constFrame, QVideoFrame::ReadWrite); + QVERIFY(frame.map(QtVideo::MapMode::ReadWrite)); + TEST_MAPPED(frame, QtVideo::MapMode::ReadWrite); + TEST_MAPPED(constFrame, QtVideo::MapMode::ReadWrite); frame.unmap(); TEST_UNMAPPED(frame); TEST_UNMAPPED(constFrame); @@ -872,17 +1137,17 @@ void tst_QVideoFrame::isReadable() QVERIFY(!frame.isMapped()); QVERIFY(!frame.isReadable()); - QVERIFY(frame.map(QVideoFrame::ReadOnly)); + QVERIFY(frame.map(QtVideo::MapMode::ReadOnly)); QVERIFY(frame.isMapped()); QVERIFY(frame.isReadable()); frame.unmap(); - QVERIFY(frame.map(QVideoFrame::WriteOnly)); + QVERIFY(frame.map(QtVideo::MapMode::WriteOnly)); QVERIFY(frame.isMapped()); QVERIFY(!frame.isReadable()); frame.unmap(); - QVERIFY(frame.map(QVideoFrame::ReadWrite)); + QVERIFY(frame.map(QtVideo::MapMode::ReadWrite)); QVERIFY(frame.isMapped()); QVERIFY(frame.isReadable()); frame.unmap(); @@ -895,17 +1160,17 @@ void tst_QVideoFrame::isWritable() QVERIFY(!frame.isMapped()); QVERIFY(!frame.isWritable()); - QVERIFY(frame.map(QVideoFrame::ReadOnly)); + QVERIFY(frame.map(QtVideo::MapMode::ReadOnly)); QVERIFY(frame.isMapped()); QVERIFY(!frame.isWritable()); frame.unmap(); - QVERIFY(frame.map(QVideoFrame::WriteOnly)); + QVERIFY(frame.map(QtVideo::MapMode::WriteOnly)); QVERIFY(frame.isMapped()); QVERIFY(frame.isWritable()); frame.unmap(); - QVERIFY(frame.map(QVideoFrame::ReadWrite)); + QVERIFY(frame.map(QtVideo::MapMode::ReadWrite)); QVERIFY(frame.isMapped()); QVERIFY(frame.isWritable()); frame.unmap(); @@ -915,94 +1180,284 @@ void tst_QVideoFrame::image_data() { QTest::addColumn<QSize>("size"); QTest::addColumn<QVideoFrameFormat::PixelFormat>("pixelFormat"); - QTest::addColumn<QImage::Format>("imageFormat"); QTest::newRow("64x64 ARGB32") << QSize(64, 64) - << QVideoFrameFormat::Format_ARGB8888 - << QImage::Format_ARGB32_Premultiplied; + << QVideoFrameFormat::Format_ARGB8888; QTest::newRow("64x64 ARGB32_Premultiplied") << QSize(64, 64) - << QVideoFrameFormat::Format_ARGB8888_Premultiplied - << QImage::Format_ARGB32_Premultiplied; + << QVideoFrameFormat::Format_ARGB8888_Premultiplied; QTest::newRow("64x64 RGB32") << QSize(64, 64) - << QVideoFrameFormat::Format_XRGB8888 - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_XRGB8888; QTest::newRow("64x64 BGRA32") << QSize(64, 64) - << QVideoFrameFormat::Format_BGRA8888 - << QImage::Format_ARGB32_Premultiplied; + << QVideoFrameFormat::Format_BGRA8888; QTest::newRow("64x64 BGRA32_Premultiplied") << QSize(64, 64) - << QVideoFrameFormat::Format_BGRA8888_Premultiplied - << QImage::Format_ARGB32_Premultiplied; + << QVideoFrameFormat::Format_BGRA8888_Premultiplied; QTest::newRow("64x64 BGR32") << QSize(64, 64) - << QVideoFrameFormat::Format_XBGR8888 - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_XBGR8888; QTest::newRow("64x64 AYUV") << QSize(64, 64) - << QVideoFrameFormat::Format_AYUV - << QImage::Format_ARGB32_Premultiplied; + << QVideoFrameFormat::Format_AYUV; QTest::newRow("64x64 YUV420P") << QSize(64, 64) - << QVideoFrameFormat::Format_YUV420P - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_YUV420P; QTest::newRow("64x64 YV12") << QSize(64, 64) - << QVideoFrameFormat::Format_YV12 - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_YV12; QTest::newRow("64x64 UYVY") << QSize(64, 64) - << QVideoFrameFormat::Format_UYVY - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_UYVY; QTest::newRow("64x64 YUYV") << QSize(64, 64) - << QVideoFrameFormat::Format_YUYV - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_YUYV; QTest::newRow("64x64 NV12") << QSize(64, 64) - << QVideoFrameFormat::Format_NV12 - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_NV12; QTest::newRow("64x64 NV21") << QSize(64, 64) - << QVideoFrameFormat::Format_NV21 - << QImage::Format_RGB32; + << QVideoFrameFormat::Format_NV21; } void tst_QVideoFrame::image() { QFETCH(QSize, size); QFETCH(QVideoFrameFormat::PixelFormat, pixelFormat); - QFETCH(QImage::Format, imageFormat); QVideoFrame frame(QVideoFrameFormat(size, pixelFormat)); QImage img = frame.toImage(); QVERIFY(!img.isNull()); - QCOMPARE(img.format(), imageFormat); QCOMPARE(img.size(), size); } void tst_QVideoFrame::emptyData() { QByteArray data(nullptr, 0); - QVideoFrame f(new QMemoryVideoBuffer(data, 600), - QVideoFrameFormat(QSize(800, 600), QVideoFrameFormat::Format_ARGB8888)); - QVERIFY(!f.map(QVideoFrame::ReadOnly)); + QVideoFrame f = QVideoFramePrivate::createFrame( + std::make_unique<QMemoryVideoBuffer>(data, 600), + QVideoFrameFormat(QSize(800, 600), QVideoFrameFormat::Format_ARGB8888)); + QVERIFY(!f.map(QtVideo::MapMode::ReadOnly)); +} + +void tst_QVideoFrame::mirrored_takesValue_fromVideoFrameFormat() +{ + QVideoFrameFormat format(QSize(10, 20), QVideoFrameFormat::Format_ARGB8888); + format.setMirrored(true); + + QVideoFrame frame(format); + QVERIFY(frame.mirrored()); + + frame.setMirrored(false); + frame.setRotation(QtVideo::Rotation::Clockwise180); + QVERIFY(!frame.mirrored()); + QVERIFY(!frame.surfaceFormat().isMirrored()); +} + +void tst_QVideoFrame::rotation_takesValue_fromVideoFrameFormat() +{ + QVideoFrameFormat format(QSize(10, 20), QVideoFrameFormat::Format_ARGB8888); + format.setRotation(QtVideo::Rotation::Clockwise270); + + QVideoFrame frame(format); + QCOMPARE(frame.rotation(), QtVideo::Rotation::Clockwise270); + + frame.setRotation(QtVideo::Rotation::Clockwise180); + + QCOMPARE(frame.rotation(), QtVideo::Rotation::Clockwise180); + QCOMPARE(frame.surfaceFormat().rotation(), QtVideo::Rotation::Clockwise180); +} + +void tst_QVideoFrame::streamFrameRate_takesValue_fromVideoFrameFormat() +{ + QVideoFrameFormat format(QSize(10, 20), QVideoFrameFormat::Format_ARGB8888); + format.setStreamFrameRate(20.); + + QVideoFrame frame(format); + QCOMPARE(frame.streamFrameRate(), 20.); + + frame.setStreamFrameRate(25.); + + QCOMPARE(frame.streamFrameRate(), 25.); + QCOMPARE(frame.surfaceFormat().streamFrameRate(), 25.); +} + +void tst_QVideoFrame::constructor_createsInvalidFrame_whenCalledWithNullImage() +{ + const QVideoFrame frame{ QImage{} }; + QVERIFY(!frame.isValid()); +} + +void tst_QVideoFrame::constructor_createsInvalidFrame_whenCalledWithEmptyImage() +{ + { + const QImage image{ QSize{}, QImage::Format_RGB32 }; + const QVideoFrame frame{ image }; + + QVERIFY(!frame.isValid()); + } + + { + const QImage image{ { 0, 0 }, QImage::Format_RGB32 }; + const QVideoFrame frame{ image }; + + QVERIFY(!frame.isValid()); + } + + { + const QImage image{ { 1, 0 }, QImage::Format_RGB32 }; + const QVideoFrame frame{ image }; + + QVERIFY(!frame.isValid()); + } + + { + const QImage image{ { 0, 1 }, QImage::Format_RGB32 }; + const QVideoFrame frame{ image }; + + QVERIFY(!frame.isValid()); + } +} + +void tst_QVideoFrame::constructor_createsInvalidFrame_whenCalledWithInvalidImageFormat() +{ + const QImage image{ { 1, 1 }, QImage::Format_Invalid}; + const QVideoFrame frame{ image }; + + QVERIFY(!frame.isValid()); +} + +// clang-format off +void tst_QVideoFrame::constructor_createsFrameWithCorrectFormat_whenCalledWithSupportedImageFormats_data() +{ + QTest::addColumn<QImage::Format>("imageFormat"); + QTest::addColumn<QVideoFrameFormat::PixelFormat>("expectedFrameFormat"); + + // Formats that do not require conversion + QTest::newRow("Format_RGB32") << QImage::Format_RGB32 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_ARGB32") << QImage::Format_ARGB32 << QVideoFrameFormat::Format_BGRA8888; + QTest::newRow("Format_ARGB32_Premultiplied") << QImage::Format_ARGB32_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_RGBA8888") << QImage::Format_RGBA8888 << QVideoFrameFormat::Format_RGBA8888; + QTest::newRow("Format_RGBA8888_Premultiplied") << QImage::Format_RGBA8888_Premultiplied << QVideoFrameFormat::Format_RGBX8888; + QTest::newRow("Format_RGBX8888") << QImage::Format_RGBX8888 << QVideoFrameFormat::Format_RGBX8888; + QTest::newRow("Format_Grayscale8") << QImage::Format_Grayscale8 << QVideoFrameFormat::Format_Y8; + QTest::newRow("Format_Grayscale16") << QImage::Format_Grayscale16 << QVideoFrameFormat::Format_Y16; + + // Formats that require conversion of input image + QTest::newRow("Format_Mono") << QImage::Format_Mono << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_MonoLSB") << QImage::Format_MonoLSB << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_Indexed8") << QImage::Format_Indexed8 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_RGB16") << QImage::Format_RGB16 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_RGB666") << QImage::Format_RGB666 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_ARGB6666_Premultiplied") << QImage::Format_ARGB6666_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_RGB555") << QImage::Format_RGB555 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_RGB888") << QImage::Format_RGB888 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_RGB444") << QImage::Format_RGB444 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_ARGB4444_Premultiplied") << QImage::Format_ARGB4444_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_BGR30") << QImage::Format_BGR30 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_A2BGR30_Premultiplied") << QImage::Format_A2BGR30_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_RGB30") << QImage::Format_RGB30 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_A2RGB30_Premultiplied") << QImage::Format_A2RGB30_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_Alpha8") << QImage::Format_Alpha8 << QVideoFrameFormat::Format_BGRA8888; + QTest::newRow("Format_RGBX64") << QImage::Format_RGBX64 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_RGBA64") << QImage::Format_RGBA64 << QVideoFrameFormat::Format_BGRA8888; + QTest::newRow("Format_RGBA64_Premultiplied") << QImage::Format_RGBA64_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_BGR888") << QImage::Format_BGR888 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_RGBX16FPx4") << QImage::Format_RGBX16FPx4 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_RGBA16FPx4") << QImage::Format_RGBA16FPx4 << QVideoFrameFormat::Format_BGRA8888; + QTest::newRow("Format_RGBA16FPx4_Premultiplied") << QImage::Format_RGBA16FPx4_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; + QTest::newRow("Format_RGBX32FPx4") << QImage::Format_RGBX32FPx4 << QVideoFrameFormat::Format_BGRX8888; + QTest::newRow("Format_RGBA32FPx4") << QImage::Format_RGBA32FPx4 << QVideoFrameFormat::Format_BGRA8888; + QTest::newRow("Format_RGBA32FPx4_Premultiplied") << QImage::Format_RGBA32FPx4_Premultiplied << QVideoFrameFormat::Format_BGRA8888_Premultiplied; +} +// clang-format on + +void tst_QVideoFrame::constructor_createsFrameWithCorrectFormat_whenCalledWithSupportedImageFormats() +{ + QFETCH(const QImage::Format, imageFormat); + QFETCH(QVideoFrameFormat::PixelFormat, expectedFrameFormat); + + const QImage image{ { 1, 1 }, imageFormat }; + const QVideoFrame frame{ image }; + + QVERIFY(frame.isValid()); + QCOMPARE_EQ(frame.pixelFormat(), expectedFrameFormat); +} + +// clang-format off +void tst_QVideoFrame::constructor_copiesImageData_whenCalledWithRGBFormats_data() +{ + QTest::addColumn<QImage::Format>("imageFormat"); + + // Formats that do not require image conversion + QTest::newRow("Format_RGB32") << QImage::Format_RGB32; + QTest::newRow("Format_RGBX8888") << QImage::Format_RGBX8888; + QTest::newRow("Format_ARGB32") << QImage::Format_ARGB32; + QTest::newRow("Format_ARGB32_Premultiplied") << QImage::Format_ARGB32_Premultiplied; + QTest::newRow("Format_RGBA8888") << QImage::Format_RGBA8888; + QTest::newRow("Format_RGBA8888_Premultiplied") << QImage::Format_RGBA8888_Premultiplied; + + // Formats that require image conversion + QTest::newRow("Format_Mono") << QImage::Format_Mono; + QTest::newRow("Format_MonoLSB") << QImage::Format_MonoLSB; + QTest::newRow("Format_Indexed8") << QImage::Format_Indexed8; + QTest::newRow("Format_RGB16") << QImage::Format_RGB16; + QTest::newRow("Format_ARGB8565_Premultiplied") << QImage::Format_ARGB8565_Premultiplied; + QTest::newRow("Format_RGB666") << QImage::Format_RGB666; + QTest::newRow("Format_ARGB6666_Premultiplied") << QImage::Format_ARGB6666_Premultiplied; + QTest::newRow("Format_RGB555") << QImage::Format_RGB555; + QTest::newRow("Format_ARGB8555_Premultiplied") << QImage::Format_ARGB8555_Premultiplied; + QTest::newRow("Format_RGB888") << QImage::Format_RGB888; + QTest::newRow("Format_RGB444") << QImage::Format_RGB444; + QTest::newRow("Format_ARGB4444_Premultiplied") << QImage::Format_ARGB4444_Premultiplied; + QTest::newRow("Format_BGR30") << QImage::Format_BGR30; + QTest::newRow("Format_A2BGR30_Premultiplied") << QImage::Format_A2BGR30_Premultiplied; + QTest::newRow("Format_RGB30") << QImage::Format_RGB30; + QTest::newRow("Format_A2RGB30_Premultiplied") << QImage::Format_A2RGB30_Premultiplied; + QTest::newRow("Format_Alpha8") << QImage::Format_Alpha8; + QTest::newRow("Format_RGBX64") << QImage::Format_RGBX64; + QTest::newRow("Format_RGBA64") << QImage::Format_RGBA64; + QTest::newRow("Format_RGBA64_Premultiplied") << QImage::Format_RGBA64_Premultiplied; + QTest::newRow("Format_BGR888") << QImage::Format_BGR888; + QTest::newRow("Format_RGBX16FPx4") << QImage::Format_RGBX16FPx4; + QTest::newRow("Format_RGBA16FPx4") << QImage::Format_RGBA16FPx4; + QTest::newRow("Format_RGBA16FPx4_Premultiplied") << QImage::Format_RGBA16FPx4_Premultiplied; + QTest::newRow("Format_RGBX32FPx4") << QImage::Format_RGBX32FPx4; + QTest::newRow("Format_RGBA32FPx4") << QImage::Format_RGBA32FPx4; + QTest::newRow("Format_RGBA32FPx4_Premultiplied") << QImage::Format_RGBA32FPx4_Premultiplied; +} + +// clang-format on + +void tst_QVideoFrame::constructor_copiesImageData_whenCalledWithRGBFormats() +{ + QFETCH(const QImage::Format, imageFormat); + + // Arrange + const QImage image{ createTestImage(imageFormat) }; + + // Act + QVideoFrame frame{ image }; + + // Assert + QVERIFY(compareEq(frame, image)); } QTEST_MAIN(tst_QVideoFrame) diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt b/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt new file mode 100644 index 000000000..d2e3086d2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2023 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_qvideoframecolormanagement + SOURCES + tst_qvideoframecolormanagement.cpp + LIBRARIES + Qt::Gui + Qt::MultimediaPrivate + BUILTIN_TESTDATA + TESTDATA ${testdata_resource_files} +) diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg Binary files differnew file mode 100644 index 000000000..52b0f620b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_abgr8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_argb8888_premultiplied_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgra8888_premultiplied_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_bgrx8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc1_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full.png Binary files differnew file mode 100644 index 000000000..90b2b3601 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video.png Binary files differnew file mode 100644 index 000000000..90b2b3601 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full.png Binary files differnew file mode 100644 index 000000000..2e78cfc31 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video.png Binary files differnew file mode 100644 index 000000000..d673b7ce5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full.png Binary files differnew file mode 100644 index 000000000..8be30a706 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video.png Binary files differnew file mode 100644 index 000000000..1f64ea0f1 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full.png Binary files differnew file mode 100644 index 000000000..24fb9065e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video.png Binary files differnew file mode 100644 index 000000000..f737d8602 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc2_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc3_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full.png Binary files differnew file mode 100644 index 000000000..6efa73ea2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video.png Binary files differnew file mode 100644 index 000000000..6efa73ea2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full.png Binary files differnew file mode 100644 index 000000000..8d6a36a1c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video.png Binary files differnew file mode 100644 index 000000000..dab23bf0d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full.png Binary files differnew file mode 100644 index 000000000..36e787cef --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video.png Binary files differnew file mode 100644 index 000000000..01e6ab967 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full.png Binary files differnew file mode 100644 index 000000000..22beff2e8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video.png Binary files differnew file mode 100644 index 000000000..c2af074b8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_imc4_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv12_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_nv21_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full.png Binary files differnew file mode 100644 index 000000000..71e107b8a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..1242dd25b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video.png Binary files differnew file mode 100644 index 000000000..71e107b8a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..1242dd25b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full.png Binary files differnew file mode 100644 index 000000000..58a7ebc92 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..4286840f8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video.png Binary files differnew file mode 100644 index 000000000..d8756caac --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..fb6d356f8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full.png Binary files differnew file mode 100644 index 000000000..905568bf9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..d819e478c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video.png Binary files differnew file mode 100644 index 000000000..f374df207 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..2fbc2225c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full.png Binary files differnew file mode 100644 index 000000000..d2ee0f8e2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..d819e478c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video.png Binary files differnew file mode 100644 index 000000000..740de7f79 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..d19223883 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p010_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full.png Binary files differnew file mode 100644 index 000000000..ad76d393a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..68509c232 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video.png Binary files differnew file mode 100644 index 000000000..ad76d393a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..68509c232 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full.png Binary files differnew file mode 100644 index 000000000..a6e47132c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2cb927d35 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video.png Binary files differnew file mode 100644 index 000000000..d9760b9c9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..3f65f27db --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full.png Binary files differnew file mode 100644 index 000000000..04ae5e1cd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..299548d61 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video.png Binary files differnew file mode 100644 index 000000000..9faa15fad --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..d544f8767 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full.png Binary files differnew file mode 100644 index 000000000..84b04ff9e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..299548d61 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video.png Binary files differnew file mode 100644 index 000000000..505752c10 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7da761925 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_p016_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgba8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_rgbx8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full.png Binary files differnew file mode 100644 index 000000000..c5243c441 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video.png Binary files differnew file mode 100644 index 000000000..c5243c441 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full.png Binary files differnew file mode 100644 index 000000000..0a9874943 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..126744377 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video.png Binary files differnew file mode 100644 index 000000000..7318c1e99 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..908b28c2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full.png Binary files differnew file mode 100644 index 000000000..68789bef5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video.png Binary files differnew file mode 100644 index 000000000..bfd6396ec --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..03e337184 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full.png Binary files differnew file mode 100644 index 000000000..704c59cf9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video.png Binary files differnew file mode 100644 index 000000000..d9ad9c239 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..0030ce5bc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_uyvy_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xbgr8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video.png Binary files differnew file mode 100644 index 000000000..682e999cc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..7d1d73109 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_xrgb8888_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full.png Binary files differnew file mode 100644 index 000000000..b1dc781f2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..584ad4c25 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video.png Binary files differnew file mode 100644 index 000000000..b1dc781f2 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..584ad4c25 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full.png Binary files differnew file mode 100644 index 000000000..619ee36a4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..16445be0c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video.png Binary files differnew file mode 100644 index 000000000..881f6be33 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..9c7e87238 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full.png Binary files differnew file mode 100644 index 000000000..b1d3111df --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..1a3025e2d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video.png Binary files differnew file mode 100644 index 000000000..e4d1ce940 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..614b71e3e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full.png Binary files differnew file mode 100644 index 000000000..b1d3111df --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..1a3025e2d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video.png Binary files differnew file mode 100644 index 000000000..df8df3edd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..d6bed0482 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y16_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full.png Binary files differnew file mode 100644 index 000000000..130a3b541 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..61d2c6ca0 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video.png Binary files differnew file mode 100644 index 000000000..130a3b541 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..61d2c6ca0 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full.png Binary files differnew file mode 100644 index 000000000..21ed2218a --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..188efe1d9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video.png Binary files differnew file mode 100644 index 000000000..f60f53d02 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..512c467b6 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full.png Binary files differnew file mode 100644 index 000000000..df59b71e7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..bfc57d849 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video.png Binary files differnew file mode 100644 index 000000000..dbca71c70 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..52f4b0223 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full.png Binary files differnew file mode 100644 index 000000000..df59b71e7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..bfc57d849 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video.png Binary files differnew file mode 100644 index 000000000..3479bb890 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..b3a488e2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_y8_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_full.png Binary files differnew file mode 100644 index 000000000..20b24da65 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_video.png Binary files differnew file mode 100644 index 000000000..20b24da65 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_full.png Binary files differnew file mode 100644 index 000000000..b96379a0b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_video.png Binary files differnew file mode 100644 index 000000000..c77645b59 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_full.png Binary files differnew file mode 100644 index 000000000..a1b8b62da --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_video.png Binary files differnew file mode 100644 index 000000000..7a69f6afa --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_full.png Binary files differnew file mode 100644 index 000000000..644b083fe --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_video.png Binary files differnew file mode 100644 index 000000000..d4e9debd7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p10_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv420p_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video.png Binary files differnew file mode 100644 index 000000000..3e255af2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full.png Binary files differnew file mode 100644 index 000000000..74fd12726 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..126744377 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video.png Binary files differnew file mode 100644 index 000000000..e358d16d8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..908b28c2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full.png Binary files differnew file mode 100644 index 000000000..cb1cbbd34 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video.png Binary files differnew file mode 100644 index 000000000..6dd95a078 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..03e337184 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full.png Binary files differnew file mode 100644 index 000000000..3e92f3695 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video.png Binary files differnew file mode 100644 index 000000000..e94891e1c --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..0030ce5bc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuv422p_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full.png Binary files differnew file mode 100644 index 000000000..c5243c441 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video.png Binary files differnew file mode 100644 index 000000000..c5243c441 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..6a0cb7dd8 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full.png Binary files differnew file mode 100644 index 000000000..0a9874943 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..126744377 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video.png Binary files differnew file mode 100644 index 000000000..7318c1e99 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..908b28c2f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full.png Binary files differnew file mode 100644 index 000000000..68789bef5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video.png Binary files differnew file mode 100644 index 000000000..bfd6396ec --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..03e337184 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full.png Binary files differnew file mode 100644 index 000000000..704c59cf9 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..7ef68b58b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video.png Binary files differnew file mode 100644 index 000000000..d9ad9c239 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..0030ce5bc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yuyv_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video.png Binary files differnew file mode 100644 index 000000000..2af7cdaa4 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video_cpu.png Binary files differnew file mode 100644 index 000000000..12685832f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_adobergb_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full.png Binary files differnew file mode 100644 index 000000000..d6d461f5d --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full_cpu.png Binary files differnew file mode 100644 index 000000000..2138f7b91 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video.png Binary files differnew file mode 100644 index 000000000..2a4f7d8a7 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video_cpu.png Binary files differnew file mode 100644 index 000000000..0de0ffbc5 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt2020_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full.png Binary files differnew file mode 100644 index 000000000..d291f62bb --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video.png Binary files differnew file mode 100644 index 000000000..35296fc03 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video_cpu.png Binary files differnew file mode 100644 index 000000000..7b198e19b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt601_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full.png Binary files differnew file mode 100644 index 000000000..64e5eb6dc --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full_cpu.png Binary files differnew file mode 100644 index 000000000..33229f55f --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_full_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video.png Binary files differnew file mode 100644 index 000000000..9f6bdd1ea --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video_cpu.png b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video_cpu.png Binary files differnew file mode 100644 index 000000000..74b3efccd --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/testdata/umbrellas.jpg_yv12_bt709_video_cpu.png diff --git a/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp b/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp new file mode 100644 index 000000000..ad221e54e --- /dev/null +++ b/tests/auto/unit/multimedia/qvideoframecolormanagement/tst_qvideoframecolormanagement.cpp @@ -0,0 +1,475 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtTest/QtTest> + +#include <qvideoframe.h> +#include <qvideoframeformat.h> +#include "private/qmemoryvideobuffer_p.h" +#include "private/qvideoframeconverter_p.h" +#include "private/qplatformmediaintegration_p.h" +#include "private/qimagevideobuffer_p.h" +#include "private/qvideoframe_p.h" +#include <QtGui/QColorSpace> +#include <QtGui/QImage> +#include <QtCore/QPointer> + +#include "../../../integration/shared/mediabackendutils.h" + +QT_USE_NAMESPACE + +namespace { + +struct TestParams +{ + QString fileName; + QVideoFrameFormat::PixelFormat pixelFormat; + QVideoFrameFormat::ColorSpace colorSpace; + QVideoFrameFormat::ColorRange colorRange; + bool forceCpu; +}; + +QString toString(QVideoFrameFormat::ColorRange r) +{ + switch (r) { + case QVideoFrameFormat::ColorRange_Video: + return "Video"; + case QVideoFrameFormat::ColorRange_Full: + return "Full"; + default: + Q_ASSERT(false); + return ""; + } +} + +std::vector<QVideoFrameFormat::ColorRange> colorRanges() +{ + return { + QVideoFrameFormat::ColorRange_Video, + QVideoFrameFormat::ColorRange_Full, + }; +} + +const QSet s_formats{ QVideoFrameFormat::Format_ARGB8888, + QVideoFrameFormat::Format_ARGB8888_Premultiplied, + QVideoFrameFormat::Format_XRGB8888, + QVideoFrameFormat::Format_BGRA8888, + QVideoFrameFormat::Format_BGRA8888_Premultiplied, + QVideoFrameFormat::Format_BGRX8888, + QVideoFrameFormat::Format_ABGR8888, + QVideoFrameFormat::Format_XBGR8888, + QVideoFrameFormat::Format_RGBA8888, + QVideoFrameFormat::Format_RGBX8888, + QVideoFrameFormat::Format_NV12, + QVideoFrameFormat::Format_NV21, + QVideoFrameFormat::Format_IMC1, + QVideoFrameFormat::Format_IMC2, + QVideoFrameFormat::Format_IMC3, + QVideoFrameFormat::Format_IMC4, + QVideoFrameFormat::Format_AYUV, + QVideoFrameFormat::Format_AYUV_Premultiplied, + QVideoFrameFormat::Format_YV12, + QVideoFrameFormat::Format_YUV420P, + QVideoFrameFormat::Format_YUV422P, + QVideoFrameFormat::Format_UYVY, + QVideoFrameFormat::Format_YUYV, + QVideoFrameFormat::Format_Y8, + QVideoFrameFormat::Format_Y16, + QVideoFrameFormat::Format_P010, + QVideoFrameFormat::Format_P016, + QVideoFrameFormat::Format_YUV420P10 }; + +bool hasCorrespondingFFmpegFormat(QVideoFrameFormat::PixelFormat format) +{ + return format != QVideoFrameFormat::Format_AYUV + && format != QVideoFrameFormat::Format_AYUV_Premultiplied; +} + +bool supportsCpuConversion(QVideoFrameFormat::PixelFormat format) +{ + return format != QVideoFrameFormat::Format_YUV420P10; +} + +QString toString(QVideoFrameFormat::PixelFormat f) +{ + return QVideoFrameFormat::pixelFormatToString(f); +} + +QSet<QVideoFrameFormat::PixelFormat> pixelFormats() +{ + return s_formats; +} + +bool isSupportedPixelFormat(QVideoFrameFormat::PixelFormat pixelFormat) +{ +#ifdef Q_OS_ANDROID + // TODO: QTBUG-125238 + switch (pixelFormat) { + case QVideoFrameFormat::Format_Y16: + case QVideoFrameFormat::Format_P010: + case QVideoFrameFormat::Format_P016: + case QVideoFrameFormat::Format_YUV420P10: + return false; + default: + return true; + } +#else + return true; +#endif +} + + +QString toString(QVideoFrameFormat::ColorSpace s) +{ + switch (s) { + case QVideoFrameFormat::ColorSpace_BT601: + return "BT601"; + case QVideoFrameFormat::ColorSpace_BT709: + return "BT709"; + case QVideoFrameFormat::ColorSpace_AdobeRgb: + return "AdobeRgb"; + case QVideoFrameFormat::ColorSpace_BT2020: + return "BT2020"; + default: + Q_ASSERT(false); + return ""; + } +} + +std::vector<QVideoFrameFormat::ColorSpace> colorSpaces() +{ + return { QVideoFrameFormat::ColorSpace_BT601, QVideoFrameFormat::ColorSpace_BT709, + QVideoFrameFormat::ColorSpace_AdobeRgb, QVideoFrameFormat::ColorSpace_BT2020 }; +} + +QString name(const TestParams &p) +{ + QString name = QStringLiteral("%1_%2_%3_%4%5") + .arg(p.fileName) + .arg(toString(p.pixelFormat)) + .arg(toString(p.colorSpace)) + .arg(toString(p.colorRange)) + .arg(p.forceCpu ? "_cpu" : "") + .toLower(); + name.replace(" ", "_"); + return name; +} + +QString path(const QTemporaryDir &dir, const TestParams ¶m, const QString &suffix = ".png") +{ + return dir.filePath(name(param) + suffix); +} + +QVideoFrame createTestFrame(const TestParams ¶ms, const QImage &image) +{ + QVideoFrameFormat format(image.size(), params.pixelFormat); + format.setColorRange(params.colorRange); + format.setColorSpace(params.colorSpace); + format.setColorTransfer(QVideoFrameFormat::ColorTransfer_Unknown); + + auto buffer = std::make_unique<QImageVideoBuffer>(image); + QVideoFrameFormat imageFormat = { + image.size(), QVideoFrameFormat::pixelFormatFromImageFormat(image.format()) + }; + + QVideoFrame source = QVideoFramePrivate::createFrame(std::move(buffer), imageFormat); + return QPlatformMediaIntegration::instance()->convertVideoFrame(source, format); +} + +struct ImageDiffReport +{ + int DiffCountAboveThreshold; // Number of channel differences above threshold + int MaxDiff; // Maximum difference between two images (max across channels) + int PixelCount; // Number of pixels in the image + QImage DiffImage; // The difference image (absolute per-channel difference) +}; + +double aboveThresholdDiffRatio(const ImageDiffReport &report) +{ + return static_cast<double>(report.DiffCountAboveThreshold) / report.PixelCount; +} + +int maxChannelDiff(QRgb lhs, QRgb rhs) +{ + // clang-format off + return std::max({ std::abs(qRed(lhs) - qRed(rhs)), + std::abs(qGreen(lhs) - qGreen(rhs)), + std::abs(qBlue(lhs) - qBlue(rhs)) }); + // clang-format on +} + +int clampedAbsDiff(int lhs, int rhs) +{ + return std::clamp(std::abs(lhs - rhs), 0, 255); +} + +QRgb pixelDiff(QRgb lhs, QRgb rhs) +{ + return qRgb(clampedAbsDiff(qRed(lhs), qRed(rhs)), clampedAbsDiff(qGreen(lhs), qGreen(rhs)), + clampedAbsDiff(qBlue(lhs), qBlue(rhs))); +} + +std::optional<ImageDiffReport> compareImagesRgb32(const QImage &computed, const QImage &baseline, + int channelThreshold) +{ + Q_ASSERT(baseline.format() == QImage::Format_RGB32); + + if (computed.size() != baseline.size()) + return {}; + + if (computed.format() != baseline.format()) + return {}; + + if (computed.colorSpace() != baseline.colorSpace()) + return {}; + + const QSize size = baseline.size(); + + ImageDiffReport report{}; + report.PixelCount = size.width() * size.height(); + report.DiffImage = QImage(size, baseline.format()); + + // Iterate over all pixels and update report + for (int l = 0; l < size.height(); l++) { + const QRgb *colorComputed = reinterpret_cast<const QRgb *>(computed.constScanLine(l)); + const QRgb *colorBaseline = reinterpret_cast<const QRgb *>(baseline.constScanLine(l)); + QRgb *colorDiff = reinterpret_cast<QRgb *>(report.DiffImage.scanLine(l)); + + int w = size.width(); + while (w--) { + *colorDiff = pixelDiff(*colorComputed, *colorBaseline); + if (*colorComputed != *colorBaseline) { + const int diff = maxChannelDiff(*colorComputed, *colorBaseline); + + if (diff > report.MaxDiff) + report.MaxDiff = diff; + + if (diff > channelThreshold) + ++report.DiffCountAboveThreshold; + } + + ++colorComputed; + ++colorBaseline; + ++colorDiff; + } + } + return report; +} + +bool copyAllFiles(const QDir &source, const QDir &dest) +{ + if (!source.exists() || !dest.exists()) + return false; + + QDirIterator it(source); + while (it.hasNext()) { + QFileInfo file{ it.next() }; + if (file.isFile()) { + const QString destination = dest.absolutePath() + "/" + file.fileName(); + QFile::copy(file.absoluteFilePath(), destination); + } + } + + return true; +} + +class ReferenceData +{ +public: + ReferenceData() + { + m_testdataDir = QTest::qExtractTestData("testdata"); + if (!m_testdataDir) + m_testdataDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir); + } + + ~ReferenceData() + { + if (m_testdataDir->autoRemove()) + return; + + QString resultPath = m_testdataDir->path(); + if (qEnvironmentVariableIsSet("COIN_CTEST_RESULTSDIR")) { + const QDir sourceDir = m_testdataDir->path(); + const QDir resultsDir{ qEnvironmentVariable("COIN_CTEST_RESULTSDIR") }; + if (!copyAllFiles(sourceDir, resultsDir)) { + qDebug() << "Failed to copy files to COIN_CTEST_RESULTSDIR"; + } else { + resultPath = resultsDir.path(); + } + } + + qDebug() << "Images with differences were found. The output images with differences" + << "can be found in" << resultPath << ". Review the images and if the" + << "differences are expected, please update the testdata with the new" + << "output images"; + } + + QImage getReference(const TestParams ¶m) const + { + const QString referenceName = name(param); + const QString referencePath = m_testdataDir->filePath(referenceName + ".png"); + QImage result; + if (result.load(referencePath)) + return result; + return {}; + } + + void saveNewReference(const QImage &reference, const TestParams ¶ms) const + { + const QString filename = path(*m_testdataDir, params); + if (!reference.save(filename)) { + qDebug() << "Failed to save reference file"; + Q_ASSERT(false); + } + + m_testdataDir->setAutoRemove(false); + } + + bool saveComputedImage(const TestParams ¶ms, const QImage &image, const QString& suffix) const + { + if (!image.save(path(*m_testdataDir, params, suffix))) { + qDebug() << "Unexpectedly failed to save actual image to file"; + Q_ASSERT(false); + return false; + } + m_testdataDir->setAutoRemove(false); + return true; + } + + QImage getTestdata(const QString &name) + { + const QString filePath = m_testdataDir->filePath(name); + QImage image; + if (image.load(filePath)) + return image; + return {}; + } + +private: + QSharedPointer<QTemporaryDir> m_testdataDir; +}; + +std::optional<ImageDiffReport> compareToReference(const TestParams ¶ms, const QImage &actual, + const ReferenceData &references, + int maxChannelThreshold) +{ + const QImage expected = references.getReference(params); + if (expected.isNull()) { + // Reference image does not exist. Create one. Adding this to + // testdata directory is a manual job. + references.saveNewReference(actual, params); + qDebug() << "Reference image is missing. Please update testdata directory with the missing " + "reference image"; + return {}; + } + + // Convert to RGB32 to simplify image comparison + const QImage computed = actual.convertToFormat(QImage::Format_RGB32); + const QImage baseline = expected.convertToFormat(QImage::Format_RGB32); + + std::optional<ImageDiffReport> diffReport = compareImagesRgb32(computed, baseline, maxChannelThreshold); + if (!diffReport) + return {}; + + if (diffReport->MaxDiff > 0) { + // Images are not equal, and may require manual inspection + if (!references.saveComputedImage(params, computed, "_actual.png")) + return {}; + if (!references.saveComputedImage(params, diffReport->DiffImage, "_diff.png")) + return {}; + } + + return diffReport; +} + +} // namespace + +class tst_qvideoframecolormanagement : public QObject +{ + Q_OBJECT +private slots: + void initTestCase() + { + if (!isFFMPEGPlatform()) + QSKIP("This test requires the ffmpeg backend to create test frames"); + } + + void qImageFromVideoFrame_returnsQImageWithCorrectColors_data() + { + QTest::addColumn<QString>("fileName"); + QTest::addColumn<TestParams>("params"); + for (const char *file : { "umbrellas.jpg" }) { + for (const QVideoFrameFormat::PixelFormat pixelFormat : pixelFormats()) { + for (const QVideoFrameFormat::ColorSpace colorSpace : colorSpaces()) { + for (const QVideoFrameFormat::ColorRange colorRange : colorRanges()) { + for (const bool forceCpu : { false, true }) { + + if (!isSupportedPixelFormat(pixelFormat)) + continue; + + if (forceCpu && !supportsCpuConversion(pixelFormat)) + continue; // TODO: CPU Conversion not implemented + + if (!hasCorrespondingFFmpegFormat(pixelFormat)) + continue; + + TestParams param{ + file, pixelFormat, colorSpace, colorRange, forceCpu, + }; + QTest::addRow("%s", name(param).toLatin1().data()) << file << param; + } + } + } + } + } + } + + // This test is a regression test for the QMultimedia display pipeline. + // It compares rendered output (as created by qImageFromVideoFrame) + // against reference images stored to file. The reference images were + // created by the test itself, and does not verify correctness, just + // changes to render output. + void qImageFromVideoFrame_returnsQImageWithCorrectColors() + { + QFETCH(const QString, fileName); + QFETCH(const TestParams, params); + + const QImage templateImage = m_reference.getTestdata(fileName); + QVERIFY(!templateImage.isNull()); + + const QVideoFrame frame = createTestFrame(params, templateImage); + + // Act + const QImage actual = + qImageFromVideoFrame(frame, QtVideo::Rotation::None, false, false, params.forceCpu); + + // Assert + constexpr int diffThreshold = 4; + std::optional<ImageDiffReport> result = + compareToReference(params, actual, m_reference, diffThreshold); + + // Sanity checks + QVERIFY(result.has_value()); + QCOMPARE_GT(result->PixelCount, 0); + + // Verify that images are similar + const double ratioAboveThreshold = + static_cast<double>(result->DiffCountAboveThreshold) / result->PixelCount; + + // These thresholds are empirically determined to allow tests to pass in CI. + // If tests fail, review the difference between the reference and actual + // output to determine if it is a platform dependent inaccuracy before + // adjusting the limits + QCOMPARE_LT(ratioAboveThreshold, 0.01); // Fraction of pixels with larger differences + QCOMPARE_LT(result->MaxDiff, 6); // Maximum per-channel difference + } + + +private: + ReferenceData m_reference; +}; + +QTEST_MAIN(tst_qvideoframecolormanagement) + +#include "tst_qvideoframecolormanagement.moc" diff --git a/tests/auto/unit/multimedia/qvideoframeformat/CMakeLists.txt b/tests/auto/unit/multimedia/qvideoframeformat/CMakeLists.txt index af14e67b0..0d1b92d55 100644 --- a/tests/auto/unit/multimedia/qvideoframeformat/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qvideoframeformat/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qvideoframeformat.pro. ##################################################################### @@ -7,7 +10,7 @@ qt_internal_add_test(tst_qvideoframeformat SOURCES tst_qvideoframeformat.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate ) diff --git a/tests/auto/unit/multimedia/qvideoframeformat/tst_qvideoframeformat.cpp b/tests/auto/unit/multimedia/qvideoframeformat/tst_qvideoframeformat.cpp index e2ab89a41..41d54de0d 100644 --- a/tests/auto/unit/multimedia/qvideoframeformat/tst_qvideoframeformat.cpp +++ b/tests/auto/unit/multimedia/qvideoframeformat/tst_qvideoframeformat.cpp @@ -1,32 +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$ -** -****************************************************************************/ - -//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> @@ -57,6 +30,8 @@ private slots: void construct(); void frameSize_data(); void frameSize(); + void planeCount_returnsNumberOfColorPlanesDictatedByPixelFormat_data() const; + void planeCount_returnsNumberOfColorPlanesDictatedByPixelFormat() const; void viewport_data(); void viewport(); void scanLineDirection_data(); @@ -109,7 +84,7 @@ void tst_QVideoFrameFormat::constructNull() QCOMPARE(format.frameHeight(), -1); QCOMPARE(format.viewport(), QRect()); QCOMPARE(format.scanLineDirection(), QVideoFrameFormat::TopToBottom); - QCOMPARE(format.frameRate(), 0.0); + QCOMPARE(format.streamFrameRate(), 0.0); QCOMPARE(format.colorSpace(), QVideoFrameFormat::ColorSpace_Undefined); } @@ -157,7 +132,7 @@ void tst_QVideoFrameFormat::construct() QCOMPARE(format.isValid(), valid); QCOMPARE(format.viewport(), viewport); QCOMPARE(format.scanLineDirection(), QVideoFrameFormat::TopToBottom); - QCOMPARE(format.frameRate(), 0.0); + QCOMPARE(format.streamFrameRate(), 0.0); QCOMPARE(format.colorSpace(), QVideoFrameFormat::ColorSpace_Undefined); } @@ -191,6 +166,59 @@ void tst_QVideoFrameFormat::frameSize() QCOMPARE(format.frameHeight(), newSize.height()); } +void tst_QVideoFrameFormat::planeCount_returnsNumberOfColorPlanesDictatedByPixelFormat_data() const { + QTest::addColumn<QVideoFrameFormat::PixelFormat>("pixelFormat"); + QTest::addColumn<int>("colorPlanes"); // Number of planes as specified by QVideoFrameFormat::PixelFormat documentation + + QTest::newRow("ARGB8888") << QVideoFrameFormat::Format_ARGB8888 << 1; + QTest::newRow("ARGB8888_Premultiplied") << QVideoFrameFormat::Format_ARGB8888_Premultiplied << 1; + QTest::newRow("XRGB8888") << QVideoFrameFormat::Format_XRGB8888 << 1; + QTest::newRow("BGRA8888") << QVideoFrameFormat::Format_BGRA8888 << 1; + QTest::newRow("BGRA8888_Premultiplied") << QVideoFrameFormat::Format_BGRA8888_Premultiplied << 1; + QTest::newRow("BGRX8888") << QVideoFrameFormat::Format_BGRX8888 << 1; + QTest::newRow("ABGR8888") << QVideoFrameFormat::Format_ABGR8888 << 1; + QTest::newRow("XBGR8888") << QVideoFrameFormat::Format_XBGR8888 << 1; + QTest::newRow("RGBA8888") << QVideoFrameFormat::Format_RGBA8888 << 1; + QTest::newRow("RGBX8888") << QVideoFrameFormat::Format_RGBX8888 << 1; + + QTest::newRow("AUYVY") << QVideoFrameFormat::Format_AYUV << 1; + QTest::newRow("AYUV_Premultiplied") << QVideoFrameFormat::Format_AYUV_Premultiplied << 1; + QTest::newRow("YUV420P") << QVideoFrameFormat::Format_YUV420P << 3; + QTest::newRow("YUV422P") << QVideoFrameFormat::Format_YUV422P << 3; + QTest::newRow("YV12") << QVideoFrameFormat::Format_YV12 << 3; + + QTest::newRow("UYVY") << QVideoFrameFormat::Format_UYVY << 1; + QTest::newRow("YUYV") << QVideoFrameFormat::Format_YUYV << 1; + QTest::newRow("NV12") << QVideoFrameFormat::Format_NV12 << 2; + QTest::newRow("NV21") << QVideoFrameFormat::Format_NV21 << 2; + + QTest::newRow("IMC1") << QVideoFrameFormat::Format_IMC1 << 3; + QTest::newRow("IMC2") << QVideoFrameFormat::Format_IMC2 << 2; + QTest::newRow("IMC3") << QVideoFrameFormat::Format_IMC3 << 3; + QTest::newRow("IMC4") << QVideoFrameFormat::Format_IMC4 << 2; + + QTest::newRow("Y8") << QVideoFrameFormat::Format_Y8 << 1; + QTest::newRow("Y16") << QVideoFrameFormat::Format_Y16 << 1; + + QTest::newRow("P010") << QVideoFrameFormat::Format_P010 << 2; + QTest::newRow("P016") << QVideoFrameFormat::Format_P016 << 2; + + QTest::newRow("SamplerExternalOES") << QVideoFrameFormat::Format_SamplerExternalOES << 1; + QTest::newRow("Jpeg") << QVideoFrameFormat::Format_Jpeg << 1; + QTest::newRow("SamplerRect") << QVideoFrameFormat::Format_SamplerRect << 1; + + QTest::newRow("YUV420P10") << QVideoFrameFormat::Format_YUV420P10 << 3; +} + +void tst_QVideoFrameFormat::planeCount_returnsNumberOfColorPlanesDictatedByPixelFormat() const { + QFETCH(QVideoFrameFormat::PixelFormat, pixelFormat); + QFETCH(int, colorPlanes); + + const QVideoFrameFormat frameFormat = QVideoFrameFormat({}, pixelFormat); + + QCOMPARE_EQ(frameFormat.planeCount(), colorPlanes); +} + void tst_QVideoFrameFormat::viewport_data() { QTest::addColumn<QSize>("initialSize"); @@ -307,9 +335,9 @@ void tst_QVideoFrameFormat::frameRate() QVideoFrameFormat format(QSize(64, 64), QVideoFrameFormat::Format_XRGB8888); - format.setFrameRate(frameRate); + format.setStreamFrameRate(frameRate); - QCOMPARE(format.frameRate(), frameRate); + QCOMPARE(format.streamFrameRate(), frameRate); } void tst_QVideoFrameFormat::compare() @@ -367,13 +395,13 @@ void tst_QVideoFrameFormat::compare() QCOMPARE(format1 == format2, true); QCOMPARE(format1 != format2, false); - format1.setFrameRate(7.5); + format1.setStreamFrameRate(7.5); // Not equal frame rate differs. QCOMPARE(format1 == format2, false); QCOMPARE(format1 != format2, true); - format2.setFrameRate(qreal(7.50001)); + format2.setStreamFrameRate(qreal(7.50001)); // Equal. QCOMPARE(format1 == format2, true); @@ -478,7 +506,7 @@ void tst_QVideoFrameFormat::copyAllParameters() original.setScanLineDirection(QVideoFrameFormat::BottomToTop); original.setViewport(QRect(0, 0, 1024, 1024)); - original.setFrameRate(qreal(15.0)); + original.setStreamFrameRate(qreal(15.0)); original.setColorSpace(QVideoFrameFormat::ColorSpace_BT709); /* Copy the original instance to copy and verify if both the instances @@ -489,7 +517,7 @@ void tst_QVideoFrameFormat::copyAllParameters() QCOMPARE(copy.frameSize(), QSize(1024, 768)); QCOMPARE(copy.scanLineDirection(), QVideoFrameFormat::BottomToTop); QCOMPARE(copy.viewport(), QRect(0, 0, 1024, 1024)); - QCOMPARE(copy.frameRate(), qreal(15.0)); + QCOMPARE(copy.streamFrameRate(), qreal(15.0)); QCOMPARE(copy.colorSpace(), QVideoFrameFormat::ColorSpace_BT709); /* Verify if both the instances are eqaul */ @@ -505,7 +533,7 @@ void tst_QVideoFrameFormat::assignAllParameters() QSize(64, 64), QVideoFrameFormat::Format_AYUV); copy.setScanLineDirection(QVideoFrameFormat::TopToBottom); copy.setViewport(QRect(0, 0, 640, 320)); - copy.setFrameRate(qreal(7.5)); + copy.setStreamFrameRate(qreal(7.5)); copy.setColorSpace(QVideoFrameFormat::ColorSpace_BT601); /* Create the instance and set all the parameters. */ @@ -513,7 +541,7 @@ void tst_QVideoFrameFormat::assignAllParameters() QSize(1024, 768), QVideoFrameFormat::Format_ARGB8888); original.setScanLineDirection(QVideoFrameFormat::BottomToTop); original.setViewport(QRect(0, 0, 1024, 1024)); - original.setFrameRate(qreal(15.0)); + original.setStreamFrameRate(qreal(15.0)); original.setColorSpace(QVideoFrameFormat::ColorSpace_BT709); /* Assign the original instance to copy and verify if both the instancess @@ -524,7 +552,7 @@ void tst_QVideoFrameFormat::assignAllParameters() QCOMPARE(copy.frameSize(), QSize(1024, 768)); QCOMPARE(copy.scanLineDirection(), QVideoFrameFormat::BottomToTop); QCOMPARE(copy.viewport(), QRect(0, 0, 1024, 1024)); - QCOMPARE(copy.frameRate(), qreal(15.0)); + QCOMPARE(copy.streamFrameRate(), qreal(15.0)); QCOMPARE(copy.colorSpace(), QVideoFrameFormat::ColorSpace_BT709); /* Verify if both the instances are eqaul */ diff --git a/tests/auto/unit/multimedia/qvideotexturehelper/CMakeLists.txt b/tests/auto/unit/multimedia/qvideotexturehelper/CMakeLists.txt new file mode 100644 index 000000000..a47b46d5b --- /dev/null +++ b/tests/auto/unit/multimedia/qvideotexturehelper/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_internal_add_test(tst_qvideotexturehelper + SOURCES + tst_qvideotexturehelper.cpp + LIBRARIES + Qt::Gui + Qt::MultimediaPrivate +) diff --git a/tests/auto/unit/multimedia/qvideotexturehelper/tst_qvideotexturehelper.cpp b/tests/auto/unit/multimedia/qvideotexturehelper/tst_qvideotexturehelper.cpp new file mode 100644 index 000000000..aa166af54 --- /dev/null +++ b/tests/auto/unit/multimedia/qvideotexturehelper/tst_qvideotexturehelper.cpp @@ -0,0 +1,260 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#include <QtCore/qbytearray.h> +#include <QtTest/qtest.h> + +#include <private/qvideotexturehelper_p.h> +#include <qvideoframe.h> + +#include "qvideoframeformat.h" + +QT_USE_NAMESPACE + +struct ColorSpaceCoeff +{ + float a; + float b; + float c; +}; + +// Coefficients used in ITU-R BT.709-6 Table 3 - Signal format +constexpr ColorSpaceCoeff BT709Coefficients = { + 0.2126f, 0.7152f, 0.0722f // E_g' = 0.2126 * E_R' + 0.7152 * E_G' + 0.0722 * E_B' + // + // Note that the other coefficients can be derived from a and c + // to re-normalize the values, see ITU-R BT.601-7, section 2.5.2 + // + // E_CB' = (E_B' - E_g') / 1.8556 -> 1.8556 == (1-0.0722) * 2 + // E_CR' = (E_R' - E_g') / 1.5748 -> 1.5748 == (1-0.2126) * 2 +}; + +// Coefficients used in ITU-R BT.2020-2 Table 4 - Signal format +constexpr ColorSpaceCoeff BT2020Coefficients = { + 0.2627f, 0.6780f, 0.0593f // Y_c' = (0.2627 R + 0.6780 G + 0.05938 B)' + // C_B' = (B' - Y') / 1.8814 -> 1.8814 == 2*(1-0.0593) + // C_R' = (R' - Y') / 1.4746 -> 1.4746 == 2*(1-0.2627) +}; + +struct ColorSpaceEntry +{ + QVideoFrameFormat::ColorSpace colorSpace; + QVideoFrameFormat::ColorRange colorRange; + ColorSpaceCoeff coefficients; +}; + +// clang-format off +const std::vector<ColorSpaceEntry> colorSpaces = { + { + QVideoFrameFormat::ColorSpace_BT709, + QVideoFrameFormat::ColorRange_Video, + BT709Coefficients + }, + { + QVideoFrameFormat::ColorSpace_BT709, + QVideoFrameFormat::ColorRange_Full, + BT709Coefficients + }, + { + QVideoFrameFormat::ColorSpace_BT2020, + QVideoFrameFormat::ColorRange_Video, + BT2020Coefficients + }, + { + QVideoFrameFormat::ColorSpace_BT2020, + QVideoFrameFormat::ColorRange_Full, + BT2020Coefficients + } +}; + +ColorSpaceCoeff getColorSpaceCoef(QVideoFrameFormat::ColorSpace colorSpace, + QVideoFrameFormat::ColorRange range) +{ + const auto it = std::find_if(colorSpaces.begin(), colorSpaces.end(), + [&](const ColorSpaceEntry &p) { + return p.colorSpace == colorSpace && p.colorRange == range; + }); + + if (it != colorSpaces.end()) + return it->coefficients; + + Q_ASSERT(false); + + return {}; +} + +QMatrix4x4 yuv2rgb(QVideoFrameFormat::ColorSpace colorSpace, QVideoFrameFormat::ColorRange range) +{ + constexpr float max8bit = static_cast<float>(255); + constexpr float uvOffset = -128.0f/max8bit; // Really -0.5, but carried over from fixed point + + QMatrix4x4 normalizeYUV; + + if (range == QVideoFrameFormat::ColorRange_Video) { + // YUV signal is assumed to be in limited range 8 bit representation, + // where Y is in range [16..235] and U and V are in range [16..240]. + // Shaders use floats in [0..1], so we scale the values accordingly. + constexpr float yRange = (235 - 16) / max8bit; + constexpr float yOffset = -16 / max8bit; + constexpr float uvRange = (240 - 16) / max8bit; + + // Second, stretch limited range YUV signals to full range + normalizeYUV.scale(1/yRange, 1/uvRange, 1/uvRange); + + // First, pull limited range signals down so that they start on 0 + normalizeYUV.translate(yOffset, uvOffset, uvOffset); + } else { + normalizeYUV.translate(0.0f, uvOffset, uvOffset); + } + + const auto [a, b, c] = getColorSpaceCoef(colorSpace, range); + + // Re-normalization coefficients that restores the color difference + // signals to (-0.5..0.5) + const auto d = 2 * (1.0f - c); + const auto e = 2 * (1.0f - a); + + // Color matrix from ITU-R BT.709-6 Table 3 - Signal Format + // Same as ITU-R BT.2020-2 Table 4 - Signal format + const QMatrix4x4 rgb2yuv { + a, b, c, 0.0f, // Item 3.2: E_g' = a * E_R' + b * E_G' + c * E_B' + -a/d, -b/d, (1-c)/d, 0.0f, // Item 3.3: E_CB' = (E_B' - E_g')/d + (1-a)/e, -b/e, -c/e, 0.0f, // Item 3.3: E_CR' = (E_R' - E_g')/e + 0.0f, 0.0f, 0.0f, 1.0f + }; + + const QMatrix4x4 yuv2rgb = rgb2yuv.inverted(); + + // Read backwards: + // 1. Offset and scale YUV signal to be in range [0..1] + // 3. Convert to RGB in range [0..1] + return yuv2rgb * normalizeYUV; +} + +// clang-format on + +bool fuzzyCompareWithTolerance(const QMatrix4x4 &computed, const QMatrix4x4 &baseline, + float tolerance) +{ + const float *computedData = computed.data(); + const float *baselineData = baseline.data(); + for (int i = 0; i < 16; ++i) { + const float c = computedData[i]; + const float b = baselineData[i]; + + bool difference = false; + if (qFuzzyIsNull(c) && qFuzzyIsNull(b)) + continue; + + difference = 2 * (std::abs(c - b) / (c + b)) > tolerance; + + if (difference) { + qDebug() << "Mismatch at index" << i << c << "vs" << b; + qDebug() << "Computed:"; + qDebug() << computed; + qDebug() << "Baseline:"; + qDebug() << baseline; + + return false; + } + } + return true; +} + +bool fuzzyCompareWithTolerance(const QVector3D &computed, const QVector3D &baseline, + float tolerance) +{ + auto fuzzyCompare = [](float c, float b, float tolerance) { + if (std::abs(c) < tolerance && std::abs(b) < tolerance) + return true; + + return 2 * std::abs(c - b) / (c + b) < tolerance; + }; + + const bool equal = fuzzyCompare(computed.x(), baseline.x(), tolerance) + && fuzzyCompare(computed.y(), baseline.y(), tolerance) + && fuzzyCompare(computed.z(), baseline.z(), tolerance); + + if (!equal) { + qDebug() << "Vectors are different. Computed:"; + qDebug() << computed; + qDebug() << "Baseline:"; + qDebug() << baseline; + } + + return equal; +} + +QMatrix4x4 getColorMatrix(const QByteArray &uniformDataBytes) +{ + const auto uniformData = + reinterpret_cast<const QVideoTextureHelper::UniformData *>(uniformDataBytes.data()); + const auto colorMatrixData = reinterpret_cast<const float *>(uniformData->colorMatrix); + + return QMatrix4x4{ colorMatrixData }.transposed(); +}; + +class tst_qvideotexturehelper : public QObject +{ + Q_OBJECT +public: +private slots: + void updateUniformData_populatesYUV2RGBColorMatrix_data() + { + QTest::addColumn<QVideoFrameFormat::ColorSpace>("colorSpace"); + QTest::addColumn<QVideoFrameFormat::ColorRange>("colorRange"); + + QTest::addRow("BT709_full") + << QVideoFrameFormat::ColorSpace_BT709 << QVideoFrameFormat::ColorRange_Full; + + QTest::addRow("BT709_video") + << QVideoFrameFormat::ColorSpace_BT709 << QVideoFrameFormat::ColorRange_Video; + + QTest::addRow("BT2020_full") + << QVideoFrameFormat::ColorSpace_BT2020 << QVideoFrameFormat::ColorRange_Full; + + QTest::addRow("BT2020_video") + << QVideoFrameFormat::ColorSpace_BT2020 << QVideoFrameFormat::ColorRange_Video; + } + + void updateUniformData_populatesYUV2RGBColorMatrix() + { + QFETCH(const QVideoFrameFormat::ColorSpace, colorSpace); + QFETCH(const QVideoFrameFormat::ColorRange, colorRange); + + // Arrange + QVideoFrameFormat format{ {}, QVideoFrameFormat::Format_NV12 }; + format.setColorSpace(colorSpace); + format.setColorRange(colorRange); + + const QMatrix4x4 expected = yuv2rgb(colorSpace, colorRange); + + // Act + QByteArray data; + QVideoTextureHelper::updateUniformData(&data, format, {}, {}, 0.0); + const QMatrix4x4 actual = getColorMatrix(data); + + // Assert + QVERIFY(fuzzyCompareWithTolerance(actual, expected, 1e-3f)); + + { // Sanity check: Color matrix maps white to white + constexpr QVector3D expectedWhiteRgb{ 1.0f, 1.0f, 1.0f }; + const QVector3D whiteYuv = expected.inverted().map(expectedWhiteRgb); + + const QVector3D actualWhiteRgb = actual.map(whiteYuv); + QVERIFY(fuzzyCompareWithTolerance(actualWhiteRgb, expectedWhiteRgb, 5e-4f)); + } + + { // Sanity check: Color matrix maps black to black + constexpr QVector3D expectedBlackRgb{ 0.0f, 0.0f, 0.0f }; + const QVector3D blackYuv = expected.inverted().map(expectedBlackRgb); + + const QVector3D actualBlackRgb = actual.map(blackYuv); + QVERIFY(fuzzyCompareWithTolerance(actualBlackRgb, expectedBlackRgb, 5e-4f)); + } + } +}; + +QTEST_MAIN(tst_qvideotexturehelper) + +#include "tst_qvideotexturehelper.moc" diff --git a/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt b/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt index 29db70754..4567e95ac 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt +++ b/tests/auto/unit/multimedia/qwavedecoder/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qwavedecoder.pro. ##################################################################### @@ -13,7 +16,7 @@ list(APPEND test_data ${test_data_glob}) qt_internal_add_test(tst_qwavedecoder SOURCES tst_qwavedecoder.cpp - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::Network diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh b/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh index c799e6f9f..99a04129b 100755 --- a/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh +++ b/tests/auto/unit/multimedia/qwavedecoder/data/gendata.sh @@ -1,31 +1,6 @@ #!/bin/bash -############################################################################# -## -## Copyright (C) 2016 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is the build configuration utility 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 WITH Qt-GPL-exception-1.0 # Generate some simple test data. Uses "sox". diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000_even_bext.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000_even_bext.wav Binary files differnew file mode 100644 index 000000000..531b0ee58 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000_even_bext.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000_odd_bext.wav b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000_odd_bext.wav Binary files differnew file mode 100644 index 000000000..467be6312 --- /dev/null +++ b/tests/auto/unit/multimedia/qwavedecoder/data/isawav_1_8_8000_odd_bext.wav diff --git a/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp b/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp index d4fa9e5b8..079f98075 100644 --- a/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp +++ b/tests/auto/unit/multimedia/qwavedecoder/tst_qwavedecoder.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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 <qwavedecoder.h> @@ -85,7 +58,7 @@ void tst_QWaveDecoder::cleanupTestCase() static QString testFilePath(const char *filename) { - QString path = QString("data/%1").arg(filename); + QString path = QStringLiteral("data/%1").arg(filename); return QFINDTESTDATA(path); } @@ -96,33 +69,35 @@ void tst_QWaveDecoder::file_data() QTest::addColumn<int>("channels"); QTest::addColumn<int>("samplesize"); QTest::addColumn<int>("samplerate"); - QTest::addColumn<QAudioFormat::Endian>("byteorder"); - - QTest::newRow("File is empty") << testFilePath("empty.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1 << QAudioFormat::LittleEndian; - QTest::newRow("File is one byte") << testFilePath("onebyte.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1 << QAudioFormat::LittleEndian; - QTest::newRow("File is not a wav(text)") << testFilePath("notawav.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1 << QAudioFormat::LittleEndian; - QTest::newRow("Wav file has no sample data") << testFilePath("nosampledata.wav") << tst_QWaveDecoder::NoSampleData << -1 << -1 << -1 << QAudioFormat::LittleEndian; - QTest::newRow("corrupt fmt chunk descriptor") << testFilePath("corrupt_fmtdesc_1_16_8000.le.wav") << tst_QWaveDecoder::FormatDescriptor << -1 << -1 << -1 << QAudioFormat::LittleEndian; - QTest::newRow("corrupt fmt string") << testFilePath("corrupt_fmtstring_1_16_8000.le.wav") << tst_QWaveDecoder::FormatString << -1 << -1 << -1 << QAudioFormat::LittleEndian; - QTest::newRow("corrupt data chunk descriptor") << testFilePath("corrupt_datadesc_1_16_8000.le.wav") << tst_QWaveDecoder::DataDescriptor << -1 << -1 << -1 << QAudioFormat::LittleEndian; - - QTest::newRow("File isawav_1_8_8000.wav") << testFilePath("isawav_1_8_8000.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_1_8_44100.wav") << testFilePath("isawav_1_8_44100.wav") << tst_QWaveDecoder::None << 1 << 8 << 44100 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_2_8_8000.wav") << testFilePath("isawav_2_8_8000.wav") << tst_QWaveDecoder::None << 2 << 8 << 8000 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_2_8_44100.wav") << testFilePath("isawav_2_8_44100.wav") << tst_QWaveDecoder::None << 2 << 8 << 44100 << QAudioFormat::LittleEndian; - - QTest::newRow("File isawav_1_16_8000_le.wav") << testFilePath("isawav_1_16_8000_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 8000 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_1_16_44100_le.wav") << testFilePath("isawav_1_16_44100_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_2_16_8000_be.wav") << testFilePath("isawav_2_16_8000_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 8000 << QAudioFormat::BigEndian; - QTest::newRow("File isawav_2_16_44100_be.wav") << testFilePath("isawav_2_16_44100_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 44100 << QAudioFormat::BigEndian; - // The next file has extra data in the wave header. - QTest::newRow("File isawav_1_16_44100_le_2.wav") << testFilePath("isawav_1_16_44100_le_2.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100 << QAudioFormat::LittleEndian; + QTest::newRow("File is empty") << testFilePath("empty.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1; + QTest::newRow("File is one byte") << testFilePath("onebyte.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1; + QTest::newRow("File is not a wav(text)") << testFilePath("notawav.wav") << tst_QWaveDecoder::NotAWav << -1 << -1 << -1; + QTest::newRow("Wav file has no sample data") << testFilePath("nosampledata.wav") << tst_QWaveDecoder::NoSampleData << -1 << -1 << -1; + QTest::newRow("corrupt fmt chunk descriptor") << testFilePath("corrupt_fmtdesc_1_16_8000.le.wav") << tst_QWaveDecoder::FormatDescriptor << -1 << -1 << -1; + QTest::newRow("corrupt fmt string") << testFilePath("corrupt_fmtstring_1_16_8000.le.wav") << tst_QWaveDecoder::FormatString << -1 << -1 << -1; + QTest::newRow("corrupt data chunk descriptor") << testFilePath("corrupt_datadesc_1_16_8000.le.wav") << tst_QWaveDecoder::DataDescriptor << -1 << -1 << -1; + + QTest::newRow("File isawav_1_8_8000.wav") << testFilePath("isawav_1_8_8000.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000; + QTest::newRow("File isawav_1_8_44100.wav") << testFilePath("isawav_1_8_44100.wav") << tst_QWaveDecoder::None << 1 << 8 << 44100; + QTest::newRow("File isawav_2_8_8000.wav") << testFilePath("isawav_2_8_8000.wav") << tst_QWaveDecoder::None << 2 << 8 << 8000; + QTest::newRow("File isawav_2_8_44100.wav") << testFilePath("isawav_2_8_44100.wav") << tst_QWaveDecoder::None << 2 << 8 << 44100; + + QTest::newRow("File isawav_1_16_8000_le.wav") << testFilePath("isawav_1_16_8000_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 8000; + QTest::newRow("File isawav_1_16_44100_le.wav") << testFilePath("isawav_1_16_44100_le.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100; + QTest::newRow("File isawav_2_16_8000_be.wav") << testFilePath("isawav_2_16_8000_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 8000; + QTest::newRow("File isawav_2_16_44100_be.wav") << testFilePath("isawav_2_16_44100_be.wav") << tst_QWaveDecoder::None << 2 << 16 << 44100; + // The next file has extra data in the wave header. + QTest::newRow("File isawav_1_16_44100_le_2.wav") << testFilePath("isawav_1_16_44100_le_2.wav") << tst_QWaveDecoder::None << 1 << 16 << 44100; + // The next file has embedded bext chunk with odd payload (QTBUG-122193) + QTest::newRow("File isawav_1_8_8000_odd_bext.wav") << testFilePath("isawav_1_8_8000_odd_bext.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000; + // The next file has embedded bext chunk with even payload + QTest::newRow("File isawav_1_8_8000_even_bext.wav") << testFilePath("isawav_1_8_8000_even_bext.wav") << tst_QWaveDecoder::None << 1 << 8 << 8000; // 32 bit waves are not supported - QTest::newRow("File isawav_1_32_8000_le.wav") << testFilePath("isawav_1_32_8000_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 8000 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_1_32_44100_le.wav") << testFilePath("isawav_1_32_44100_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 44100 << QAudioFormat::LittleEndian; - QTest::newRow("File isawav_2_32_8000_be.wav") << testFilePath("isawav_2_32_8000_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 8000 << QAudioFormat::BigEndian; - QTest::newRow("File isawav_2_32_44100_be.wav") << testFilePath("isawav_2_32_44100_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 44100 << QAudioFormat::BigEndian; + QTest::newRow("File isawav_1_32_8000_le.wav") << testFilePath("isawav_1_32_8000_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 8000; + QTest::newRow("File isawav_1_32_44100_le.wav") << testFilePath("isawav_1_32_44100_le.wav") << tst_QWaveDecoder::FormatDescriptor << 1 << 32 << 44100; + QTest::newRow("File isawav_2_32_8000_be.wav") << testFilePath("isawav_2_32_8000_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 8000; + QTest::newRow("File isawav_2_32_44100_be.wav") << testFilePath("isawav_2_32_44100_be.wav") << tst_QWaveDecoder::FormatDescriptor << 2 << 32 << 44100; } void tst_QWaveDecoder::file() @@ -132,7 +107,6 @@ void tst_QWaveDecoder::file() QFETCH(int, channels); QFETCH(int, samplesize); QFETCH(int, samplerate); - QFETCH(QAudioFormat::Endian, byteorder); QFile stream; stream.setFileName(file); @@ -141,8 +115,10 @@ void tst_QWaveDecoder::file() QVERIFY(stream.isOpen()); QWaveDecoder waveDecoder(&stream); - QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown())); - QSignalSpy parsingErrorSpy(&waveDecoder, SIGNAL(parsingError())); + QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown); + QSignalSpy parsingErrorSpy(&waveDecoder, &QWaveDecoder::parsingError); + + QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly)); if (corruption == NotAWav) { QSKIP("Not all failures detected correctly yet"); @@ -174,11 +150,8 @@ void tst_QWaveDecoder::file() QAudioFormat format = waveDecoder.audioFormat(); QVERIFY(format.isValid()); QVERIFY(format.channelCount() == channels); - QVERIFY(format.sampleSize() == samplesize); + QCOMPARE(format.bytesPerSample() * 8, samplesize); QVERIFY(format.sampleRate() == samplerate); - if (format.sampleSize() != 8) { - QVERIFY(format.byteOrder() == byteorder); - } } stream.close(); @@ -191,7 +164,6 @@ void tst_QWaveDecoder::http() QFETCH(int, channels); QFETCH(int, samplesize); QFETCH(int, samplerate); - QFETCH(QAudioFormat::Endian, byteorder); QFile stream; stream.setFileName(file); @@ -204,8 +176,10 @@ void tst_QWaveDecoder::http() QNetworkReply *reply = nam.get(QNetworkRequest(QUrl::fromLocalFile(file))); QWaveDecoder waveDecoder(reply); - QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown())); - QSignalSpy parsingErrorSpy(&waveDecoder, SIGNAL(parsingError())); + QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown); + QSignalSpy parsingErrorSpy(&waveDecoder, &QWaveDecoder::parsingError); + + QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly)); if (corruption == NotAWav) { QSKIP("Not all failures detected correctly yet"); @@ -237,11 +211,8 @@ void tst_QWaveDecoder::http() QAudioFormat format = waveDecoder.audioFormat(); QVERIFY(format.isValid()); QVERIFY(format.channelCount() == channels); - QVERIFY(format.sampleSize() == samplesize); + QCOMPARE(format.bytesPerSample() * 8, samplesize); QVERIFY(format.sampleRate() == samplerate); - if (format.sampleSize() != 8) { - QVERIFY(format.byteOrder() == byteorder); - } } delete reply; @@ -256,7 +227,9 @@ void tst_QWaveDecoder::readAllAtOnce() QVERIFY(stream.isOpen()); QWaveDecoder waveDecoder(&stream); - QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown())); + QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown); + + QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly)); QTRY_COMPARE(validFormatSpy.count(), 1); QVERIFY(waveDecoder.size() > 0); @@ -282,7 +255,9 @@ void tst_QWaveDecoder::readPerByte() QVERIFY(stream.isOpen()); QWaveDecoder waveDecoder(&stream); - QSignalSpy validFormatSpy(&waveDecoder, SIGNAL(formatKnown())); + QSignalSpy validFormatSpy(&waveDecoder, &QWaveDecoder::formatKnown); + + QVERIFY(waveDecoder.open(QIODeviceBase::ReadOnly)); QTRY_COMPARE(validFormatSpy.count(), 1); QVERIFY(waveDecoder.size() > 0); diff --git a/tests/auto/unit/multimediawidgets/CMakeLists.txt b/tests/auto/unit/multimediawidgets/CMakeLists.txt index 336488835..87adc0190 100644 --- a/tests/auto/unit/multimediawidgets/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from multimediawidgets.pro. add_subdirectory(qcamerawidgets) diff --git a/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt index 9df51a5b1..486f34690 100644 --- a/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qcamerawidgets/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qcamerawidgets.pro. ##################################################################### @@ -9,11 +12,11 @@ qt_internal_add_test(tst_qcamerawidgets tst_qcamerawidgets.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Widgets - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp b/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp index 7db456190..6c31a4b66 100644 --- a/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp +++ b/tests/auto/unit/multimediawidgets/qcamerawidgets/tst_qcamerawidgets.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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 <QDebug> @@ -42,10 +15,11 @@ #include <qvideosink.h> #include "qmockmediacapturesession.h" -#include "qmockintegration_p.h" +#include "qmockintegration.h" QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN class tst_QCameraWidgets: public QObject { @@ -58,9 +32,6 @@ public slots: private slots: void testCameraEncodingProperyChange(); void testSetVideoOutput(); - -private: - QMockIntegration mockIntegration; }; void tst_QCameraWidgets::initTestCase() @@ -80,12 +51,12 @@ void tst_QCameraWidgets::testCameraEncodingProperyChange() session.setCamera(&camera); session.setImageCapture(&imageCapture); - QSignalSpy activeChangedSignal(&camera, SIGNAL(activeChanged(bool))); + QSignalSpy activeChangedSignal(&camera, &QCamera::activeChanged); camera.start(); QCOMPARE(camera.isActive(), true); - QCOMPARE(activeChangedSignal.count(), 1); + QCOMPARE(activeChangedSignal.size(), 1); } void tst_QCameraWidgets::testSetVideoOutput() diff --git a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt index 227eb18ff..599042725 100644 --- a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qgraphicsvideoitem.pro. ##################################################################### @@ -9,10 +12,10 @@ qt_internal_add_test(tst_qgraphicsvideoitem tst_qgraphicsvideoitem.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Widgets - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp index 583115f99..0cda11de1 100644 --- a/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp +++ b/tests/auto/unit/multimediawidgets/qgraphicsvideoitem/tst_qgraphicsvideoitem.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** 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 <qtmultimediaglobal.h> #include "qgraphicsvideoitem.h" @@ -41,9 +14,12 @@ #include <QtWidgets/qgraphicsscene.h> #include <QtWidgets/qgraphicsview.h> -#include <qmockintegration_p.h> +#include <qmockintegration.h> QT_USE_NAMESPACE + +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class tst_QGraphicsVideoItem : public QObject { Q_OBJECT @@ -66,9 +42,6 @@ private slots: void boundingRect(); void paint(); - -public: - QMockIntegration integration; }; class QtTestGraphicsVideoItem : public QGraphicsVideoItem @@ -241,7 +214,7 @@ void tst_QGraphicsVideoItem::nativeSize() QCOMPARE(item.nativeSize(), QSizeF()); - QSignalSpy spy(&item, SIGNAL(nativeSizeChanged(QSizeF))); + QSignalSpy spy(&item, &QGraphicsVideoItem::nativeSizeChanged); QVideoFrameFormat format(frameSize, QVideoFrameFormat::Format_ARGB8888); format.setViewport(viewport); @@ -250,7 +223,7 @@ void tst_QGraphicsVideoItem::nativeSize() QCoreApplication::processEvents(); QCOMPARE(item.nativeSize(), nativeSize); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.last().first().toSizeF(), nativeSize); } @@ -402,7 +375,7 @@ void tst_QGraphicsVideoItem::paint() QVideoFrameFormat format(QSize(2, 2), QVideoFrameFormat::Format_XRGB8888); QVideoFrame frame(format); - frame.map(QVideoFrame::WriteOnly); + frame.map(QtVideo::MapMode::WriteOnly); memcpy(frame.bits(0), rgb32ImageData, frame.mappedBytes(0)); frame.unmap(); diff --git a/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt index b1859fb39..7c54e67b9 100644 --- a/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qmediaplayerwidgets.pro. ##################################################################### @@ -9,12 +12,12 @@ qt_internal_add_test(tst_qmediaplayerwidgets tst_qmediaplayerwidgets.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES # Remove: L${CMAKE_CURRENT_SOURCE_DIR} Qt::Gui Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Network Qt::Widgets - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/tst_qmediaplayerwidgets.cpp b/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/tst_qmediaplayerwidgets.cpp index a8fafc86b..e0f23d477 100644 --- a/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/tst_qmediaplayerwidgets.cpp +++ b/tests/auto/unit/multimediawidgets/qmediaplayerwidgets/tst_qmediaplayerwidgets.cpp @@ -1,32 +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$ -** -****************************************************************************/ - -//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/qdebug.h> @@ -38,47 +11,22 @@ #include <private/qplatformmediaplayer_p.h> #include "qvideosink.h" -#include "qmockintegration_p.h" +#include "qmockintegration.h" QT_USE_NAMESPACE +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class tst_QMediaPlayerWidgets: public QObject { Q_OBJECT -public slots: - void initTestCase(); - void cleanupTestCase(); - void init(); - void cleanup(); - private slots: void testSetVideoOutput(); void testSetVideoOutputNoService(); void testSetVideoOutputNoControl(); - -private: - QMockIntegration *mockIntegration; }; -void tst_QMediaPlayerWidgets::initTestCase() -{ - mockIntegration = new QMockIntegration; -} - -void tst_QMediaPlayerWidgets::cleanupTestCase() -{ - delete mockIntegration; -} - -void tst_QMediaPlayerWidgets::init() -{ -} - -void tst_QMediaPlayerWidgets::cleanup() -{ -} - void tst_QMediaPlayerWidgets::testSetVideoOutput() { QVideoWidget widget; @@ -128,9 +76,9 @@ void tst_QMediaPlayerWidgets::testSetVideoOutputNoService() QGraphicsVideoItem item; QVideoSink surface; - mockIntegration->setFlags(QMockIntegration::NoPlayerInterface); + QMockIntegration::instance()->setFlags(QMockIntegration::NoPlayerInterface); QMediaPlayer player; - mockIntegration->setFlags({}); + QMockIntegration::instance()->setFlags({}); player.setVideoOutput(&widget); diff --git a/tests/auto/unit/multimediawidgets/qvideowidget/BLACKLIST b/tests/auto/unit/multimediawidgets/qvideowidget/BLACKLIST new file mode 100644 index 000000000..2ce9e48fa --- /dev/null +++ b/tests/auto/unit/multimediawidgets/qvideowidget/BLACKLIST @@ -0,0 +1,3 @@ +# QTBUG-110453 +[fullScreen] +android diff --git a/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt b/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt index 673cd7ff6..b179b0b3b 100644 --- a/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt +++ b/tests/auto/unit/multimediawidgets/qvideowidget/CMakeLists.txt @@ -1,3 +1,6 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + # Generated from qvideowidget.pro. ##################################################################### @@ -9,10 +12,10 @@ qt_internal_add_test(tst_qvideowidget tst_qvideowidget.cpp INCLUDE_DIRECTORIES ../../mockbackend - PUBLIC_LIBRARIES + LIBRARIES Qt::Gui Qt::MultimediaPrivate Qt::MultimediaWidgetsPrivate Qt::Widgets - QtMultimediaMockBackend + MockMultimediaPlugin ) diff --git a/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp b/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp index 1f43a2be4..b999020a4 100644 --- a/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp +++ b/tests/auto/unit/multimediawidgets/qvideowidget/tst_qvideowidget.cpp @@ -1,32 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtmultimediaglobal.h> #include <QtTest/QtTest> @@ -40,13 +13,19 @@ #include <QtWidgets/qapplication.h> -#include <qmockintegration_p.h> +#include <qmockintegration.h> #include <qmockvideosink.h> QT_USE_NAMESPACE + +Q_ENABLE_MOCK_MULTIMEDIA_PLUGIN + class tst_QVideoWidget : public QObject { Q_OBJECT +public slots: + void initTestCase(); + private slots: void nullObject(); @@ -82,6 +61,15 @@ public: } }; +void tst_QVideoWidget::initTestCase() +{ +#ifdef Q_OS_MACOS + if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci") + QSKIP("SKIP on macOS CI since metal is not supported, otherwise it often crashes. To be " + "fixed."); +#endif +} + void tst_QVideoWidget::nullObject() { QtTestVideoWidget widget; @@ -117,8 +105,8 @@ void tst_QVideoWidget::show() void tst_QVideoWidget::aspectRatio() { - QMediaPlayer player; QtTestVideoWidget widget; + QMediaPlayer player; player.setVideoOutput(&widget); // Test the aspect ratio defaults to keeping the aspect ratio. @@ -172,11 +160,11 @@ void tst_QVideoWidget::sizeHint() // QFETCH(QRect, viewport); QFETCH(QSize, expectedSize); - QMockIntegration mock; - QMediaPlayer player; QtTestVideoWidget widget; + QMediaPlayer player; + player.setVideoOutput(&widget); - auto mockSink = mock.lastVideoSink(); + auto mockSink = QMockIntegration::instance()->lastVideoSink(); widget.show(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); @@ -188,28 +176,28 @@ void tst_QVideoWidget::sizeHint() void tst_QVideoWidget::fullScreen() { - QMediaPlayer player; QtTestVideoWidget widget; + QMediaPlayer player; player.setVideoOutput(&widget); widget.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); Qt::WindowFlags windowFlags = widget.windowFlags(); - QSignalSpy spy(&widget, SIGNAL(fullScreenChanged(bool))); + QSignalSpy spy(&widget, &QVideoWidget::fullScreenChanged); // Test showing full screen with setFullScreen(true). widget.setFullScreen(true); QVERIFY(QTest::qWaitForWindowExposed(&widget)); QCOMPARE(widget.isFullScreen(), true); - QCOMPARE(spy.count(), 1); + QCOMPARE(spy.size(), 1); QCOMPARE(spy.value(0).value(0).toBool(), true); // Test returning to normal with setFullScreen(false). widget.setFullScreen(false); QVERIFY(QTest::qWaitForWindowExposed(&widget)); QCOMPARE(widget.isFullScreen(), false); - QCOMPARE(spy.count(), 2); + QCOMPARE(spy.size(), 2); QCOMPARE(spy.value(1).value(0).toBool(), false); QCOMPARE(widget.windowFlags(), windowFlags); @@ -217,35 +205,35 @@ void tst_QVideoWidget::fullScreen() widget.showFullScreen(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); QCOMPARE(widget.isFullScreen(), true); - QCOMPARE(spy.count(), 3); + QCOMPARE(spy.size(), 3); QCOMPARE(spy.value(2).value(0).toBool(), true); // Test returning to normal with showNormal(). widget.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); QCOMPARE(widget.isFullScreen(), false); - QCOMPARE(spy.count(), 4); + QCOMPARE(spy.size(), 4); QCOMPARE(spy.value(3).value(0).toBool(), false); QCOMPARE(widget.windowFlags(), windowFlags); // Test setFullScreen(false) and showNormal() do nothing when isFullScreen() == false. widget.setFullScreen(false); QCOMPARE(widget.isFullScreen(), false); - QCOMPARE(spy.count(), 4); + QCOMPARE(spy.size(), 4); widget.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); QCOMPARE(widget.isFullScreen(), false); - QCOMPARE(spy.count(), 4); + QCOMPARE(spy.size(), 4); // Test setFullScreen(true) and showFullScreen() do nothing when isFullScreen() == true. widget.showFullScreen(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); widget.setFullScreen(true); QCOMPARE(widget.isFullScreen(), true); - QCOMPARE(spy.count(), 5); + QCOMPARE(spy.size(), 5); widget.showFullScreen(); QCOMPARE(widget.isFullScreen(), true); - QCOMPARE(spy.count(), 5); + QCOMPARE(spy.size(), 5); } static const uchar rgb32ImageData[] = @@ -256,8 +244,8 @@ static const uchar rgb32ImageData[] = void tst_QVideoWidget::paint() { - QMediaPlayer player; QtTestVideoWidget widget; + QMediaPlayer player; player.setVideoOutput(&widget); widget.resize(640,480); widget.show(); @@ -265,7 +253,7 @@ void tst_QVideoWidget::paint() QVideoFrameFormat format(QSize(2, 2), QVideoFrameFormat::Format_XRGB8888); QVideoFrame frame(format); - QVERIFY(frame.map(QVideoFrame::ReadWrite)); + QVERIFY(frame.map(QtVideo::MapMode::ReadWrite)); uchar *data = frame.bits(0); memcpy(data, rgb32ImageData, sizeof(rgb32ImageData)); frame.unmap(); |