Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #include <QtMultimediaTestLib/private/mediabackendutils_p.h>
++ #include <QtMultimediaTestLib/private/osdetection_p.h>
++ #ifdef Q_OS_MACOS
++ # include <QtMultimediaTestLib/private/qcoreaudioaggregatedevice_p.h>
++ #endif
++ #include <QtTest/qsignalspy.h>
++ #include <QtTest/qtest.h>
++ #include <QtMultimedia/qaudiodevice.h>
++ #include <QtMultimedia/qmediadevices.h>
++ #include <QtMultimedia/private/qplatformmediaintegration_p.h>
++ #include <QtCore/qlist.h>
++ #include <QtCore/qsemaphore.h>
++ #include <QtCore/qstringlist.h>
++ #include <QtCore/qthread.h>
++ #ifdef Q_OS_ANDROID
++ #include <QtCore/qcoreapplication_platform.h>
++ #endif
++
++ class tst_QAudioDeviceBackend : public QObject
++ {
++ Q_OBJECT
++ public:
++ tst_QAudioDeviceBackend(QObject* parent=nullptr) : QObject(parent) {}
++
++ private slots:
++ void initTestCase();
++ void checkAvailableDefaultInput();
++ void checkAvailableDefaultOutput();
++ void channels();
++ void sampleFormat();
++ void sampleRates();
++ void isFormatSupported();
++ void preferred();
++ void assignOperator();
++ void id();
++ void defaultConstructor();
++ void equalityOperator();
++ #ifdef Q_OS_MACOS
++ void coreaudio_deviceMonitor_notifiesOnAggregateAddRemove();
++ #endif
++ #ifdef Q_OS_ANDROID
++ void noDeadlockOnDestroy();
++ #endif
++
++ private:
++ std::unique_ptr<QAudioDevice> device;
++ };
++
++ void tst_QAudioDeviceBackend::initTestCase()
++ {
++ // Only perform tests if audio output device exists!
++ QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
++ if (devices.size() == 0) {
++ QSKIP("NOTE: no audio output device found, no tests will be performed");
++ } else {
++ device = std::make_unique<QAudioDevice>(devices.at(0));
++ }
++ }
++
++ void tst_QAudioDeviceBackend::checkAvailableDefaultInput()
++ {
++ // Only perform tests if audio input device exists!
++ QList<QAudioDevice> devices = QMediaDevices::audioInputs();
++ if (devices.size() > 0) {
++ auto defaultInput = QMediaDevices::defaultAudioInput();
++ QVERIFY(!defaultInput.isNull());
++ QCOMPARE(std::count(devices.begin(), devices.end(), defaultInput), 1);
++ }
++ }
++
++ void tst_QAudioDeviceBackend::checkAvailableDefaultOutput()
++ {
++ // Only perform tests if audio input device exists!
++ QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
++ if (devices.size() > 0) {
++ auto defaultOutput = QMediaDevices::defaultAudioOutput();
++ QVERIFY(!defaultOutput.isNull());
++ QCOMPARE(std::count(devices.begin(), devices.end(), defaultOutput), 1);
++ }
++ }
++
++ void tst_QAudioDeviceBackend::channels()
++ {
++ QVERIFY(device->minimumChannelCount() > 0);
++ QVERIFY(device->maximumChannelCount() >= device->minimumChannelCount());
++ }
++
++ void tst_QAudioDeviceBackend::sampleFormat()
++ {
++ QList<QAudioFormat::SampleFormat> avail = device->supportedSampleFormats();
++ QVERIFY(avail.size() > 0);
++ }
++
++ void tst_QAudioDeviceBackend::sampleRates()
++ {
++ QVERIFY(device->minimumSampleRate() > 0);
++ QVERIFY(device->maximumSampleRate() >= device->minimumSampleRate());
++ }
++
++ void tst_QAudioDeviceBackend::isFormatSupported()
++ {
++ if (isCI() && isMacOS)
++ QSKIP("MacOS vms don't seem to support 44100hz on CI?");
++
++ QAudioFormat format;
++ format.setSampleRate(44100);
++ format.setChannelCount(2);
++ format.setSampleFormat(QAudioFormat::Int16);
++
++ // Should always be true for these format
++ QVERIFY(device->isFormatSupported(format));
++ }
++
++ void tst_QAudioDeviceBackend::preferred()
++ {
++ auto isPreferredSupported = [](const QAudioDevice &dev) {
++ QAudioFormat format = dev.preferredFormat();
++ QVERIFY(format.isValid());
++ QVERIFY(dev.isFormatSupported(format));
++ };
++
++ for (const auto &dev: QMediaDevices::audioOutputs())
++ isPreferredSupported(dev);
++
++ for (const auto &dev: QMediaDevices::audioInputs())
++ isPreferredSupported(dev);
++ }
++
++ // QAudioDevice's assignOperator method
++ void tst_QAudioDeviceBackend::assignOperator()
++ {
++ QAudioDevice dev;
++ QVERIFY(dev.id().isNull());
++ QVERIFY(dev.isNull() == true);
++
++ QList<QAudioDevice> devices = QMediaDevices::audioOutputs();
++ QVERIFY(devices.size() > 0);
++ QAudioDevice dev1(devices.at(0));
++ dev = dev1;
++ QVERIFY(dev.isNull() == false);
++ QVERIFY(dev.id() == dev1.id());
++ }
++
++ void tst_QAudioDeviceBackend::id()
++ {
++ QVERIFY(!device->id().isNull());
++ QVERIFY(device->id() == QMediaDevices::audioOutputs().at(0).id());
++ }
++
++ // QAudioDevice's defaultConstructor method
++ void tst_QAudioDeviceBackend::defaultConstructor()
++ {
++ QAudioDevice dev;
++ QVERIFY(dev.isNull() == true);
++ QVERIFY(dev.id().isNull());
++ }
++
++ void tst_QAudioDeviceBackend::equalityOperator()
++ {
++ // Get some default device infos
++ QAudioDevice dev1;
++ QAudioDevice dev2;
++
++ QVERIFY(dev1 == dev2);
++ QVERIFY(!(dev1 != dev2));
++
++ // Make sure each available device is not equal to null
++ const auto infos = QMediaDevices::audioOutputs();
++ for (const QAudioDevice &info : infos) {
++ QVERIFY(dev1 != info);
++ QVERIFY(!(dev1 == info));
++
++ dev2 = info;
++
++ QVERIFY(dev2 == info);
++ QVERIFY(!(dev2 != info));
++
++ QVERIFY(dev1 != dev2);
++ QVERIFY(!(dev1 == dev2));
++ }
++
++ // XXX Perhaps each available device should not be equal to any other
++ }
++
++ #ifdef Q_OS_MACOS
++ void tst_QAudioDeviceBackend::coreaudio_deviceMonitor_notifiesOnAggregateAddRemove()
++ {
++ using namespace std::chrono_literals;
++
++ QMediaDevices mediaDevices;
++ QSignalSpy outputsChanged{ &mediaDevices, &QMediaDevices::audioOutputsChanged };
++ QSignalSpy inputsChanged{ &mediaDevices, &QMediaDevices::audioInputsChanged };
++ QVERIFY(outputsChanged.isValid() && inputsChanged.isValid());
++
++ auto aggregate =
++ QCoreAudioAggregateDevice::create(QCoreAudioAggregateDevice::Direction::Output);
++ if (!aggregate)
++ QSKIP("Could not create a CoreAudio aggregate device");
++
++ const auto device = aggregate->waitForQtDevice(5s);
++ if (!device)
++ QSKIP("The CoreAudio aggregate device was not listed by QMediaDevices");
++ QVERIFY(QMediaDevices::audioOutputs().contains(*device)
++ || QMediaDevices::audioInputs().contains(*device));
++ QTRY_VERIFY(!outputsChanged.isEmpty() || !inputsChanged.isEmpty());
++
++ outputsChanged.clear();
++ inputsChanged.clear();
++ aggregate.reset();
++ QTRY_VERIFY_WITH_TIMEOUT(!outputsChanged.isEmpty() || !inputsChanged.isEmpty(), 5s);
++ }
++ #endif
++
++ #ifdef Q_OS_ANDROID
++ void tst_QAudioDeviceBackend::noDeadlockOnDestroy()
++ {
++ // Ensure QAndroidAudioDevices exists, to be destroyed by resetInstance()
++ QPlatformMediaIntegration::instance()->audioDevices();
++
++ // Block the Android UI thread to simulate terminateQtNativeApplication's sem_wait.
++ QSemaphore uiThreadBlocked; // Released by UI thread when it starts blocking
++ QSemaphore releaseUiThread; // Released to unblock the UI thread
++
++ // RAII: Unblock UI thread on early exit
++ QSemaphoreReleaser uiThreadReleaser(releaseUiThread);
++
++ auto uiThreadBlocker = QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
++ [&uiThreadBlocked, &releaseUiThread]() -> QVariant {
++ uiThreadBlocked.release();
++ releaseUiThread.acquire();
++ return { };
++ });
++
++ QVERIFY2(uiThreadBlocked.tryAcquire(1, 2000),
++ "Timed out waiting for Android UI thread to block");
++
++ // Destroy and recreate the integration
++ std::unique_ptr<QThread> resetThread(QThread::create([]() {
++ QPlatformMediaIntegration::instance()->resetInstance();
++ }));
++ resetThread->start();
++
++ const bool finishedInTime = resetThread->wait(5000);
++
++ // Unblock UI thread
++ uiThreadReleaser.cancel();
++ releaseUiThread.release();
++
++ QVERIFY2(finishedInTime,
++ "QAndroidAudioDevices destructor deadlocked with blocked Android UI thread");
++ }
++ #endif
++
++ QTEST_MAIN(tst_QAudioDeviceBackend)
++
++ #include "tst_qaudiodevicebackend.moc"