Revision control
Copy as Markdown
// 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"