diff options
Diffstat (limited to 'tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp')
-rw-r--r-- | tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp | 731 |
1 files changed, 558 insertions, 173 deletions
diff --git a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp index 1e582d14b..53a1445cd 100644 --- a/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp +++ b/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp @@ -1,40 +1,30 @@ -/**************************************************************************** -** -** 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) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QtTest/QtTest> #include <QDebug> #include "qaudiodecoder.h" -#include "../shared/mediafileselector.h" +#include "mediafileselector.h" +#include "mediabackendutils.h" -#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" +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; + +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 @@ -54,13 +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() @@ -70,81 +76,343 @@ void tst_QAudioDecoderBackend::init() void tst_QAudioDecoderBackend::initTestCase() { QAudioDecoder d; - if (!d.isAvailable()) + if (!d.isSupported()) QSKIP("Audio decoder service is not available"); - qRegisterMetaType<QMediaContent>(); + 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() +{ + 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::ServiceMissingError) + if (d.error() == QAudioDecoder::NotSupportedError) QSKIP("There is no audio decoding support on this platform."); QAudioBuffer buffer; quint64 duration = 0; int byteCount = 0; int sampleCount = 0; - QVERIFY(d.state() == QAudioDecoder::StoppedState); + QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.sourceFilename(), QString("")); + QCOMPARE(d.source(), QStringLiteral("")); QVERIFY(d.audioFormat() == QAudioFormat()); // Test local file - QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME)); - d.setSourceFilename(fileInfo.absoluteFilePath()); - QVERIFY(d.state() == QAudioDecoder::StoppedState); + + d.setSource(*m_wavFile); + QVERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); - QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath()); + 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 stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State))); - 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.state() == QAudioDecoder::DecodingState); - QTRY_VERIFY(!stateSpy.isEmpty()); + + 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().sampleSize(), 16); - QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt); - QCOMPARE(buffer.format().codec(), QString("audio/pcm")); + QCOMPARE(buffer.format().sampleRate(), testFileSampleRate); + QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16); QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono // The decoder should still have no format set QVERIFY(d.audioFormat() == QAudioFormat()); - QVERIFY(errorSpy.isEmpty()); duration += buffer.duration(); @@ -152,7 +420,7 @@ void tst_QAudioDecoderBackend::fileTest() byteCount += buffer.byteCount(); // Now drain the decoder - if (sampleCount < 44094) { + if (sampleCount < testFileSampleCount) { QTRY_COMPARE(d.bufferAvailable(), true); } @@ -160,45 +428,46 @@ void tst_QAudioDecoderBackend::fileTest() buffer = d.read(); QVERIFY(buffer.isValid()); QTRY_VERIFY(!positionSpy.isEmpty()); - QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000)); + QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qint64(duration / 1000)); 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_COMPARE(d.state(), QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); d.stop(); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); - QTRY_COMPARE(durationSpy.count(), 2); + QTRY_VERIFY(!d.isDecoding()); + QTRY_COMPARE(durationSpy.size(), 2); QCOMPARE(d.duration(), qint64(-1)); QVERIFY(!d.bufferAvailable()); readySpy.clear(); bufferChangedSpy.clear(); - stateSpy.clear(); + isDecodingSpy.clear(); durationSpy.clear(); 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); - format.setSampleSize(8); format.setSampleRate(11050); - format.setCodec("audio/pcm"); - format.setSampleType(QAudioFormat::SignedInt); + format.setSampleFormat(QAudioFormat::UInt8); d.setAudioFormat(format); @@ -213,13 +482,16 @@ void tst_QAudioDecoderBackend::fileTest() byteCount = 0; d.start(); - QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState); - QTRY_VERIFY(!stateSpy.isEmpty()); + 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()); @@ -235,40 +507,35 @@ void tst_QAudioDecoderBackend::fileTest() sampleCount += buffer.sampleCount(); byteCount += buffer.byteCount(); - // Now drain the decoder - if (duration < 998000) { - 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()); - QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000)); - QVERIFY(d.position() - (duration / 1000) < 20); + QCOMPARE(positionSpy.takeLast().at(0).toLongLong(), qlonglong(duration / 1000)); + QCOMPARE_LT(d.position() - (duration / 1000), 20u); duration += buffer.duration(); sampleCount += buffer.sampleCount(); byteCount += buffer.byteCount(); - - if (duration < 998000) { - 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()); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); + QVERIFY(!d.isDecoding()); d.stop(); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); - QTRY_COMPARE(durationSpy.count(), 2); + QTRY_VERIFY(!d.isDecoding()); + QTRY_COMPARE(durationSpy.size(), 2); QCOMPARE(d.duration(), qint64(-1)); QVERIFY(!d.bufferAvailable()); } @@ -279,32 +546,32 @@ void tst_QAudioDecoderBackend::fileTest() void tst_QAudioDecoderBackend::unsupportedFileTest() { QAudioDecoder d; - if (d.error() == QAudioDecoder::ServiceMissingError) + if (d.error() == QAudioDecoder::NotSupportedError) QSKIP("There is no audio decoding support on this platform."); QAudioBuffer buffer; - QVERIFY(d.state() == QAudioDecoder::StoppedState); + QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.sourceFilename(), QString("")); + QCOMPARE(d.source(), QStringLiteral("")); QVERIFY(d.audioFormat() == QAudioFormat()); // Test local file - QFileInfo fileInfo(QFINDTESTDATA(TEST_UNSUPPORTED_FILE_NAME)); - d.setSourceFilename(fileInfo.absoluteFilePath()); - QVERIFY(d.state() == QAudioDecoder::StoppedState); + QUrl url = testFileUrl(TEST_UNSUPPORTED_FILE_NAME); + d.setSource(url); + QVERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); - QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath()); + 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 stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State))); - 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.state() == QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); QCOMPARE(d.audioFormat(), QAudioFormat()); QCOMPARE(d.duration(), qint64(-1)); @@ -321,16 +588,17 @@ void tst_QAudioDecoderBackend::unsupportedFileTest() // Check all other spies. QVERIFY(readySpy.isEmpty()); QVERIFY(bufferChangedSpy.isEmpty()); - QVERIFY(stateSpy.isEmpty()); + QVERIFY(isDecodingSpy.isEmpty()); QVERIFY(finishedSpy.isEmpty()); QVERIFY(positionSpy.isEmpty()); - QVERIFY(durationSpy.isEmpty()); + // Either reject the file directly, or set the duration to 5secs on setUrl() and back to -1 on start() + QVERIFY(durationSpy.isEmpty() || durationSpy.size() == 2); errorSpy.clear(); // Try read even if the file is not supported to test robustness. buffer = d.read(); - QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); QVERIFY(!buffer.isValid()); QVERIFY(!d.bufferAvailable()); QCOMPARE(d.position(), qint64(-1)); @@ -338,14 +606,14 @@ void tst_QAudioDecoderBackend::unsupportedFileTest() QVERIFY(errorSpy.isEmpty()); QVERIFY(readySpy.isEmpty()); QVERIFY(bufferChangedSpy.isEmpty()); - QVERIFY(stateSpy.isEmpty()); + QVERIFY(isDecodingSpy.isEmpty()); QVERIFY(finishedSpy.isEmpty()); QVERIFY(positionSpy.isEmpty()); - QVERIFY(durationSpy.isEmpty()); + QVERIFY(durationSpy.isEmpty() || durationSpy.size() == 2); d.stop(); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); QCOMPARE(d.duration(), qint64(-1)); QVERIFY(!d.bufferAvailable()); } @@ -357,32 +625,32 @@ void tst_QAudioDecoderBackend::unsupportedFileTest() void tst_QAudioDecoderBackend::corruptedFileTest() { QAudioDecoder d; - if (d.error() == QAudioDecoder::ServiceMissingError) + if (d.error() == QAudioDecoder::NotSupportedError) QSKIP("There is no audio decoding support on this platform."); QAudioBuffer buffer; - QVERIFY(d.state() == QAudioDecoder::StoppedState); + QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.sourceFilename(), QString("")); + QCOMPARE(d.source(), QUrl()); QVERIFY(d.audioFormat() == QAudioFormat()); // Test local file - QFileInfo fileInfo(QFINDTESTDATA(TEST_CORRUPTED_FILE_NAME)); - d.setSourceFilename(fileInfo.absoluteFilePath()); - QVERIFY(d.state() == QAudioDecoder::StoppedState); + QUrl url = testFileUrl(TEST_CORRUPTED_FILE_NAME); + d.setSource(url); + QVERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); - QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath()); + 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 stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State))); - 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.state() == QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); QVERIFY(!d.bufferAvailable()); QCOMPARE(d.audioFormat(), QAudioFormat()); QCOMPARE(d.duration(), qint64(-1)); @@ -399,7 +667,7 @@ void tst_QAudioDecoderBackend::corruptedFileTest() // Check all other spies. QVERIFY(readySpy.isEmpty()); QVERIFY(bufferChangedSpy.isEmpty()); - QVERIFY(stateSpy.isEmpty()); + QVERIFY(isDecodingSpy.isEmpty()); QVERIFY(finishedSpy.isEmpty()); QVERIFY(positionSpy.isEmpty()); QVERIFY(durationSpy.isEmpty()); @@ -408,7 +676,7 @@ void tst_QAudioDecoderBackend::corruptedFileTest() // Try read even if the file is corrupted to test the robustness. buffer = d.read(); - QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); QVERIFY(!buffer.isValid()); QVERIFY(!d.bufferAvailable()); QCOMPARE(d.position(), qint64(-1)); @@ -416,72 +684,161 @@ void tst_QAudioDecoderBackend::corruptedFileTest() QVERIFY(errorSpy.isEmpty()); QVERIFY(readySpy.isEmpty()); QVERIFY(bufferChangedSpy.isEmpty()); - QVERIFY(stateSpy.isEmpty()); + QVERIFY(isDecodingSpy.isEmpty()); + QVERIFY(finishedSpy.isEmpty()); + QVERIFY(positionSpy.isEmpty()); + QVERIFY(durationSpy.isEmpty()); + + d.stop(); + QTRY_VERIFY(!d.isDecoding()); + QCOMPARE(d.duration(), qint64(-1)); + QVERIFY(!d.bufferAvailable()); +} + +void tst_QAudioDecoderBackend::invalidSource() +{ + QAudioDecoder d; + if (d.error() == QAudioDecoder::NotSupportedError) + QSKIP("There is no audio decoding support on this platform."); + QAudioBuffer buffer; + + QVERIFY(!d.isDecoding()); + QVERIFY(d.bufferAvailable() == false); + QCOMPARE(d.source(), QUrl()); + QVERIFY(d.audioFormat() == QAudioFormat()); + + // Test invalid file source + QFileInfo fileInfo(TEST_INVALID_SOURCE); + QUrl url = QUrl::fromLocalFile(fileInfo.absoluteFilePath()); + d.setSource(url); + QVERIFY(!d.isDecoding()); + QVERIFY(!d.bufferAvailable()); + QCOMPARE(d.source(), url); + + QSignalSpy readySpy(&d, &QAudioDecoder::bufferReady); + QSignalSpy bufferChangedSpy(&d, &QAudioDecoder::bufferAvailableChanged); + QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error))); + 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()); + QVERIFY(!d.bufferAvailable()); + QCOMPARE(d.audioFormat(), QAudioFormat()); + QCOMPARE(d.duration(), qint64(-1)); + QCOMPARE(d.position(), qint64(-1)); + + // Check the error code. + QTRY_VERIFY(!errorSpy.isEmpty()); + + // Have to use qvariant_cast, toInt will return 0 because unrecognized type; + QAudioDecoder::Error errorCode = qvariant_cast<QAudioDecoder::Error>(errorSpy.takeLast().at(0)); + QCOMPARE(errorCode, QAudioDecoder::ResourceError); + QCOMPARE(d.error(), QAudioDecoder::ResourceError); + + // Check all other spies. + QVERIFY(readySpy.isEmpty()); + QVERIFY(bufferChangedSpy.isEmpty()); + QVERIFY(isDecodingSpy.isEmpty()); + QVERIFY(finishedSpy.isEmpty()); + QVERIFY(positionSpy.isEmpty()); + QVERIFY(durationSpy.isEmpty()); + + errorSpy.clear(); + + d.stop(); + QTRY_VERIFY(!d.isDecoding()); + QCOMPARE(d.duration(), qint64(-1)); + QVERIFY(!d.bufferAvailable()); + + QFile file; + file.setFileName(TEST_INVALID_SOURCE); + file.open(QIODevice::ReadOnly); + d.setSourceDevice(&file); + + d.start(); + QTRY_VERIFY(!d.isDecoding()); + QVERIFY(!d.bufferAvailable()); + QCOMPARE(d.audioFormat(), QAudioFormat()); + QCOMPARE(d.duration(), qint64(-1)); + QCOMPARE(d.position(), qint64(-1)); + + // Check the error code. + QTRY_VERIFY(!errorSpy.isEmpty()); + errorCode = qvariant_cast<QAudioDecoder::Error>(errorSpy.takeLast().at(0)); + QCOMPARE(errorCode, QAudioDecoder::ResourceError); + QCOMPARE(d.error(), QAudioDecoder::ResourceError); + // Check all other spies. + QVERIFY(readySpy.isEmpty()); + QVERIFY(bufferChangedSpy.isEmpty()); + QVERIFY(isDecodingSpy.isEmpty()); QVERIFY(finishedSpy.isEmpty()); QVERIFY(positionSpy.isEmpty()); QVERIFY(durationSpy.isEmpty()); + errorSpy.clear(); d.stop(); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); QCOMPARE(d.duration(), qint64(-1)); QVERIFY(!d.bufferAvailable()); } 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::ServiceMissingError) + if (d.error() == QAudioDecoder::NotSupportedError) QSKIP("There is no audio decoding support on this platform."); QAudioBuffer buffer; 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 stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State))); - 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.state() == QAudioDecoder::StoppedState); + QVERIFY(!d.isDecoding()); QVERIFY(d.bufferAvailable() == false); - QCOMPARE(d.sourceFilename(), 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); QVERIFY(d.sourceDevice() == &file); - QVERIFY(d.sourceFilename().isEmpty()); + QVERIFY(d.source().isEmpty()); // We haven't set the format yet QVERIFY(d.audioFormat() == QAudioFormat()); d.start(); - QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState); - QTRY_VERIFY(!stateSpy.isEmpty()); + + 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().sampleSize(), 16); - QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt); - QCOMPARE(buffer.format().codec(), QString("audio/pcm")); + QCOMPARE(buffer.format().sampleRate(), testFileSampleRate); + QCOMPARE(buffer.format().sampleFormat(), QAudioFormat::Int16); QVERIFY(errorSpy.isEmpty()); @@ -489,7 +846,7 @@ void tst_QAudioDecoderBackend::deviceTest() sampleCount += buffer.sampleCount(); // Now drain the decoder - if (sampleCount < 44094) { + if (sampleCount < testFileSampleCount) { QTRY_COMPARE(d.bufferAvailable(), true); } @@ -497,43 +854,50 @@ 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_COMPARE(d.state(), QAudioDecoder::StoppedState); + QTRY_VERIFY(!d.isDecoding()); d.stop(); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); + 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(); - stateSpy.clear(); + isDecodingSpy.clear(); durationSpy.clear(); 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); - format.setSampleSize(8); format.setSampleRate(8000); - format.setCodec("audio/pcm"); - format.setSampleType(QAudioFormat::SignedInt); + format.setSampleFormat(QAudioFormat::UInt8); d.setAudioFormat(format); @@ -541,13 +905,18 @@ void tst_QAudioDecoderBackend::deviceTest() QVERIFY(d.audioFormat() == format); d.start(); - QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState); - QTRY_VERIFY(!stateSpy.isEmpty()); + QVERIFY(d.error() == QAudioDecoder::NoError); + 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()); @@ -560,12 +929,28 @@ void tst_QAudioDecoderBackend::deviceTest() QVERIFY(errorSpy.isEmpty()); d.stop(); - QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState); + 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" |