Revision control

++ // Copyright (C) 2024 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtMultimediaTestLib/private/mediabackendutils_p.h>
++ #include <QtTest/qtest.h>
++ #include <QtMultimedia/qsoundeffect.h>
++ #include <QtCore/qatomic.h>
++ #include <QtCore/qstring.h>
++ #include <QtCore/qthread.h>
++
++ #include <array>
++ #include <chrono>
++
++ using namespace std::chrono_literals;
++
++
++ using namespace Qt::StringLiterals;
++
++ class tst_qsoundeffect_concurrent : public QObject
++ {
++ Q_OBJECT
++
++ private Q_SLOTS:
++
++ // Verify that QSoundEffect plays sound and does not cause asserts when
++ // QSoundEffect is constructed on worker thread before media backend is initialized.
++ // See also QTBUG-129597
++ void play_playsSound_whenMediaBackendInitializedOnWorkerThread()
++ {
++ QSKIP_OHOS("QRtAudioEngine::play() calls QOhosAudioSinkStream::resume() from worker thread, "
++ "violating the OH_AudioRenderer thread-affinity assertion (cross-platform issue)");
++ const QUrl url{ "qrc:/double-drop.wav"_L1 };
++
++ QAtomicInteger success = true;
++ constexpr size_t threadCount = 3;
++ std::array<std::unique_ptr<QObject>, threadCount> contextElements;
++ std::array<std::unique_ptr<QThread>, threadCount> threads;
++
++ // Stress test QSoundEffect a bit to make sure it works concurrently
++ // in multiple threads
++ for (size_t i = 0; i < threadCount; ++i) {
++ threads[i] = std::make_unique<QThread>();
++ threads[i]->start();
++
++ auto contextElement = new QObject{};
++ contextElements[i].reset(contextElement);
++ contextElement->moveToThread(threads[i].get());
++ QMetaObject::invokeMethod(contextElement, [&, thread = threads[i].get()] {
++ if (!playSound(url, thread))
++ success = false;
++ thread->exit();
++ });
++ }
++
++ for (size_t i = 0; i < threadCount; ++i)
++ QTRY_VERIFY(threads[i]->wait(10ms));
++
++ QVERIFY(success);
++
++ // Bonus test, make sure playing sound works from main thread also
++ QVERIFY(playSound(url));
++ }
++
++ private:
++ bool playSound(const QUrl &url, QThread *currentThread = nullptr)
++ {
++ QSoundEffect effect;
++ effect.setSource(url);
++ effect.setLoopCount(5);
++ effect.play();
++
++ auto waitCondition = [&] {
++ // Error is success because CI might not have audio device.
++ // We still want to run this test in CI to uncover any crashes or asserts
++ return effect.isPlaying() || effect.status() == QSoundEffect::Error;
++ };
++
++ if (currentThread) {
++ auto start = std::chrono::steady_clock::now();
++ while (std::chrono::steady_clock::now() - start < 60s) {
++ if (waitCondition())
++ return true;
++ currentThread->eventDispatcher()->processEvents(QEventLoop::AllEvents);
++ };
++ return false;
++ } else {
++ return QTest::qWaitFor([&] {
++ return waitCondition();
++ }, 60s);
++ }
++ }
++ };
++
++ // Don't initialize Gui because we want to test the situation
++ // where COM is not already initialized on the main thread.
++ QTEST_GUILESS_MAIN(tst_qsoundeffect_concurrent);
++ #include "tst_qsoundeffect_concurrent.moc"