annotate data/fileio/test/AudioFileReaderTest.h @ 1310:aa1b1fc2d018 mp3-gapless

Stop reporting sync errors only when we really are at eof, i.e. after the input callback has been called again (previously we just tested whether we'd buffered up all the input, which of course we do in one go at the start)
author Chris Cannam
date Tue, 29 Nov 2016 16:45:29 +0000
parents 2e7fcdd5f627
children ff9697592bef
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) {
Chris@1309 123 maxLimit = 0.02;
Chris@1309 124 meanLimit = 0.02;
cannam@1308 125 } else if (extension == "ogg" || extension == "mp3") {
Chris@1309 126 maxLimit = 0.1;
Chris@1309 127 meanLimit = 0.035;
cannam@1308 128 edgeLimit = maxLimit * 3;
cannam@1308 129 } else if (extension == "aac" || extension == "m4a") {
Chris@1309 130 maxLimit = 0.3; // seems max diff can be quite large here
cannam@1308 131 // even when mean is fairly small
Chris@1309 132 meanLimit = 0.01;
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
Chris@1309 201 for (sv_frame_t i = refFrames; i + offset < read; ++i) {
Chris@1309 202 sv_frame_t ix = i + offset;
Chris@1309 203 float quiet = 1e-6f;
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