annotate data/fileio/test/AudioFileReaderTest.h @ 1308:80c77916fe85 mp3-gapless

Update m4a files to exports from CoreAudio, rather than FAAC; update tests accordingly, and add test for spurious data after end of decode
author Chris Cannam <cannam@all-day-breakfast.com>
date Tue, 29 Nov 2016 14:25:57 +0000
parents fc9cef5e988d
children 2e7fcdd5f627
rev   line source
Chris@756 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@756 2
Chris@756 3 /*
Chris@756 4 Sonic Visualiser
Chris@756 5 An audio file viewer and annotation editor.
Chris@756 6 Centre for Digital Music, Queen Mary, University of London.
Chris@756 7 This file copyright 2013 Chris Cannam.
Chris@756 8
Chris@756 9 This program is free software; you can redistribute it and/or
Chris@756 10 modify it under the terms of the GNU General Public License as
Chris@756 11 published by the Free Software Foundation; either version 2 of the
Chris@756 12 License, or (at your option) any later version. See the file
Chris@756 13 COPYING included with this distribution for more information.
Chris@756 14 */
Chris@756 15
Chris@756 16 #ifndef TEST_AUDIO_FILE_READER_H
Chris@756 17 #define TEST_AUDIO_FILE_READER_H
Chris@756 18
Chris@756 19 #include "../AudioFileReaderFactory.h"
Chris@756 20 #include "../AudioFileReader.h"
Chris@756 21
Chris@756 22 #include "AudioTestData.h"
Chris@756 23
Chris@756 24 #include <cmath>
Chris@756 25
Chris@756 26 #include <QObject>
Chris@756 27 #include <QtTest>
Chris@756 28 #include <QDir>
Chris@756 29
Chris@756 30 #include <iostream>
Chris@756 31
Chris@756 32 using namespace std;
Chris@756 33
Chris@1263 34 static QString audioDir = "svcore/data/fileio/test/testfiles";
Chris@756 35
Chris@756 36 class AudioFileReaderTest : public QObject
Chris@756 37 {
Chris@756 38 Q_OBJECT
Chris@756 39
Chris@756 40 const char *strOf(QString s) {
Chris@756 41 return strdup(s.toLocal8Bit().data());
Chris@756 42 }
Chris@756 43
Chris@756 44 private slots:
Chris@756 45 void init()
Chris@756 46 {
Chris@756 47 if (!QDir(audioDir).exists()) {
Chris@756 48 cerr << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist" << endl;
Chris@756 49 QVERIFY2(QDir(audioDir).exists(), "Audio test file directory not found");
Chris@756 50 }
Chris@756 51 }
Chris@756 52
Chris@756 53 void read_data()
Chris@756 54 {
Chris@756 55 QTest::addColumn<QString>("audiofile");
Chris@756 56 QStringList files = QDir(audioDir).entryList(QDir::Files);
Chris@756 57 foreach (QString filename, files) {
Chris@756 58 QTest::newRow(strOf(filename)) << filename;
Chris@756 59 }
Chris@756 60 }
Chris@756 61
Chris@756 62 void read()
Chris@756 63 {
Chris@756 64 QFETCH(QString, audiofile);
Chris@756 65
Chris@1040 66 sv_samplerate_t readRate = 48000;
Chris@757 67
Chris@756 68 AudioFileReader *reader =
Chris@756 69 AudioFileReaderFactory::createReader
Chris@757 70 (audioDir + "/" + audiofile, readRate);
Chris@757 71
Chris@757 72 QStringList fileAndExt = audiofile.split(".");
Chris@757 73 QStringList bits = fileAndExt[0].split("-");
Chris@757 74 QString extension = fileAndExt[1];
Chris@1040 75 sv_samplerate_t nominalRate = bits[0].toInt();
Chris@757 76 int nominalChannels = bits[1].toInt();
Chris@757 77 int nominalDepth = 16;
Chris@757 78 if (bits.length() > 2) nominalDepth = bits[2].toInt();
Chris@756 79
Chris@756 80 if (!reader) {
Chris@820 81 #if ( QT_VERSION >= 0x050000 )
Chris@763 82 QSKIP("Unsupported file, skipping");
Chris@820 83 #else
Chris@820 84 QSKIP("Unsupported file, skipping", SkipSingle);
Chris@820 85 #endif
Chris@756 86 }
Chris@756 87
Chris@757 88 QCOMPARE((int)reader->getChannelCount(), nominalChannels);
Chris@1040 89 QCOMPARE(reader->getNativeRate(), nominalRate);
Chris@1040 90 QCOMPARE(reader->getSampleRate(), readRate);
Chris@757 91
Chris@756 92 int channels = reader->getChannelCount();
Chris@757 93 AudioTestData tdata(readRate, channels);
Chris@756 94
Chris@756 95 float *reference = tdata.getInterleavedData();
Chris@1040 96 sv_frame_t refFrames = tdata.getFrameCount();
Chris@756 97
Chris@756 98 // The reader should give us exactly the expected number of
Chris@759 99 // frames, except for mp3/aac files. We ask for quite a lot
Chris@759 100 // more, though, so we can (a) check that we only get the
Chris@759 101 // expected number back (if this is not mp3/aac) or (b) take
Chris@759 102 // into account silence at beginning and end (if it is).
Chris@1041 103 vector<float> test = reader->getInterleavedFrames(0, refFrames + 5000);
Chris@1040 104 sv_frame_t read = test.size() / channels;
Chris@756 105
Chris@759 106 if (extension == "mp3" || extension == "aac" || extension == "m4a") {
Chris@759 107 // mp3s and aacs can have silence at start and end
Chris@759 108 QVERIFY(read >= refFrames);
Chris@757 109 } else {
Chris@759 110 QCOMPARE(read, refFrames);
Chris@757 111 }
Chris@757 112
Chris@757 113 // Our limits are pretty relaxed -- we're not testing decoder
Chris@757 114 // or resampler quality here, just whether the results are
cannam@1308 115 // plainly wrong (e.g. at wrong samplerate or with an offset).
Chris@757 116
cannam@1308 117 double maxLimit = 0.01;
cannam@1308 118 double meanLimit = 0.001;
cannam@1308 119 double edgeLimit = maxLimit * 10; // in first or final edgeSize frames
Chris@759 120 int edgeSize = 100;
Chris@759 121
Chris@757 122 if (nominalDepth < 16) {
cannam@1308 123 maxLimit *= 2;
cannam@1308 124 meanLimit *= 20;
cannam@1308 125 } else if (extension == "ogg" || extension == "mp3") {
cannam@1308 126 maxLimit *= 10;
cannam@1308 127 meanLimit *= 10;
cannam@1308 128 edgeLimit = maxLimit * 3;
cannam@1308 129 } else if (extension == "aac" || extension == "m4a") {
cannam@1308 130 maxLimit *= 30; // seems max diff can be quite large here
cannam@1308 131 // even when mean is fairly small
cannam@1308 132 meanLimit *= 10;
cannam@1308 133 edgeLimit = maxLimit * 3;
Chris@757 134 }
Chris@757 135
Chris@759 136 // And we ignore completely the last few frames when upsampling
Chris@1040 137 int discard = 1 + int(round(readRate / nominalRate));
Chris@759 138
Chris@759 139 int offset = 0;
Chris@759 140
Chris@759 141 if (extension == "aac" || extension == "m4a") {
Chris@759 142 // our m4a file appears to have a fixed offset of 1024 (at
Chris@759 143 // file sample rate)
cannam@1308 144 // offset = int(round((1024 / nominalRate) * readRate));
cannam@1308 145 offset = 0;
Chris@759 146 }
Chris@759 147
Chris@759 148 if (extension == "mp3") {
Chris@1296 149 // ...while mp3s appear to vary. What we're looking for is
Chris@1296 150 // the first peak of the sinusoid in the first channel
Chris@1296 151 // (since we may have only the one channel). This should
Chris@1296 152 // appear at 0.4ms (see AudioTestData.h)
Chris@1296 153 int expectedPeak = int(0.0004 * readRate);
Chris@1296 154 // std::cerr << "expectedPeak = " << expectedPeak << std::endl;
Chris@1296 155 for (int i = 1; i < read; ++i) {
Chris@1296 156 if (test[i * channels] > 0.8 &&
Chris@1296 157 test[(i+1) * channels] < test[i * channels]) {
Chris@1296 158 offset = i - expectedPeak - 1;
Chris@1296 159 // std::cerr << "actual peak = " << i-1 << std::endl;
Chris@759 160 break;
Chris@759 161 }
Chris@759 162 }
Chris@759 163 // std::cerr << "offset = " << offset << std::endl;
Chris@759 164 }
Chris@756 165
Chris@756 166 for (int c = 0; c < channels; ++c) {
cannam@1308 167
Chris@756 168 float maxdiff = 0.f;
Chris@756 169 int maxAt = 0;
Chris@756 170 float totdiff = 0.f;
cannam@1308 171
Chris@1296 172 for (int i = 0; i < refFrames; ++i) {
Chris@1296 173 int ix = i + offset;
Chris@1296 174 if (ix >= read) {
cannam@1308 175 cerr << "ERROR: audiofile " << audiofile << " reads truncated (read-rate reference frames " << i << " onward, of " << refFrames << ", are lost)" << endl;
Chris@1296 176 QVERIFY(ix < read);
Chris@1296 177 }
Chris@1296 178 if (ix + discard >= read) {
Chris@1296 179 // we forgive the very edge samples when
Chris@1296 180 // resampling (discard > 0)
Chris@1296 181 continue;
Chris@1296 182 }
cannam@1308 183 float diff = fabsf(test[ix * channels + c] -
Chris@756 184 reference[i * channels + c]);
Chris@756 185 totdiff += diff;
Chris@757 186 // in edge areas, record this only if it exceeds edgeLimit
Chris@759 187 if (i < edgeSize || i + edgeSize >= read - offset) {
Chris@968 188 if (diff > edgeLimit && diff > maxdiff) {
Chris@757 189 maxdiff = diff;
Chris@757 190 maxAt = i;
Chris@757 191 }
Chris@757 192 } else {
Chris@757 193 if (diff > maxdiff) {
Chris@757 194 maxdiff = diff;
Chris@757 195 maxAt = i;
Chris@757 196 }
Chris@756 197 }
Chris@756 198 }
cannam@1308 199
cannam@1308 200 // check for spurious material at end
cannam@1308 201 for (int i = refFrames; i + offset < read; ++i) {
cannam@1308 202 int ix = i + offset;
cannam@1308 203 float quiet = 1e-6;
cannam@1308 204 float mag = fabsf(test[ix * channels + c]);
cannam@1308 205 if (mag > quiet) {
cannam@1308 206 cerr << "ERROR: audiofile " << audiofile << " contains spurious data after end of reference (found sample " << test[ix * channels + c] << " at index " << ix << " of channel " << c << ")" << endl;
cannam@1308 207 QVERIFY(mag < quiet);
cannam@1308 208 }
cannam@1308 209 }
cannam@1308 210
Chris@1040 211 float meandiff = totdiff / float(read);
Chris@756 212 // cerr << "meandiff on channel " << c << ": " << meandiff << endl;
Chris@756 213 // cerr << "maxdiff on channel " << c << ": " << maxdiff << " at " << maxAt << endl;
cannam@1308 214 if (meandiff >= meanLimit) {
Chris@759 215 cerr << "ERROR: for audiofile " << audiofile << ": mean diff = " << meandiff << " for channel " << c << endl;
cannam@1308 216 QVERIFY(meandiff < meanLimit);
Chris@759 217 }
cannam@1308 218 if (maxdiff >= maxLimit) {
Chris@759 219 cerr << "ERROR: for audiofile " << audiofile << ": max diff = " << maxdiff << " at frame " << maxAt << " of " << read << " on channel " << c << " (mean diff = " << meandiff << ")" << endl;
cannam@1308 220 QVERIFY(maxdiff < maxLimit);
Chris@756 221 }
Chris@756 222 }
Chris@756 223 }
Chris@756 224 };
Chris@756 225
Chris@756 226 #endif