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 | |
61 | class tst_QAudioOutput : public QObject |
62 | { |
63 | Q_OBJECT |
64 | public: |
65 | tst_QAudioOutput(QObject* parent=0) : QObject(parent) {} |
66 | |
67 | private 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 | |
104 | private: |
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 | |
121 | QString 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 | |
137 | void 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 | |
182 | void 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 | |
194 | void 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 (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 | |
278 | void 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 | |
299 | void 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 | |
324 | void 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 | |
342 | void 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 | |
350 | void 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 | |
363 | void 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 | |
372 | void 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 | |
385 | void 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 | |
424 | void 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 | |
444 | void 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 | |
464 | void 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 | |
484 | void 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 | |
544 | void 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 | |
630 | void 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 | |
717 | void 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 | |
847 | void 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 | |
964 | void 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 | |
973 | void 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 | |
986 | QTEST_MAIN(tst_QAudioOutput) |
987 | |
988 | #include "tst_qaudiooutput.moc" |
989 | |