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 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#include <QtTest/QtTest>
30#include <QDebug>
31#include "qaudiodecoder.h"
32
33#include "../shared/mediafileselector.h"
34
35#define TEST_FILE_NAME "testdata/test.wav"
36#define TEST_UNSUPPORTED_FILE_NAME "testdata/test-unsupported.avi"
37#define TEST_CORRUPTED_FILE_NAME "testdata/test-corrupted.wav"
38
39QT_USE_NAMESPACE
40
41/*
42 This is the backend conformance test.
43
44 Since it relies on platform media framework
45 it may be less stable.
46*/
47
48class tst_QAudioDecoderBackend : public QObject
49{
50 Q_OBJECT
51public slots:
52 void init();
53 void cleanup();
54 void initTestCase();
55
56private slots:
57 void fileTest();
58 void unsupportedFileTest();
59 void corruptedFileTest();
60 void deviceTest();
61
62private:
63 bool isWavSupported();
64};
65
66void tst_QAudioDecoderBackend::init()
67{
68}
69
70void tst_QAudioDecoderBackend::initTestCase()
71{
72 QAudioDecoder d;
73 if (!d.isAvailable())
74 QSKIP("Audio decoder service is not available");
75
76 qRegisterMetaType<QMediaContent>();
77}
78
79void tst_QAudioDecoderBackend::cleanup()
80{
81}
82
83bool tst_QAudioDecoderBackend::isWavSupported()
84{
85#ifdef WAV_SUPPORT_NOT_FORCED
86 return !MediaFileSelector::selectMediaFile(QStringList() << QFINDTESTDATA(TEST_FILE_NAME)).isNull();
87#else
88 return true;
89#endif
90}
91
92void tst_QAudioDecoderBackend::fileTest()
93{
94 if (!isWavSupported())
95 QSKIP("Sound format is not supported");
96
97 QAudioDecoder d;
98 if (d.error() == QAudioDecoder::ServiceMissingError)
99 QSKIP("There is no audio decoding support on this platform.");
100 QAudioBuffer buffer;
101 quint64 duration = 0;
102 int byteCount = 0;
103 int sampleCount = 0;
104
105 QVERIFY(d.state() == QAudioDecoder::StoppedState);
106 QVERIFY(d.bufferAvailable() == false);
107 QCOMPARE(d.sourceFilename(), QString(""));
108 QVERIFY(d.audioFormat() == QAudioFormat());
109
110 // Test local file
111 QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
112 d.setSourceFilename(fileInfo.absoluteFilePath());
113 QVERIFY(d.state() == QAudioDecoder::StoppedState);
114 QVERIFY(!d.bufferAvailable());
115 QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
116
117 QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
118 QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
119 QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
120 QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
121 QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
122 QSignalSpy finishedSpy(&d, SIGNAL(finished()));
123 QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
124
125 d.start();
126 QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
127 QTRY_VERIFY(!stateSpy.isEmpty());
128 QTRY_VERIFY(!readySpy.isEmpty());
129 QTRY_VERIFY(!bufferChangedSpy.isEmpty());
130 QVERIFY(d.bufferAvailable());
131 QTRY_VERIFY(!durationSpy.isEmpty());
132 QVERIFY(qAbs(d.duration() - 1000) < 20);
133
134 buffer = d.read();
135 QVERIFY(buffer.isValid());
136
137 // Test file is 44.1K 16bit mono, 44094 samples
138 QCOMPARE(buffer.format().channelCount(), 1);
139 QCOMPARE(buffer.format().sampleRate(), 44100);
140 QCOMPARE(buffer.format().sampleSize(), 16);
141 QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt);
142 QCOMPARE(buffer.format().codec(), QString("audio/pcm"));
143 QCOMPARE(buffer.byteCount(), buffer.sampleCount() * 2); // 16bit mono
144
145 // The decoder should still have no format set
146 QVERIFY(d.audioFormat() == QAudioFormat());
147
148 QVERIFY(errorSpy.isEmpty());
149
150 duration += buffer.duration();
151 sampleCount += buffer.sampleCount();
152 byteCount += buffer.byteCount();
153
154 // Now drain the decoder
155 if (sampleCount < 44094) {
156 QTRY_COMPARE(d.bufferAvailable(), true);
157 }
158
159 while (d.bufferAvailable()) {
160 buffer = d.read();
161 QVERIFY(buffer.isValid());
162 QTRY_VERIFY(!positionSpy.isEmpty());
163 QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
164
165 duration += buffer.duration();
166 sampleCount += buffer.sampleCount();
167 byteCount += buffer.byteCount();
168
169 if (sampleCount < 44094) {
170 QTRY_COMPARE(d.bufferAvailable(), true);
171 }
172 }
173
174 // Make sure the duration is roughly correct (+/- 20ms)
175 QCOMPARE(sampleCount, 44094);
176 QCOMPARE(byteCount, 44094 * 2);
177 QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
178 QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
179 QTRY_COMPARE(finishedSpy.count(), 1);
180 QVERIFY(!d.bufferAvailable());
181 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
182
183 d.stop();
184 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
185 QTRY_COMPARE(durationSpy.count(), 2);
186 QCOMPARE(d.duration(), qint64(-1));
187 QVERIFY(!d.bufferAvailable());
188 readySpy.clear();
189 bufferChangedSpy.clear();
190 stateSpy.clear();
191 durationSpy.clear();
192 finishedSpy.clear();
193 positionSpy.clear();
194
195 // change output audio format
196 QAudioFormat format;
197 format.setChannelCount(2);
198 format.setSampleSize(8);
199 format.setSampleRate(11050);
200 format.setCodec("audio/pcm");
201 format.setSampleType(QAudioFormat::SignedInt);
202
203 d.setAudioFormat(format);
204
205 // We expect 1 second still, at 11050 * 2 samples == 22k samples.
206 // (at 1 byte/sample -> 22kb)
207
208 // Make sure it stuck
209 QVERIFY(d.audioFormat() == format);
210
211 duration = 0;
212 sampleCount = 0;
213 byteCount = 0;
214
215 d.start();
216 QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
217 QTRY_VERIFY(!stateSpy.isEmpty());
218 QTRY_VERIFY(!readySpy.isEmpty());
219 QTRY_VERIFY(!bufferChangedSpy.isEmpty());
220 QVERIFY(d.bufferAvailable());
221 QTRY_VERIFY(!durationSpy.isEmpty());
222 QVERIFY(qAbs(d.duration() - 1000) < 20);
223
224 buffer = d.read();
225 QVERIFY(buffer.isValid());
226 // See if we got the right format
227 QVERIFY(buffer.format() == format);
228
229 // The decoder should still have the same format
230 QVERIFY(d.audioFormat() == format);
231
232 QVERIFY(errorSpy.isEmpty());
233
234 duration += buffer.duration();
235 sampleCount += buffer.sampleCount();
236 byteCount += buffer.byteCount();
237
238 // Now drain the decoder
239 if (duration < 998000) {
240 QTRY_COMPARE(d.bufferAvailable(), true);
241 }
242
243 while (d.bufferAvailable()) {
244 buffer = d.read();
245 QVERIFY(buffer.isValid());
246 QTRY_VERIFY(!positionSpy.isEmpty());
247 QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
248 QVERIFY(d.position() - (duration / 1000) < 20);
249
250 duration += buffer.duration();
251 sampleCount += buffer.sampleCount();
252 byteCount += buffer.byteCount();
253
254 if (duration < 998000) {
255 QTRY_COMPARE(d.bufferAvailable(), true);
256 }
257 }
258
259 // Resampling might end up with fewer or more samples
260 // so be a bit sloppy
261 QVERIFY(qAbs(sampleCount - 22047) < 100);
262 QVERIFY(qAbs(byteCount - 22047) < 100);
263 QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
264 QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
265 QTRY_COMPARE(finishedSpy.count(), 1);
266 QVERIFY(!d.bufferAvailable());
267 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
268
269 d.stop();
270 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
271 QTRY_COMPARE(durationSpy.count(), 2);
272 QCOMPARE(d.duration(), qint64(-1));
273 QVERIFY(!d.bufferAvailable());
274}
275
276/*
277 The avi file has an audio stream not supported by any codec.
278*/
279void tst_QAudioDecoderBackend::unsupportedFileTest()
280{
281 QAudioDecoder d;
282 if (d.error() == QAudioDecoder::ServiceMissingError)
283 QSKIP("There is no audio decoding support on this platform.");
284 QAudioBuffer buffer;
285
286 QVERIFY(d.state() == QAudioDecoder::StoppedState);
287 QVERIFY(d.bufferAvailable() == false);
288 QCOMPARE(d.sourceFilename(), QString(""));
289 QVERIFY(d.audioFormat() == QAudioFormat());
290
291 // Test local file
292 QFileInfo fileInfo(QFINDTESTDATA(TEST_UNSUPPORTED_FILE_NAME));
293 d.setSourceFilename(fileInfo.absoluteFilePath());
294 QVERIFY(d.state() == QAudioDecoder::StoppedState);
295 QVERIFY(!d.bufferAvailable());
296 QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
297
298 QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
299 QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
300 QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
301 QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
302 QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
303 QSignalSpy finishedSpy(&d, SIGNAL(finished()));
304 QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
305
306 d.start();
307 QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
308 QVERIFY(!d.bufferAvailable());
309 QCOMPARE(d.audioFormat(), QAudioFormat());
310 QCOMPARE(d.duration(), qint64(-1));
311 QCOMPARE(d.position(), qint64(-1));
312
313 // Check the error code.
314 QTRY_VERIFY(!errorSpy.isEmpty());
315
316 // Have to use qvariant_cast, toInt will return 0 because unrecognized type;
317 QAudioDecoder::Error errorCode = qvariant_cast<QAudioDecoder::Error>(v: errorSpy.takeLast().at(i: 0));
318 QCOMPARE(errorCode, QAudioDecoder::FormatError);
319 QCOMPARE(d.error(), QAudioDecoder::FormatError);
320
321 // Check all other spies.
322 QVERIFY(readySpy.isEmpty());
323 QVERIFY(bufferChangedSpy.isEmpty());
324 QVERIFY(stateSpy.isEmpty());
325 QVERIFY(finishedSpy.isEmpty());
326 QVERIFY(positionSpy.isEmpty());
327 QVERIFY(durationSpy.isEmpty());
328
329 errorSpy.clear();
330
331 // Try read even if the file is not supported to test robustness.
332 buffer = d.read();
333 QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
334 QVERIFY(!buffer.isValid());
335 QVERIFY(!d.bufferAvailable());
336 QCOMPARE(d.position(), qint64(-1));
337
338 QVERIFY(errorSpy.isEmpty());
339 QVERIFY(readySpy.isEmpty());
340 QVERIFY(bufferChangedSpy.isEmpty());
341 QVERIFY(stateSpy.isEmpty());
342 QVERIFY(finishedSpy.isEmpty());
343 QVERIFY(positionSpy.isEmpty());
344 QVERIFY(durationSpy.isEmpty());
345
346
347 d.stop();
348 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
349 QCOMPARE(d.duration(), qint64(-1));
350 QVERIFY(!d.bufferAvailable());
351}
352
353/*
354 The corrupted file is generated by copying a few random numbers
355 from /dev/random on a linux machine.
356*/
357void tst_QAudioDecoderBackend::corruptedFileTest()
358{
359 QAudioDecoder d;
360 if (d.error() == QAudioDecoder::ServiceMissingError)
361 QSKIP("There is no audio decoding support on this platform.");
362 QAudioBuffer buffer;
363
364 QVERIFY(d.state() == QAudioDecoder::StoppedState);
365 QVERIFY(d.bufferAvailable() == false);
366 QCOMPARE(d.sourceFilename(), QString(""));
367 QVERIFY(d.audioFormat() == QAudioFormat());
368
369 // Test local file
370 QFileInfo fileInfo(QFINDTESTDATA(TEST_CORRUPTED_FILE_NAME));
371 d.setSourceFilename(fileInfo.absoluteFilePath());
372 QVERIFY(d.state() == QAudioDecoder::StoppedState);
373 QVERIFY(!d.bufferAvailable());
374 QCOMPARE(d.sourceFilename(), fileInfo.absoluteFilePath());
375
376 QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
377 QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
378 QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
379 QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
380 QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
381 QSignalSpy finishedSpy(&d, SIGNAL(finished()));
382 QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
383
384 d.start();
385 QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
386 QVERIFY(!d.bufferAvailable());
387 QCOMPARE(d.audioFormat(), QAudioFormat());
388 QCOMPARE(d.duration(), qint64(-1));
389 QCOMPARE(d.position(), qint64(-1));
390
391 // Check the error code.
392 QTRY_VERIFY(!errorSpy.isEmpty());
393
394 // Have to use qvariant_cast, toInt will return 0 because unrecognized type;
395 QAudioDecoder::Error errorCode = qvariant_cast<QAudioDecoder::Error>(v: errorSpy.takeLast().at(i: 0));
396 QCOMPARE(errorCode, QAudioDecoder::FormatError);
397 QCOMPARE(d.error(), QAudioDecoder::FormatError);
398
399 // Check all other spies.
400 QVERIFY(readySpy.isEmpty());
401 QVERIFY(bufferChangedSpy.isEmpty());
402 QVERIFY(stateSpy.isEmpty());
403 QVERIFY(finishedSpy.isEmpty());
404 QVERIFY(positionSpy.isEmpty());
405 QVERIFY(durationSpy.isEmpty());
406
407 errorSpy.clear();
408
409 // Try read even if the file is corrupted to test the robustness.
410 buffer = d.read();
411 QTRY_VERIFY(d.state() == QAudioDecoder::StoppedState);
412 QVERIFY(!buffer.isValid());
413 QVERIFY(!d.bufferAvailable());
414 QCOMPARE(d.position(), qint64(-1));
415
416 QVERIFY(errorSpy.isEmpty());
417 QVERIFY(readySpy.isEmpty());
418 QVERIFY(bufferChangedSpy.isEmpty());
419 QVERIFY(stateSpy.isEmpty());
420 QVERIFY(finishedSpy.isEmpty());
421 QVERIFY(positionSpy.isEmpty());
422 QVERIFY(durationSpy.isEmpty());
423
424
425 d.stop();
426 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
427 QCOMPARE(d.duration(), qint64(-1));
428 QVERIFY(!d.bufferAvailable());
429}
430
431void tst_QAudioDecoderBackend::deviceTest()
432{
433 if (!isWavSupported())
434 QSKIP("Sound format is not supported");
435
436 QAudioDecoder d;
437 if (d.error() == QAudioDecoder::ServiceMissingError)
438 QSKIP("There is no audio decoding support on this platform.");
439 QAudioBuffer buffer;
440 quint64 duration = 0;
441 int sampleCount = 0;
442
443 QSignalSpy readySpy(&d, SIGNAL(bufferReady()));
444 QSignalSpy bufferChangedSpy(&d, SIGNAL(bufferAvailableChanged(bool)));
445 QSignalSpy errorSpy(&d, SIGNAL(error(QAudioDecoder::Error)));
446 QSignalSpy stateSpy(&d, SIGNAL(stateChanged(QAudioDecoder::State)));
447 QSignalSpy durationSpy(&d, SIGNAL(durationChanged(qint64)));
448 QSignalSpy finishedSpy(&d, SIGNAL(finished()));
449 QSignalSpy positionSpy(&d, SIGNAL(positionChanged(qint64)));
450
451 QVERIFY(d.state() == QAudioDecoder::StoppedState);
452 QVERIFY(d.bufferAvailable() == false);
453 QCOMPARE(d.sourceFilename(), QString(""));
454 QVERIFY(d.audioFormat() == QAudioFormat());
455
456 QFileInfo fileInfo(QFINDTESTDATA(TEST_FILE_NAME));
457 QFile file(fileInfo.absoluteFilePath());
458 QVERIFY(file.open(QIODevice::ReadOnly));
459 d.setSourceDevice(&file);
460
461 QVERIFY(d.sourceDevice() == &file);
462 QVERIFY(d.sourceFilename().isEmpty());
463
464 // We haven't set the format yet
465 QVERIFY(d.audioFormat() == QAudioFormat());
466
467 d.start();
468 QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
469 QTRY_VERIFY(!stateSpy.isEmpty());
470 QTRY_VERIFY(!readySpy.isEmpty());
471 QTRY_VERIFY(!bufferChangedSpy.isEmpty());
472 QVERIFY(d.bufferAvailable());
473 QTRY_VERIFY(!durationSpy.isEmpty());
474 QVERIFY(qAbs(d.duration() - 1000) < 20);
475
476 buffer = d.read();
477 QVERIFY(buffer.isValid());
478
479 // Test file is 44.1K 16bit mono
480 QCOMPARE(buffer.format().channelCount(), 1);
481 QCOMPARE(buffer.format().sampleRate(), 44100);
482 QCOMPARE(buffer.format().sampleSize(), 16);
483 QCOMPARE(buffer.format().sampleType(), QAudioFormat::SignedInt);
484 QCOMPARE(buffer.format().codec(), QString("audio/pcm"));
485
486 QVERIFY(errorSpy.isEmpty());
487
488 duration += buffer.duration();
489 sampleCount += buffer.sampleCount();
490
491 // Now drain the decoder
492 if (sampleCount < 44094) {
493 QTRY_COMPARE(d.bufferAvailable(), true);
494 }
495
496 while (d.bufferAvailable()) {
497 buffer = d.read();
498 QVERIFY(buffer.isValid());
499 QTRY_VERIFY(!positionSpy.isEmpty());
500 QVERIFY(positionSpy.takeLast().at(0).toLongLong() == qint64(duration / 1000));
501 QVERIFY(d.position() - (duration / 1000) < 20);
502
503 duration += buffer.duration();
504 sampleCount += buffer.sampleCount();
505 if (sampleCount < 44094) {
506 QTRY_COMPARE(d.bufferAvailable(), true);
507 }
508 }
509
510 // Make sure the duration is roughly correct (+/- 20ms)
511 QCOMPARE(sampleCount, 44094);
512 QVERIFY(qAbs(qint64(duration) - 1000000) < 20000);
513 QVERIFY(qAbs((d.position() + (buffer.duration() / 1000)) - 1000) < 20);
514 QTRY_COMPARE(finishedSpy.count(), 1);
515 QVERIFY(!d.bufferAvailable());
516 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
517
518 d.stop();
519 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
520 QVERIFY(!d.bufferAvailable());
521 QTRY_COMPARE(durationSpy.count(), 2);
522 QCOMPARE(d.duration(), qint64(-1));
523 readySpy.clear();
524 bufferChangedSpy.clear();
525 stateSpy.clear();
526 durationSpy.clear();
527 finishedSpy.clear();
528 positionSpy.clear();
529
530 // Now try changing formats
531 QAudioFormat format;
532 format.setChannelCount(2);
533 format.setSampleSize(8);
534 format.setSampleRate(8000);
535 format.setCodec("audio/pcm");
536 format.setSampleType(QAudioFormat::SignedInt);
537
538 d.setAudioFormat(format);
539
540 // Make sure it stuck
541 QVERIFY(d.audioFormat() == format);
542
543 d.start();
544 QTRY_VERIFY(d.state() == QAudioDecoder::DecodingState);
545 QTRY_VERIFY(!stateSpy.isEmpty());
546 QTRY_VERIFY(!readySpy.isEmpty());
547 QTRY_VERIFY(!bufferChangedSpy.isEmpty());
548 QVERIFY(d.bufferAvailable());
549 QTRY_VERIFY(!durationSpy.isEmpty());
550 QVERIFY(qAbs(d.duration() - 1000) < 20);
551
552 buffer = d.read();
553 QVERIFY(buffer.isValid());
554 // See if we got the right format
555 QVERIFY(buffer.format() == format);
556
557 // The decoder should still have the same format
558 QVERIFY(d.audioFormat() == format);
559
560 QVERIFY(errorSpy.isEmpty());
561
562 d.stop();
563 QTRY_COMPARE(d.state(), QAudioDecoder::StoppedState);
564 QVERIFY(!d.bufferAvailable());
565 QTRY_COMPARE(durationSpy.count(), 2);
566 QCOMPARE(d.duration(), qint64(-1));
567}
568
569QTEST_MAIN(tst_QAudioDecoderBackend)
570
571#include "tst_qaudiodecoderbackend.moc"
572

source code of qtmultimedia/tests/auto/integration/qaudiodecoderbackend/tst_qaudiodecoderbackend.cpp