/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the test suite of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ //TESTED_COMPONENT=src/multimedia #include #include #include #include #include #include #include #include #include #include "wavheader.h" #define AUDIO_BUFFER 192000 #ifndef QTRY_VERIFY2 #define QTRY_VERIFY2(__expr,__msg) \ do { \ const int __step = 50; \ const int __timeout = 5000; \ if (!(__expr)) { \ QTest::qWait(0); \ } \ for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \ QTest::qWait(__step); \ } \ QVERIFY2(__expr,__msg); \ } while (0) #endif class tst_QAudioOutput : public QObject { Q_OBJECT public: tst_QAudioOutput(QObject* parent=0) : QObject(parent) {} private slots: void initTestCase(); void format(); void invalidFormat_data(); void invalidFormat(); void bufferSize_data(); void bufferSize(); void notifyInterval_data(); void notifyInterval(); void disableNotifyInterval(); void stopWhileStopped(); void suspendWhileStopped(); void resumeWhileStopped(); void pull_data(){generate_audiofile_testrows();} void pull(); void pullSuspendResume_data(){generate_audiofile_testrows();} void pullSuspendResume(); void push_data(){generate_audiofile_testrows();} void push(); void pushSuspendResume_data(){generate_audiofile_testrows();} void pushSuspendResume(); void pushUnderrun_data(){generate_audiofile_testrows();} void pushUnderrun(); void volume_data(); void volume(); private: typedef QSharedPointer FilePtr; QString formatToFileName(const QAudioFormat &format); void createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate = 440); void generate_audiofile_testrows(); QAudioDeviceInfo audioDevice; QList testFormats; QList audioFiles; QScopedPointer m_temporaryDir; QScopedPointer m_byteArray; QScopedPointer m_buffer; bool m_inCISystem; }; QString tst_QAudioOutput::formatToFileName(const QAudioFormat &format) { const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian) ? QString("LE") : QString("BE"); const QString formatSigned = (format.sampleType() == QAudioFormat::SignedInt) ? QString("signed") : QString("unsigned"); return QString("%1_%2_%3_%4_%5") .arg(format.sampleRate()) .arg(format.sampleSize()) .arg(formatSigned) .arg(formatEndian) .arg(format.channelCount()); } void tst_QAudioOutput::createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate) { const int channelBytes = format.sampleSize() / 8; const int sampleBytes = format.channelCount() * channelBytes; Q_ASSERT(length % sampleBytes == 0); Q_UNUSED(sampleBytes) // suppress warning in release builds m_byteArray.reset(new QByteArray(length, 0)); unsigned char *ptr = reinterpret_cast(m_byteArray->data()); int sampleIndex = 0; while (length) { const qreal x = qSin(2 * M_PI * sampleRate * qreal(sampleIndex % format.sampleRate()) / format.sampleRate()); for (int i=0; i((1.0 + x) / 2 * 255); *reinterpret_cast(ptr) = value; } else if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::SignedInt) { const qint8 value = static_cast(x * 127); *reinterpret_cast(ptr) = value; } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::UnSignedInt) { quint16 value = static_cast((1.0 + x) / 2 * 65535); if (format.byteOrder() == QAudioFormat::LittleEndian) qToLittleEndian(value, ptr); else qToBigEndian(value, ptr); } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt) { qint16 value = static_cast(x * 32767); if (format.byteOrder() == QAudioFormat::LittleEndian) qToLittleEndian(value, ptr); else qToBigEndian(value, ptr); } ptr += channelBytes; length -= channelBytes; } ++sampleIndex; } m_buffer.reset(new QBuffer(m_byteArray.data(), this)); Q_ASSERT(m_buffer->open(QIODevice::ReadOnly)); } void tst_QAudioOutput::generate_audiofile_testrows() { QTest::addColumn("audioFile"); QTest::addColumn("audioFormat"); for (int i=0; i(); // Only perform tests if audio output device exists const QList devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); if (devices.size() <= 0) QSKIP("No audio backend"); audioDevice = QAudioDeviceInfo::defaultOutputDevice(); QAudioFormat format; format.setCodec("audio/pcm"); if (audioDevice.isFormatSupported(audioDevice.preferredFormat())) testFormats.append(audioDevice.preferredFormat()); // PCM 8000 mono S8 format.setSampleRate(8000); format.setSampleSize(8); format.setSampleType(QAudioFormat::SignedInt); format.setByteOrder(QAudioFormat::LittleEndian); format.setChannelCount(1); if (audioDevice.isFormatSupported(format)) testFormats.append(format); // PCM 11025 mono S16LE format.setSampleRate(11025); format.setSampleSize(16); if (audioDevice.isFormatSupported(format)) testFormats.append(format); // PCM 22050 mono S16LE format.setSampleRate(22050); if (audioDevice.isFormatSupported(format)) testFormats.append(format); // PCM 22050 stereo S16LE format.setChannelCount(2); if (audioDevice.isFormatSupported(format)) testFormats.append(format); // PCM 44100 stereo S16LE format.setSampleRate(44100); if (audioDevice.isFormatSupported(format)) testFormats.append(format); // PCM 48000 stereo S16LE format.setSampleRate(48000); if (audioDevice.isFormatSupported(format)) testFormats.append(format); QVERIFY(testFormats.size()); const QChar slash = QLatin1Char('/'); QString temporaryPattern = QDir::tempPath(); if (!temporaryPattern.endsWith(slash)) temporaryPattern += slash; temporaryPattern += "tst_qaudiooutputXXXXXX"; m_temporaryDir.reset(new QTemporaryDir(temporaryPattern)); m_temporaryDir->setAutoRemove(true); QVERIFY(m_temporaryDir->isValid()); const QString temporaryAudioPath = m_temporaryDir->path() + slash; foreach (const QAudioFormat &format, testFormats) { qint64 len = (format.sampleRate()*format.channelCount()*(format.sampleSize()/8)*2); // 2 seconds createSineWaveData(format, len); // Write generate sine wave data to file const QString fileName = temporaryAudioPath + QStringLiteral("generated") + formatToFileName(format) + QStringLiteral(".wav"); FilePtr file(new QFile(fileName)); QVERIFY2(file->open(QIODevice::WriteOnly), qPrintable(file->errorString())); WavHeader wavHeader(format, len); wavHeader.write(*file.data()); file->write(m_byteArray->data(), len); file->close(); audioFiles.append(file); } qgetenv("QT_TEST_CI").toInt(&m_inCISystem,10); } void tst_QAudioOutput::format() { QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QAudioFormat requested = audioDevice.preferredFormat(); QAudioFormat actual = audioOutput.format(); QVERIFY2((requested.channelCount() == actual.channelCount()), QString("channels: requested=%1, actual=%2").arg(requested.channelCount()).arg(actual.channelCount()).toLocal8Bit().constData()); QVERIFY2((requested.sampleRate() == actual.sampleRate()), QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toLocal8Bit().constData()); QVERIFY2((requested.sampleSize() == actual.sampleSize()), QString("sampleSize: requested=%1, actual=%2").arg(requested.sampleSize()).arg(actual.sampleSize()).toLocal8Bit().constData()); QVERIFY2((requested.codec() == actual.codec()), QString("codec: requested=%1, actual=%2").arg(requested.codec()).arg(actual.codec()).toLocal8Bit().constData()); QVERIFY2((requested.byteOrder() == actual.byteOrder()), QString("byteOrder: requested=%1, actual=%2").arg(requested.byteOrder()).arg(actual.byteOrder()).toLocal8Bit().constData()); QVERIFY2((requested.sampleType() == actual.sampleType()), QString("sampleType: requested=%1, actual=%2").arg(requested.sampleType()).arg(actual.sampleType()).toLocal8Bit().constData()); } void tst_QAudioOutput::invalidFormat_data() { QTest::addColumn("invalidFormat"); QAudioFormat format; QTest::newRow("Null Format") << format; format = audioDevice.preferredFormat(); format.setChannelCount(0); QTest::newRow("Channel count 0") << format; format = audioDevice.preferredFormat(); format.setSampleRate(0); QTest::newRow("Sample rate 0") << format; format = audioDevice.preferredFormat(); format.setSampleSize(0); QTest::newRow("Sample size 0") << format; } void tst_QAudioOutput::invalidFormat() { QFETCH(QAudioFormat, invalidFormat); QVERIFY2(!audioDevice.isFormatSupported(invalidFormat), "isFormatSupported() is returning true on an invalid format"); QAudioOutput audioOutput(invalidFormat, this); // Check that we are in the default state before calling start QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); audioOutput.start(); // Check that error is raised QTRY_VERIFY2((audioOutput.error() == QAudio::OpenError),"error() was not set to QAudio::OpenError after start()"); } void tst_QAudioOutput::bufferSize_data() { QTest::addColumn("bufferSize"); QTest::newRow("Buffer size 512") << 512; QTest::newRow("Buffer size 4096") << 4096; QTest::newRow("Buffer size 8192") << 8192; } void tst_QAudioOutput::bufferSize() { QFETCH(int, bufferSize); QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() was not set to QAudio::NoError on creation(%1)").arg(bufferSize).toLocal8Bit().constData()); audioOutput.setBufferSize(bufferSize); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize"); QVERIFY2((audioOutput.bufferSize() == bufferSize), QString("bufferSize: requested=%1, actual=%2").arg(bufferSize).arg(audioOutput.bufferSize()).toLocal8Bit().constData()); } void tst_QAudioOutput::notifyInterval_data() { QTest::addColumn("interval"); QTest::newRow("Notify interval 50") << 50; QTest::newRow("Notify interval 100") << 100; QTest::newRow("Notify interval 250") << 250; QTest::newRow("Notify interval 1000") << 1000; } void tst_QAudioOutput::notifyInterval() { QFETCH(int, interval); QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation"); audioOutput.setNotifyInterval(interval); QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() is not QAudio::NoError after setNotifyInterval(%1)").arg(interval).toLocal8Bit().constData()); QVERIFY2((audioOutput.notifyInterval() == interval), QString("notifyInterval: requested=%1, actual=%2").arg(interval).arg(audioOutput.notifyInterval()).toLocal8Bit().constData()); } void tst_QAudioOutput::disableNotifyInterval() { // Sets an invalid notification interval (QAudioOutput::setNotifyInterval(0)) // Checks that // - No error is raised (QAudioOutput::error() returns QAudio::NoError) // - if <= 0, set to zero and disable notify signal QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation"); audioOutput.setNotifyInterval(0); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(0)"); QVERIFY2((audioOutput.notifyInterval() == 0), "notifyInterval() is not zero after setNotifyInterval(0)"); audioOutput.setNotifyInterval(-1); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(-1)"); QVERIFY2((audioOutput.notifyInterval() == 0), "notifyInterval() is not zero after setNotifyInterval(-1)"); //start and run to check if notify() is emitted if (audioFiles.size() > 0) { QAudioOutput audioOutputCheck(testFormats.at(0), this); audioOutputCheck.setNotifyInterval(0); audioOutputCheck.setVolume(0.1f); QSignalSpy notifySignal(&audioOutputCheck, SIGNAL(notify())); QFile *audioFile = audioFiles.at(0).data(); audioFile->open(QIODevice::ReadOnly); audioOutputCheck.start(audioFile); QTest::qWait(3000); // 3 seconds should be plenty audioOutputCheck.stop(); QVERIFY2((notifySignal.count() == 0), QString("didn't disable notify interval: shouldn't have got any but got %1").arg(notifySignal.count()).toLocal8Bit().constData()); audioFile->close(); } } void tst_QAudioOutput::stopWhileStopped() { // Calls QAudioOutput::stop() when object is already in StoppedState // Checks that // - No state change occurs // - No error is raised (QAudioOutput::error() returns QAudio::NoError) QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); audioOutput.stop(); // Check that no state transition occurred QVERIFY2((stateSignal.count() == 0), "stop() while stopped is emitting a signal and it shouldn't"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()"); } void tst_QAudioOutput::suspendWhileStopped() { // Calls QAudioOutput::suspend() when object is already in StoppedState // Checks that // - No state change occurs // - No error is raised (QAudioOutput::error() returns QAudio::NoError) QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); audioOutput.suspend(); // Check that no state transition occurred QVERIFY2((stateSignal.count() == 0), "stop() while suspended is emitting a signal and it shouldn't"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()"); } void tst_QAudioOutput::resumeWhileStopped() { // Calls QAudioOutput::resume() when object is already in StoppedState // Checks that // - No state change occurs // - No error is raised (QAudioOutput::error() returns QAudio::NoError) QAudioOutput audioOutput(audioDevice.preferredFormat(), this); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); audioOutput.resume(); // Check that no state transition occurred QVERIFY2((stateSignal.count() == 0), "resume() while stopped is emitting a signal and it shouldn't"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after resume()"); } void tst_QAudioOutput::pull() { QFETCH(FilePtr, audioFile); QFETCH(QAudioFormat, audioFormat); QAudioOutput audioOutput(audioFormat, this); audioOutput.setNotifyInterval(100); audioOutput.setVolume(0.1f); QSignalSpy notifySignal(&audioOutput, SIGNAL(notify())); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); // Check that we are in the default state before calling start QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation"); audioFile->close(); audioFile->open(QIODevice::ReadOnly); audioFile->seek(WavHeader::headerLength()); audioOutput.start(audioFile.data()); // Check that QAudioOutput immediately transitions to ActiveState QTRY_VERIFY2((stateSignal.count() == 1), QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); QVERIFY(audioOutput.periodSize() > 0); stateSignal.clear(); // Check that 'elapsed' increases QTest::qWait(40); QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()"); // Wait until playback finishes QTest::qWait(3000); // 3 seconds should be plenty QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QVERIFY2((stateSignal.count() == 1), QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); qint64 processedUs = audioOutput.processedUSecs(); audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.count() == 1), QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((processedUs == 2000000), QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData()); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState"); QVERIFY2(notifySignal.count() > 0, "not emitting notify() signal"); audioFile->close(); } void tst_QAudioOutput::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); QAudioOutput audioOutput(audioFormat, this); audioOutput.setNotifyInterval(100); audioOutput.setVolume(0.1f); QSignalSpy notifySignal(&audioOutput, SIGNAL(notify())); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); // Check that we are in the default state before calling start QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation"); audioFile->close(); audioFile->open(QIODevice::ReadOnly); audioFile->seek(WavHeader::headerLength()); audioOutput.start(audioFile.data()); // Check that QAudioOutput immediately transitions to ActiveState QTRY_VERIFY2((stateSignal.count() == 1), QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); QVERIFY(audioOutput.periodSize() > 0); stateSignal.clear(); // Wait for half of clip to play QTest::qWait(1000); audioOutput.suspend(); // Give backends running in separate threads a chance to suspend. QTest::qWait(100); QVERIFY2((stateSignal.count() == 1), QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()"); stateSignal.clear(); // Check that only 'elapsed', and not 'processed' increases while suspended qint64 elapsedUs = audioOutput.elapsedUSecs(); qint64 processedUs = audioOutput.processedUSecs(); QTest::qWait(1000); QVERIFY(audioOutput.elapsedUSecs() > elapsedUs); QVERIFY(audioOutput.processedUSecs() == processedUs); audioOutput.resume(); // Give backends running in separate threads a chance to suspend. QTest::qWait(100); // Check that QAudioOutput immediately transitions to ActiveState QVERIFY2((stateSignal.count() == 1), QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()"); stateSignal.clear(); // Wait until playback finishes QTest::qWait(3000); // 3 seconds should be plenty QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QVERIFY2((stateSignal.count() == 1), QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); processedUs = audioOutput.processedUSecs(); audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.count() == 1), QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((processedUs == 2000000), QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData()); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState"); audioFile->close(); } void tst_QAudioOutput::push() { QFETCH(FilePtr, audioFile); QFETCH(QAudioFormat, audioFormat); QAudioOutput audioOutput(audioFormat, this); audioOutput.setNotifyInterval(100); audioOutput.setVolume(0.1f); QSignalSpy notifySignal(&audioOutput, SIGNAL(notify())); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); // Check that we are in the default state before calling start QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation"); audioFile->close(); audioFile->open(QIODevice::ReadOnly); audioFile->seek(WavHeader::headerLength()); QIODevice* feed = audioOutput.start(); // Check that QAudioOutput immediately transitions to IdleState QTRY_VERIFY2((stateSignal.count() == 1), QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); QVERIFY(audioOutput.periodSize() > 0); stateSignal.clear(); // Check that 'elapsed' increases QTest::qWait(40); QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()"); QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()"); qint64 written = 0; bool firstBuffer = true; QByteArray buffer(AUDIO_BUFFER, 0); while (written < audioFile->size()-WavHeader::headerLength()) { if (audioOutput.bytesFree() >= audioOutput.periodSize()) { qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize()); written += feed->write(buffer.constData(), len); if (firstBuffer) { // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.count() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; stateSignal.clear(); } } else QTest::qWait(20); } // Wait until playback finishes QTest::qWait(3000); // 3 seconds should be plenty QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QVERIFY2((stateSignal.count() == 1), QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); qint64 processedUs = audioOutput.processedUSecs(); audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.count() == 1), QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((processedUs == 2000000), QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData()); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState"); QVERIFY2(notifySignal.count() > 0, "not emitting notify signal"); audioFile->close(); } void tst_QAudioOutput::pushSuspendResume() { #ifdef Q_OS_LINUX if (m_inCISystem) QSKIP("QTBUG-26504 Fails 20% of time with pulseaudio backend"); #endif QFETCH(FilePtr, audioFile); QFETCH(QAudioFormat, audioFormat); QAudioOutput audioOutput(audioFormat, this); audioOutput.setNotifyInterval(100); audioOutput.setVolume(0.1f); QSignalSpy notifySignal(&audioOutput, SIGNAL(notify())); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); // Check that we are in the default state before calling start QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation"); audioFile->close(); audioFile->open(QIODevice::ReadOnly); audioFile->seek(WavHeader::headerLength()); QIODevice* feed = audioOutput.start(); // Check that QAudioOutput immediately transitions to IdleState QTRY_VERIFY2((stateSignal.count() == 1), QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); QVERIFY(audioOutput.periodSize() > 0); stateSignal.clear(); // Check that 'elapsed' increases QTest::qWait(40); QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()"); QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()"); qint64 written = 0; bool firstBuffer = true; QByteArray buffer(AUDIO_BUFFER, 0); // Play half of the clip while (written < (audioFile->size()-WavHeader::headerLength())/2) { if (audioOutput.bytesFree() >= audioOutput.periodSize()) { qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize()); written += feed->write(buffer.constData(), len); if (firstBuffer) { // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.count() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; } } else QTest::qWait(20); } stateSignal.clear(); audioOutput.suspend(); // Give backends running in separate threads a chance to suspend. QTest::qWait(100); QVERIFY2((stateSignal.count() == 1), QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()"); stateSignal.clear(); // Check that only 'elapsed', and not 'processed' increases while suspended qint64 elapsedUs = audioOutput.elapsedUSecs(); qint64 processedUs = audioOutput.processedUSecs(); QTest::qWait(1000); QVERIFY(audioOutput.elapsedUSecs() > elapsedUs); QVERIFY(audioOutput.processedUSecs() == processedUs); audioOutput.resume(); // Give backends running in separate threads a chance to resume // but not too much or the rest of the file may be processed QTest::qWait(20); // Check that QAudioOutput immediately transitions to IdleState QVERIFY2((stateSignal.count() == 1), QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after resume()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()"); stateSignal.clear(); // Play rest of the clip while (!audioFile->atEnd()) { if (audioOutput.bytesFree() >= audioOutput.periodSize()) { qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize()); written += feed->write(buffer.constData(), len); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after writing audio data"); } else QTest::qWait(20); } stateSignal.clear(); // Wait until playback finishes QTest::qWait(1000); // 1 seconds should be plenty QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QVERIFY2((stateSignal.count() == 1), QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); processedUs = audioOutput.processedUSecs(); audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.count() == 1), QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((processedUs == 2000000), QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData()); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState"); audioFile->close(); } void tst_QAudioOutput::pushUnderrun() { QFETCH(FilePtr, audioFile); QFETCH(QAudioFormat, audioFormat); QAudioOutput audioOutput(audioFormat, this); audioOutput.setNotifyInterval(100); audioOutput.setVolume(0.1f); QSignalSpy notifySignal(&audioOutput, SIGNAL(notify())); QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State))); // Check that we are in the default state before calling start QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()"); QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation"); audioFile->close(); audioFile->open(QIODevice::ReadOnly); audioFile->seek(WavHeader::headerLength()); QIODevice* feed = audioOutput.start(); // Check that QAudioOutput immediately transitions to IdleState QTRY_VERIFY2((stateSignal.count() == 1), QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); QVERIFY(audioOutput.periodSize() > 0); stateSignal.clear(); // Check that 'elapsed' increases QTest::qWait(40); QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()"); QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()"); qint64 written = 0; bool firstBuffer = true; QByteArray buffer(AUDIO_BUFFER, 0); // Play half of the clip while (written < (audioFile->size()-WavHeader::headerLength())/2) { if (audioOutput.bytesFree() >= audioOutput.periodSize()) { qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize()); written += feed->write(buffer.constData(), len); if (firstBuffer) { // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.count() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; } } else QTest::qWait(20); } stateSignal.clear(); // Wait for data to be played QTest::qWait(1000); QVERIFY2((stateSignal.count() == 1), QString("didn't emit IdleState signal after suspend(), got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState, no data"); QVERIFY2((audioOutput.error() == QAudio::UnderrunError), "error state is not equal to QAudio::UnderrunError, no data"); stateSignal.clear(); firstBuffer = true; // Play rest of the clip while (!audioFile->atEnd()) { if (audioOutput.bytesFree() >= audioOutput.periodSize()) { qint64 len = audioFile->read(buffer.data(),audioOutput.periodSize()); written += feed->write(buffer.constData(), len); if (firstBuffer) { // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.count() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") .arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; } } else QTest::qWait(20); } stateSignal.clear(); // Wait until playback finishes QTest::qWait(1000); // 1 seconds should be plenty QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QVERIFY2((stateSignal.count() == 1), QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); qint64 processedUs = audioOutput.processedUSecs(); audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.count() == 1), QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((processedUs == 2000000), QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData()); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState"); audioFile->close(); } void tst_QAudioOutput::volume_data() { QTest::addColumn("actualFloat"); QTest::addColumn("expectedInt"); QTest::newRow("Volume 0.3") << 0.3f << 3; QTest::newRow("Volume 0.6") << 0.6f << 6; QTest::newRow("Volume 0.9") << 0.9f << 9; } void tst_QAudioOutput::volume() { QFETCH(float, actualFloat); QFETCH(int, expectedInt); QAudioOutput audioOutput(audioDevice.preferredFormat(), this); audioOutput.setVolume(actualFloat); QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt); // Wait a while to see if this changes QTest::qWait(500); QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt); } QTEST_MAIN(tst_QAudioOutput) #include "tst_qaudiooutput.moc"