1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29//TESTED_COMPONENT=src/multimedia
30
31#include <QtTest/QtTest>
32#include <QtCore/qlocale.h>
33#include <QtCore/QTemporaryDir>
34#include <QtCore/QSharedPointer>
35#include <QtCore/QScopedPointer>
36
37#include <qaudiooutput.h>
38#include <qaudiodeviceinfo.h>
39#include <qaudioformat.h>
40#include <qaudio.h>
41
42#include "wavheader.h"
43
44#define AUDIO_BUFFER 192000
45
46#ifndef QTRY_VERIFY2
47#define QTRY_VERIFY2(__expr,__msg) \
48 do { \
49 const int __step = 50; \
50 const int __timeout = 5000; \
51 if (!(__expr)) { \
52 QTest::qWait(0); \
53 } \
54 for (int __i = 0; __i < __timeout && !(__expr); __i+=__step) { \
55 QTest::qWait(__step); \
56 } \
57 QVERIFY2(__expr,__msg); \
58 } while (0)
59#endif
60
61class tst_QAudioOutput : public QObject
62{
63 Q_OBJECT
64public:
65 tst_QAudioOutput(QObject* parent=0) : QObject(parent) {}
66
67private slots:
68 void initTestCase();
69
70 void format();
71 void invalidFormat_data();
72 void invalidFormat();
73
74 void bufferSize_data();
75 void bufferSize();
76
77 void notifyInterval_data();
78 void notifyInterval();
79
80 void disableNotifyInterval();
81
82 void stopWhileStopped();
83 void suspendWhileStopped();
84 void resumeWhileStopped();
85
86 void pull_data(){generate_audiofile_testrows();}
87 void pull();
88
89 void pullSuspendResume_data(){generate_audiofile_testrows();}
90 void pullSuspendResume();
91
92 void push_data(){generate_audiofile_testrows();}
93 void push();
94
95 void pushSuspendResume_data(){generate_audiofile_testrows();}
96 void pushSuspendResume();
97
98 void pushUnderrun_data(){generate_audiofile_testrows();}
99 void pushUnderrun();
100
101 void volume_data();
102 void volume();
103
104private:
105 typedef QSharedPointer<QFile> FilePtr;
106
107 QString formatToFileName(const QAudioFormat &format);
108 void createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate = 440);
109
110 void generate_audiofile_testrows();
111
112 QAudioDeviceInfo audioDevice;
113 QList<QAudioFormat> testFormats;
114 QList<FilePtr> audioFiles;
115 QScopedPointer<QTemporaryDir> m_temporaryDir;
116
117 QScopedPointer<QByteArray> m_byteArray;
118 QScopedPointer<QBuffer> m_buffer;
119};
120
121QString tst_QAudioOutput::formatToFileName(const QAudioFormat &format)
122{
123 const QString formatEndian = (format.byteOrder() == QAudioFormat::LittleEndian)
124 ? QString("LE") : QString("BE");
125
126 const QString formatSigned = (format.sampleType() == QAudioFormat::SignedInt)
127 ? QString("signed") : QString("unsigned");
128
129 return QString("%1_%2_%3_%4_%5")
130 .arg(a: format.sampleRate())
131 .arg(a: format.sampleSize())
132 .arg(a: formatSigned)
133 .arg(a: formatEndian)
134 .arg(a: format.channelCount());
135}
136
137void tst_QAudioOutput::createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate)
138{
139 const int channelBytes = format.sampleSize() / 8;
140 const int sampleBytes = format.channelCount() * channelBytes;
141
142 Q_ASSERT(length % sampleBytes == 0);
143 Q_UNUSED(sampleBytes) // suppress warning in release builds
144
145 m_byteArray.reset(other: new QByteArray(length, 0));
146 unsigned char *ptr = reinterpret_cast<unsigned char *>(m_byteArray->data());
147 int sampleIndex = 0;
148
149 while (length) {
150 const qreal x = qSin(v: 2 * M_PI * sampleRate * qreal(sampleIndex % format.sampleRate()) / format.sampleRate());
151 for (int i=0; i<format.channelCount(); ++i) {
152 if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt) {
153 const quint8 value = static_cast<quint8>((1.0 + x) / 2 * 255);
154 *reinterpret_cast<quint8*>(ptr) = value;
155 } else if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::SignedInt) {
156 const qint8 value = static_cast<qint8>(x * 127);
157 *reinterpret_cast<quint8*>(ptr) = value;
158 } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::UnSignedInt) {
159 quint16 value = static_cast<quint16>((1.0 + x) / 2 * 65535);
160 if (format.byteOrder() == QAudioFormat::LittleEndian)
161 qToLittleEndian<quint16>(src: value, dest: ptr);
162 else
163 qToBigEndian<quint16>(src: value, dest: ptr);
164 } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt) {
165 qint16 value = static_cast<qint16>(x * 32767);
166 if (format.byteOrder() == QAudioFormat::LittleEndian)
167 qToLittleEndian<qint16>(src: value, dest: ptr);
168 else
169 qToBigEndian<qint16>(src: value, dest: ptr);
170 }
171
172 ptr += channelBytes;
173 length -= channelBytes;
174 }
175 ++sampleIndex;
176 }
177
178 m_buffer.reset(other: new QBuffer(m_byteArray.data(), this));
179 Q_ASSERT(m_buffer->open(QIODevice::ReadOnly));
180}
181
182void tst_QAudioOutput::generate_audiofile_testrows()
183{
184 QTest::addColumn<FilePtr>(name: "audioFile");
185 QTest::addColumn<QAudioFormat>(name: "audioFormat");
186
187 for (int i=0; i<audioFiles.count(); i++) {
188 QTest::newRow(dataTag: QString("Audio File %1").arg(a: i).toLocal8Bit().constData())
189 << audioFiles.at(i) << testFormats.at(i);
190
191 }
192}
193
194void tst_QAudioOutput::initTestCase()
195{
196 qRegisterMetaType<QAudioFormat>();
197
198 // Only perform tests if audio output device exists
199 const QList<QAudioDeviceInfo> devices =
200 QAudioDeviceInfo::availableDevices(mode: QAudio::AudioOutput);
201
202 if (devices.size() <= 0)
203 QSKIP("No audio backend");
204
205 audioDevice = QAudioDeviceInfo::defaultOutputDevice();
206
207
208 QAudioFormat format;
209
210 format.setCodec("audio/pcm");
211
212 if (audioDevice.isFormatSupported(format: audioDevice.preferredFormat()))
213 testFormats.append(t: audioDevice.preferredFormat());
214
215 // PCM 8000 mono S8
216 format.setSampleRate(8000);
217 format.setSampleSize(8);
218 format.setSampleType(QAudioFormat::SignedInt);
219 format.setByteOrder(QAudioFormat::LittleEndian);
220 format.setChannelCount(1);
221 if (audioDevice.isFormatSupported(format))
222 testFormats.append(t: format);
223
224 // PCM 11025 mono S16LE
225 format.setSampleRate(11025);
226 format.setSampleSize(16);
227 if (audioDevice.isFormatSupported(format))
228 testFormats.append(t: format);
229
230 // PCM 22050 mono S16LE
231 format.setSampleRate(22050);
232 if (audioDevice.isFormatSupported(format))
233 testFormats.append(t: format);
234
235 // PCM 22050 stereo S16LE
236 format.setChannelCount(2);
237 if (audioDevice.isFormatSupported(format))
238 testFormats.append(t: format);
239
240 // PCM 44100 stereo S16LE
241 format.setSampleRate(44100);
242 if (audioDevice.isFormatSupported(format))
243 testFormats.append(t: format);
244
245 // PCM 48000 stereo S16LE
246 format.setSampleRate(48000);
247 if (audioDevice.isFormatSupported(format))
248 testFormats.append(t: format);
249
250 QVERIFY(testFormats.size());
251
252 const QChar slash = QLatin1Char('/');
253 QString temporaryPattern = QDir::tempPath();
254 if (!temporaryPattern.endsWith(c: slash))
255 temporaryPattern += slash;
256 temporaryPattern += "tst_qaudiooutputXXXXXX";
257 m_temporaryDir.reset(other: new QTemporaryDir(temporaryPattern));
258 m_temporaryDir->setAutoRemove(true);
259 QVERIFY(m_temporaryDir->isValid());
260
261 const QString temporaryAudioPath = m_temporaryDir->path() + slash;
262 for (const QAudioFormat &format : qAsConst(t&: testFormats)) {
263 qint64 len = (format.sampleRate()*format.channelCount()*(format.sampleSize()/8)*2); // 2 seconds
264 createSineWaveData(format, length: len);
265 // Write generate sine wave data to file
266 const QString fileName = temporaryAudioPath + QStringLiteral("generated")
267 + formatToFileName(format) + QStringLiteral(".wav");
268 FilePtr file(new QFile(fileName));
269 QVERIFY2(file->open(QIODevice::WriteOnly), qPrintable(file->errorString()));
270 WavHeader wavHeader(format, len);
271 wavHeader.write(device&: *file.data());
272 file->write(data: m_byteArray->data(), len);
273 file->close();
274 audioFiles.append(t: file);
275 }
276}
277
278void tst_QAudioOutput::format()
279{
280 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
281
282 QAudioFormat requested = audioDevice.preferredFormat();
283 QAudioFormat actual = audioOutput.format();
284
285 QVERIFY2((requested.channelCount() == actual.channelCount()),
286 QString("channels: requested=%1, actual=%2").arg(requested.channelCount()).arg(actual.channelCount()).toLocal8Bit().constData());
287 QVERIFY2((requested.sampleRate() == actual.sampleRate()),
288 QString("sampleRate: requested=%1, actual=%2").arg(requested.sampleRate()).arg(actual.sampleRate()).toLocal8Bit().constData());
289 QVERIFY2((requested.sampleSize() == actual.sampleSize()),
290 QString("sampleSize: requested=%1, actual=%2").arg(requested.sampleSize()).arg(actual.sampleSize()).toLocal8Bit().constData());
291 QVERIFY2((requested.codec() == actual.codec()),
292 QString("codec: requested=%1, actual=%2").arg(requested.codec()).arg(actual.codec()).toLocal8Bit().constData());
293 QVERIFY2((requested.byteOrder() == actual.byteOrder()),
294 QString("byteOrder: requested=%1, actual=%2").arg(requested.byteOrder()).arg(actual.byteOrder()).toLocal8Bit().constData());
295 QVERIFY2((requested.sampleType() == actual.sampleType()),
296 QString("sampleType: requested=%1, actual=%2").arg(requested.sampleType()).arg(actual.sampleType()).toLocal8Bit().constData());
297}
298
299void tst_QAudioOutput::invalidFormat_data()
300{
301 QTest::addColumn<QAudioFormat>(name: "invalidFormat");
302
303 QAudioFormat format;
304
305 QTest::newRow(dataTag: "Null Format")
306 << format;
307
308 format = audioDevice.preferredFormat();
309 format.setChannelCount(0);
310 QTest::newRow(dataTag: "Channel count 0")
311 << format;
312
313 format = audioDevice.preferredFormat();
314 format.setSampleRate(0);
315 QTest::newRow(dataTag: "Sample rate 0")
316 << format;
317
318 format = audioDevice.preferredFormat();
319 format.setSampleSize(0);
320 QTest::newRow(dataTag: "Sample size 0")
321 << format;
322}
323
324void tst_QAudioOutput::invalidFormat()
325{
326 QFETCH(QAudioFormat, invalidFormat);
327
328 QVERIFY2(!audioDevice.isFormatSupported(invalidFormat),
329 "isFormatSupported() is returning true on an invalid format");
330
331 QAudioOutput audioOutput(invalidFormat, this);
332
333 // Check that we are in the default state before calling start
334 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
335 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
336
337 audioOutput.start();
338 // Check that error is raised
339 QTRY_VERIFY2((audioOutput.error() == QAudio::OpenError),"error() was not set to QAudio::OpenError after start()");
340}
341
342void tst_QAudioOutput::bufferSize_data()
343{
344 QTest::addColumn<int>(name: "bufferSize");
345 QTest::newRow(dataTag: "Buffer size 512") << 512;
346 QTest::newRow(dataTag: "Buffer size 4096") << 4096;
347 QTest::newRow(dataTag: "Buffer size 8192") << 8192;
348}
349
350void tst_QAudioOutput::bufferSize()
351{
352 QFETCH(int, bufferSize);
353 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
354
355 QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() was not set to QAudio::NoError on creation(%1)").arg(bufferSize).toLocal8Bit().constData());
356
357 audioOutput.setBufferSize(bufferSize);
358 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setBufferSize");
359 QVERIFY2((audioOutput.bufferSize() == bufferSize),
360 QString("bufferSize: requested=%1, actual=%2").arg(bufferSize).arg(audioOutput.bufferSize()).toLocal8Bit().constData());
361}
362
363void tst_QAudioOutput::notifyInterval_data()
364{
365 QTest::addColumn<int>(name: "interval");
366 QTest::newRow(dataTag: "Notify interval 50") << 50;
367 QTest::newRow(dataTag: "Notify interval 100") << 100;
368 QTest::newRow(dataTag: "Notify interval 250") << 250;
369 QTest::newRow(dataTag: "Notify interval 1000") << 1000;
370}
371
372void tst_QAudioOutput::notifyInterval()
373{
374 QFETCH(int, interval);
375 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
376
377 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
378
379 audioOutput.setNotifyInterval(interval);
380 QVERIFY2((audioOutput.error() == QAudio::NoError), QString("error() is not QAudio::NoError after setNotifyInterval(%1)").arg(interval).toLocal8Bit().constData());
381 QVERIFY2((audioOutput.notifyInterval() == interval),
382 QString("notifyInterval: requested=%1, actual=%2").arg(interval).arg(audioOutput.notifyInterval()).toLocal8Bit().constData());
383}
384
385void tst_QAudioOutput::disableNotifyInterval()
386{
387 // Sets an invalid notification interval (QAudioOutput::setNotifyInterval(0))
388 // Checks that
389 // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
390 // - if <= 0, set to zero and disable notify signal
391
392 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
393
394 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError on creation");
395
396 audioOutput.setNotifyInterval(0);
397 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(0)");
398 QVERIFY2((audioOutput.notifyInterval() == 0),
399 "notifyInterval() is not zero after setNotifyInterval(0)");
400
401 audioOutput.setNotifyInterval(-1);
402 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after setNotifyInterval(-1)");
403 QVERIFY2((audioOutput.notifyInterval() == 0),
404 "notifyInterval() is not zero after setNotifyInterval(-1)");
405
406 //start and run to check if notify() is emitted
407 if (audioFiles.size() > 0) {
408 QAudioOutput audioOutputCheck(testFormats.at(i: 0), this);
409 audioOutputCheck.setNotifyInterval(0);
410 audioOutputCheck.setVolume(0.1f);
411
412 QSignalSpy notifySignal(&audioOutputCheck, SIGNAL(notify()));
413 QFile *audioFile = audioFiles.at(i: 0).data();
414 audioFile->open(flags: QIODevice::ReadOnly);
415 audioOutputCheck.start(device: audioFile);
416 QTest::qWait(ms: 3000); // 3 seconds should be plenty
417 audioOutputCheck.stop();
418 QVERIFY2((notifySignal.count() == 0),
419 QString("didn't disable notify interval: shouldn't have got any but got %1").arg(notifySignal.count()).toLocal8Bit().constData());
420 audioFile->close();
421 }
422}
423
424void tst_QAudioOutput::stopWhileStopped()
425{
426 // Calls QAudioOutput::stop() when object is already in StoppedState
427 // Checks that
428 // - No state change occurs
429 // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
430
431 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
432
433 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
434 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
435
436 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
437 audioOutput.stop();
438
439 // Check that no state transition occurred
440 QVERIFY2((stateSignal.count() == 0), "stop() while stopped is emitting a signal and it shouldn't");
441 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
442}
443
444void tst_QAudioOutput::suspendWhileStopped()
445{
446 // Calls QAudioOutput::suspend() when object is already in StoppedState
447 // Checks that
448 // - No state change occurs
449 // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
450
451 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
452
453 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
454 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
455
456 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
457 audioOutput.suspend();
458
459 // Check that no state transition occurred
460 QVERIFY2((stateSignal.count() == 0), "stop() while suspended is emitting a signal and it shouldn't");
461 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after stop()");
462}
463
464void tst_QAudioOutput::resumeWhileStopped()
465{
466 // Calls QAudioOutput::resume() when object is already in StoppedState
467 // Checks that
468 // - No state change occurs
469 // - No error is raised (QAudioOutput::error() returns QAudio::NoError)
470
471 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
472
473 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
474 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
475
476 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
477 audioOutput.resume();
478
479 // Check that no state transition occurred
480 QVERIFY2((stateSignal.count() == 0), "resume() while stopped is emitting a signal and it shouldn't");
481 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError after resume()");
482}
483
484void tst_QAudioOutput::pull()
485{
486 QFETCH(FilePtr, audioFile);
487 QFETCH(QAudioFormat, audioFormat);
488
489 QAudioOutput audioOutput(audioFormat, this);
490
491 audioOutput.setNotifyInterval(100);
492 audioOutput.setVolume(0.1f);
493
494 QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
495 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
496
497 // Check that we are in the default state before calling start
498 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
499 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
500 QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
501
502 audioFile->close();
503 audioFile->open(flags: QIODevice::ReadOnly);
504 audioFile->seek(offset: WavHeader::headerLength());
505
506 audioOutput.start(device: audioFile.data());
507
508 // Check that QAudioOutput immediately transitions to ActiveState
509 QTRY_VERIFY2((stateSignal.count() == 1),
510 QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
511 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
512 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
513 QVERIFY(audioOutput.periodSize() > 0);
514 stateSignal.clear();
515
516 // Check that 'elapsed' increases
517 QTest::qWait(ms: 40);
518 QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
519
520 // Wait until playback finishes
521 QTRY_VERIFY2(audioFile->atEnd(), "didn't play to EOF");
522 QTRY_VERIFY(stateSignal.count() > 0);
523 QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
524 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
525 stateSignal.clear();
526
527 qint64 processedUs = audioOutput.processedUSecs();
528
529 audioOutput.stop();
530 QTest::qWait(ms: 40);
531 QVERIFY2((stateSignal.count() == 1),
532 QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
533 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
534
535 QVERIFY2((processedUs == 2000000),
536 QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
537 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
538 QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
539 QVERIFY2(notifySignal.count() > 0, "not emitting notify() signal");
540
541 audioFile->close();
542}
543
544void tst_QAudioOutput::pullSuspendResume()
545{
546 QFETCH(FilePtr, audioFile);
547 QFETCH(QAudioFormat, audioFormat);
548 QAudioOutput audioOutput(audioFormat, this);
549
550 audioOutput.setNotifyInterval(100);
551 audioOutput.setVolume(0.1f);
552
553 QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
554 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
555
556 // Check that we are in the default state before calling start
557 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
558 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
559 QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
560
561 audioFile->close();
562 audioFile->open(flags: QIODevice::ReadOnly);
563 audioFile->seek(offset: WavHeader::headerLength());
564
565 audioOutput.start(device: audioFile.data());
566 // Check that QAudioOutput immediately transitions to ActiveState
567 QTRY_VERIFY2((stateSignal.count() == 1),
568 QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
569 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()");
570 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
571 QVERIFY(audioOutput.periodSize() > 0);
572 stateSignal.clear();
573
574 // Wait for half of clip to play
575 QTest::qWait(ms: 1000);
576
577 audioOutput.suspend();
578
579 // Give backends running in separate threads a chance to suspend.
580 QTest::qWait(ms: 100);
581
582 QVERIFY2((stateSignal.count() == 1),
583 QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead")
584 .arg(stateSignal.count()).toLocal8Bit().constData());
585 QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()");
586 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()");
587 stateSignal.clear();
588
589 // Check that only 'elapsed', and not 'processed' increases while suspended
590 qint64 elapsedUs = audioOutput.elapsedUSecs();
591 qint64 processedUs = audioOutput.processedUSecs();
592 QTest::qWait(ms: 1000);
593 QVERIFY(audioOutput.elapsedUSecs() > elapsedUs);
594 QVERIFY(audioOutput.processedUSecs() == processedUs);
595
596 audioOutput.resume();
597
598 // Check that QAudioOutput immediately transitions to ActiveState
599 QVERIFY2((stateSignal.count() == 1),
600 QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
601 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()");
602 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
603 stateSignal.clear();
604
605 // Wait until playback finishes
606 QTest::qWait(ms: 3000); // 3 seconds should be plenty
607
608 QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
609 QVERIFY(stateSignal.count() > 0);
610 QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
611 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
612 stateSignal.clear();
613
614 processedUs = audioOutput.processedUSecs();
615
616 audioOutput.stop();
617 QTest::qWait(ms: 40);
618 QVERIFY2((stateSignal.count() == 1),
619 QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
620 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
621
622 QVERIFY2((processedUs == 2000000),
623 QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
624 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
625 QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
626
627 audioFile->close();
628}
629
630void tst_QAudioOutput::push()
631{
632 QFETCH(FilePtr, audioFile);
633 QFETCH(QAudioFormat, audioFormat);
634
635 QAudioOutput audioOutput(audioFormat, this);
636
637 audioOutput.setNotifyInterval(100);
638 audioOutput.setVolume(0.1f);
639
640 QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
641 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
642
643 // Check that we are in the default state before calling start
644 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
645 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
646 QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
647
648 audioFile->close();
649 audioFile->open(flags: QIODevice::ReadOnly);
650 audioFile->seek(offset: WavHeader::headerLength());
651
652 QIODevice* feed = audioOutput.start();
653
654 // Check that QAudioOutput immediately transitions to IdleState
655 QTRY_VERIFY2((stateSignal.count() == 1),
656 QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
657 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
658 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
659 QVERIFY(audioOutput.periodSize() > 0);
660 stateSignal.clear();
661
662 // Check that 'elapsed' increases
663 QTest::qWait(ms: 40);
664 QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
665 QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
666
667 qint64 written = 0;
668 bool firstBuffer = true;
669 QByteArray buffer(AUDIO_BUFFER, 0);
670
671 while (written < audioFile->size()-WavHeader::headerLength()) {
672
673 if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
674 qint64 len = audioFile->read(data: buffer.data(),maxlen: audioOutput.periodSize());
675 written += feed->write(data: buffer.constData(), len);
676
677 if (firstBuffer) {
678 // Check for transition to ActiveState when data is provided
679 QVERIFY2((stateSignal.count() == 1),
680 QString("didn't emit signal after receiving data, got %1 signals instead")
681 .arg(stateSignal.count()).toLocal8Bit().constData());
682 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
683 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
684 firstBuffer = false;
685 stateSignal.clear();
686 }
687 } else
688 QTest::qWait(ms: 20);
689 }
690
691 // Wait until playback finishes
692 QTest::qWait(ms: 3000); // 3 seconds should be plenty
693
694 QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
695 QVERIFY(stateSignal.count() > 0);
696 QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
697 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
698 stateSignal.clear();
699
700 qint64 processedUs = audioOutput.processedUSecs();
701
702 audioOutput.stop();
703 QTest::qWait(ms: 40);
704 QVERIFY2((stateSignal.count() == 1),
705 QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
706 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
707
708 QVERIFY2((processedUs == 2000000),
709 QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
710 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
711 QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
712 QVERIFY2(notifySignal.count() > 0, "not emitting notify signal");
713
714 audioFile->close();
715}
716
717void tst_QAudioOutput::pushSuspendResume()
718{
719 QFETCH(FilePtr, audioFile);
720 QFETCH(QAudioFormat, audioFormat);
721
722 QAudioOutput audioOutput(audioFormat, this);
723
724 audioOutput.setNotifyInterval(100);
725 audioOutput.setVolume(0.1f);
726
727 QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
728 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
729
730 // Check that we are in the default state before calling start
731 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
732 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
733 QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
734
735 audioFile->close();
736 audioFile->open(flags: QIODevice::ReadOnly);
737 audioFile->seek(offset: WavHeader::headerLength());
738
739 QIODevice* feed = audioOutput.start();
740
741 // Check that QAudioOutput immediately transitions to IdleState
742 QTRY_VERIFY2((stateSignal.count() == 1),
743 QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
744 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
745 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
746 QVERIFY(audioOutput.periodSize() > 0);
747 stateSignal.clear();
748
749 // Check that 'elapsed' increases
750 QTest::qWait(ms: 40);
751 QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
752 QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
753
754 qint64 written = 0;
755 bool firstBuffer = true;
756 QByteArray buffer(AUDIO_BUFFER, 0);
757
758 // Play half of the clip
759 while (written < (audioFile->size()-WavHeader::headerLength())/2) {
760
761 if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
762 qint64 len = audioFile->read(data: buffer.data(),maxlen: audioOutput.periodSize());
763 written += feed->write(data: buffer.constData(), len);
764
765 if (firstBuffer) {
766 // Check for transition to ActiveState when data is provided
767 QVERIFY2((stateSignal.count() == 1),
768 QString("didn't emit signal after receiving data, got %1 signals instead")
769 .arg(stateSignal.count()).toLocal8Bit().constData());
770 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
771 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
772 firstBuffer = false;
773 }
774 } else
775 QTest::qWait(ms: 20);
776 }
777 stateSignal.clear();
778
779 audioOutput.suspend();
780
781 // Give backends running in separate threads a chance to suspend.
782 QTest::qWait(ms: 100);
783
784 QVERIFY2((stateSignal.count() == 1),
785 QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead")
786 .arg(stateSignal.count()).toLocal8Bit().constData());
787 QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()");
788 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()");
789 stateSignal.clear();
790
791 // Check that only 'elapsed', and not 'processed' increases while suspended
792 qint64 elapsedUs = audioOutput.elapsedUSecs();
793 qint64 processedUs = audioOutput.processedUSecs();
794 QTest::qWait(ms: 1000);
795 QVERIFY(audioOutput.elapsedUSecs() > elapsedUs);
796 QVERIFY(audioOutput.processedUSecs() == processedUs);
797
798 audioOutput.resume();
799
800 // Give backends running in separate threads a chance to resume
801 // but not too much or the rest of the file may be processed
802 QTest::qWait(ms: 20);
803
804 // Check that QAudioOutput immediately transitions to IdleState
805 QVERIFY2((stateSignal.count() == 1),
806 QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
807 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after resume()");
808 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()");
809 stateSignal.clear();
810
811 // Play rest of the clip
812 while (!audioFile->atEnd()) {
813 if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
814 qint64 len = audioFile->read(data: buffer.data(),maxlen: audioOutput.periodSize());
815 written += feed->write(data: buffer.constData(), len);
816 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after writing audio data");
817 } else
818 QTest::qWait(ms: 20);
819 }
820 stateSignal.clear();
821
822 // Wait until playback finishes
823 QTest::qWait(ms: 1000); // 1 seconds should be plenty
824
825 QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
826 QVERIFY(stateSignal.count() > 0);
827 QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState);
828 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
829 stateSignal.clear();
830
831 processedUs = audioOutput.processedUSecs();
832
833 audioOutput.stop();
834 QTest::qWait(ms: 40);
835 QVERIFY2((stateSignal.count() == 1),
836 QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
837 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
838
839 QVERIFY2((processedUs == 2000000),
840 QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
841 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
842 QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
843
844 audioFile->close();
845}
846
847void tst_QAudioOutput::pushUnderrun()
848{
849 QFETCH(FilePtr, audioFile);
850 QFETCH(QAudioFormat, audioFormat);
851
852 QAudioOutput audioOutput(audioFormat, this);
853
854 audioOutput.setNotifyInterval(100);
855 audioOutput.setVolume(0.1f);
856
857 QSignalSpy notifySignal(&audioOutput, SIGNAL(notify()));
858 QSignalSpy stateSignal(&audioOutput, SIGNAL(stateChanged(QAudio::State)));
859
860 // Check that we are in the default state before calling start
861 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "state() was not set to StoppedState before start()");
862 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() was not set to QAudio::NoError before start()");
863 QVERIFY2((audioOutput.elapsedUSecs() == qint64(0)),"elapsedUSecs() not zero on creation");
864
865 audioFile->close();
866 audioFile->open(flags: QIODevice::ReadOnly);
867 audioFile->seek(offset: WavHeader::headerLength());
868
869 QIODevice* feed = audioOutput.start();
870
871 // Check that QAudioOutput immediately transitions to IdleState
872 QTRY_VERIFY2((stateSignal.count() == 1),
873 QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
874 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()");
875 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()");
876 QVERIFY(audioOutput.periodSize() > 0);
877 stateSignal.clear();
878
879 // Check that 'elapsed' increases
880 QTest::qWait(ms: 40);
881 QVERIFY2((audioOutput.elapsedUSecs() > 0), "elapsedUSecs() is still zero after start()");
882 QVERIFY2((audioOutput.processedUSecs() == qint64(0)), "processedUSecs() is not zero after start()");
883
884 qint64 written = 0;
885 bool firstBuffer = true;
886 QByteArray buffer(AUDIO_BUFFER, 0);
887
888 // Play half of the clip
889 while (written < (audioFile->size()-WavHeader::headerLength())/2) {
890
891 if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
892 qint64 len = audioFile->read(data: buffer.data(),maxlen: audioOutput.periodSize());
893 written += feed->write(data: buffer.constData(), len);
894
895 if (firstBuffer) {
896 // Check for transition to ActiveState when data is provided
897 QVERIFY2((stateSignal.count() == 1),
898 QString("didn't emit signal after receiving data, got %1 signals instead")
899 .arg(stateSignal.count()).toLocal8Bit().constData());
900 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
901 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
902 firstBuffer = false;
903 }
904 } else
905 QTest::qWait(ms: 20);
906 }
907 stateSignal.clear();
908
909 // Wait for data to be played
910 QTest::qWait(ms: 1000);
911
912 QVERIFY2((stateSignal.count() == 1),
913 QString("didn't emit IdleState signal after suspend(), got %1 signals instead")
914 .arg(stateSignal.count()).toLocal8Bit().constData());
915 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState, no data");
916 QVERIFY2((audioOutput.error() == QAudio::UnderrunError), "error state is not equal to QAudio::UnderrunError, no data");
917 stateSignal.clear();
918
919 firstBuffer = true;
920 // Play rest of the clip
921 while (!audioFile->atEnd()) {
922 if (audioOutput.bytesFree() >= audioOutput.periodSize()) {
923 qint64 len = audioFile->read(data: buffer.data(),maxlen: audioOutput.periodSize());
924 written += feed->write(data: buffer.constData(), len);
925 if (firstBuffer) {
926 // Check for transition to ActiveState when data is provided
927 QVERIFY2((stateSignal.count() == 1),
928 QString("didn't emit signal after receiving data, got %1 signals instead")
929 .arg(stateSignal.count()).toLocal8Bit().constData());
930 QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data");
931 QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data");
932 firstBuffer = false;
933 }
934 } else
935 QTest::qWait(ms: 20);
936 }
937 stateSignal.clear();
938
939 // Wait until playback finishes
940 QTest::qWait(ms: 1000); // 1 seconds should be plenty
941
942 QVERIFY2(audioFile->atEnd(), "didn't play to EOF");
943 QVERIFY2((stateSignal.count() == 1),
944 QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
945 QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF");
946 stateSignal.clear();
947
948 qint64 processedUs = audioOutput.processedUSecs();
949
950 audioOutput.stop();
951 QTest::qWait(ms: 40);
952 QVERIFY2((stateSignal.count() == 1),
953 QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.count()).toLocal8Bit().constData());
954 QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()");
955
956 QVERIFY2((processedUs == 2000000),
957 QString("processedUSecs() doesn't equal file duration in us (%1)").arg(processedUs).toLocal8Bit().constData());
958 QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()");
959 QVERIFY2((audioOutput.elapsedUSecs() == (qint64)0), "elapsedUSecs() not equal to zero in StoppedState");
960
961 audioFile->close();
962}
963
964void tst_QAudioOutput::volume_data()
965{
966 QTest::addColumn<float>(name: "actualFloat");
967 QTest::addColumn<int>(name: "expectedInt");
968 QTest::newRow(dataTag: "Volume 0.3") << 0.3f << 3;
969 QTest::newRow(dataTag: "Volume 0.6") << 0.6f << 6;
970 QTest::newRow(dataTag: "Volume 0.9") << 0.9f << 9;
971}
972
973void tst_QAudioOutput::volume()
974{
975 QFETCH(float, actualFloat);
976 QFETCH(int, expectedInt);
977 QAudioOutput audioOutput(audioDevice.preferredFormat(), this);
978
979 audioOutput.setVolume(actualFloat);
980 QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt);
981 // Wait a while to see if this changes
982 QTest::qWait(ms: 500);
983 QTRY_VERIFY(qRound(audioOutput.volume()*10.0f) == expectedInt);
984}
985
986QTEST_MAIN(tst_QAudioOutput)
987
988#include "tst_qaudiooutput.moc"
989

source code of qtmultimedia/tests/auto/integration/qaudiooutput/tst_qaudiooutput.cpp