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@1313
|
21 #include "../WavFileWriter.h"
|
Chris@756
|
22
|
Chris@756
|
23 #include "AudioTestData.h"
|
Chris@1698
|
24 #include "UnsupportedFormat.h"
|
Chris@756
|
25
|
Chris@756
|
26 #include <cmath>
|
Chris@756
|
27
|
Chris@756
|
28 #include <QObject>
|
Chris@756
|
29 #include <QtTest>
|
Chris@756
|
30 #include <QDir>
|
Chris@756
|
31
|
Chris@756
|
32 #include <iostream>
|
Chris@756
|
33
|
Chris@756
|
34 using namespace std;
|
Chris@756
|
35
|
Chris@756
|
36 class AudioFileReaderTest : public QObject
|
Chris@756
|
37 {
|
Chris@756
|
38 Q_OBJECT
|
Chris@756
|
39
|
Chris@1346
|
40 private:
|
Chris@1346
|
41 QString testDirBase;
|
Chris@1346
|
42 QString audioDir;
|
Chris@1346
|
43 QString diffDir;
|
Chris@1346
|
44
|
Chris@1346
|
45 public:
|
Chris@1346
|
46 AudioFileReaderTest(QString base) {
|
Chris@1346
|
47 if (base == "") {
|
Chris@1346
|
48 base = "svcore/data/fileio/test";
|
Chris@1346
|
49 }
|
Chris@1346
|
50 testDirBase = base;
|
Chris@1359
|
51 audioDir = base + "/audio";
|
Chris@1346
|
52 diffDir = base + "/diffs";
|
Chris@1346
|
53 }
|
Chris@1346
|
54
|
Chris@1346
|
55 private:
|
Chris@756
|
56 const char *strOf(QString s) {
|
Chris@756
|
57 return strdup(s.toLocal8Bit().data());
|
Chris@756
|
58 }
|
Chris@756
|
59
|
Chris@1313
|
60 void getFileMetadata(QString filename,
|
Chris@1313
|
61 QString &extension,
|
Chris@1313
|
62 sv_samplerate_t &rate,
|
Chris@1313
|
63 int &channels,
|
Chris@1313
|
64 int &bitdepth) {
|
Chris@1313
|
65
|
Chris@1313
|
66 QStringList fileAndExt = filename.split(".");
|
Chris@1313
|
67 QStringList bits = fileAndExt[0].split("-");
|
Chris@1313
|
68
|
Chris@1313
|
69 extension = fileAndExt[1];
|
Chris@1313
|
70 rate = bits[0].toInt();
|
Chris@1313
|
71 channels = bits[1].toInt();
|
Chris@1313
|
72 bitdepth = 16;
|
Chris@1313
|
73 if (bits.length() > 2) {
|
Chris@1313
|
74 bitdepth = bits[2].toInt();
|
Chris@1313
|
75 }
|
Chris@1313
|
76 }
|
Chris@1698
|
77
|
cannam@1315
|
78 void getExpectedThresholds(QString format,
|
cannam@1315
|
79 QString filename,
|
Chris@1313
|
80 bool resampled,
|
Chris@1313
|
81 bool gapless,
|
Chris@1313
|
82 bool normalised,
|
Chris@1313
|
83 double &maxLimit,
|
Chris@1313
|
84 double &rmsLimit) {
|
Chris@1313
|
85
|
Chris@1313
|
86 QString extension;
|
Chris@1313
|
87 sv_samplerate_t fileRate;
|
Chris@1313
|
88 int channels;
|
Chris@1313
|
89 int bitdepth;
|
Chris@1313
|
90 getFileMetadata(filename, extension, fileRate, channels, bitdepth);
|
Chris@1313
|
91
|
Chris@1313
|
92 if (normalised) {
|
Chris@1313
|
93
|
cannam@1315
|
94 if (format == "ogg") {
|
Chris@1313
|
95
|
Chris@1313
|
96 // Our ogg is not especially high quality and is
|
Chris@1313
|
97 // actually further from the original if normalised
|
Chris@1313
|
98
|
Chris@1313
|
99 maxLimit = 0.1;
|
Chris@1313
|
100 rmsLimit = 0.03;
|
Chris@1313
|
101
|
Chris@1598
|
102 } else if (format == "opus") {
|
Chris@1598
|
103
|
Chris@1598
|
104 maxLimit = 0.06;
|
Chris@1598
|
105 rmsLimit = 0.015;
|
Chris@1598
|
106
|
cannam@1315
|
107 } else if (format == "aac") {
|
Chris@1313
|
108
|
cannam@1315
|
109 // Terrible performance for this test, load of spill
|
cannam@1315
|
110 // from one channel to the other. I guess they know
|
cannam@1315
|
111 // what they're doing, it's perceptual after all, but
|
cannam@1315
|
112 // it does make this check a bit superfluous, you
|
cannam@1315
|
113 // could probably pass it with a signal that sounds
|
cannam@1315
|
114 // nothing like the original
|
cannam@1315
|
115 maxLimit = 0.2;
|
cannam@1314
|
116 rmsLimit = 0.1;
|
Chris@1313
|
117
|
Chris@1603
|
118 } else if (format == "wma") {
|
Chris@1603
|
119
|
Chris@1603
|
120 maxLimit = 0.05;
|
Chris@1603
|
121 rmsLimit = 0.01;
|
Chris@1603
|
122
|
cannam@1315
|
123 } else if (format == "mp3") {
|
Chris@1313
|
124
|
Chris@1313
|
125 if (resampled && !gapless) {
|
Chris@1313
|
126
|
Chris@1313
|
127 // We expect worse figures here, because the
|
Chris@1313
|
128 // combination of uncompensated encoder delay +
|
Chris@1313
|
129 // resampling results in a fractional delay which
|
Chris@1313
|
130 // means the decoded signal is slightly out of
|
Chris@1313
|
131 // phase compared to the test signal
|
Chris@1313
|
132
|
Chris@1313
|
133 maxLimit = 0.1;
|
Chris@1313
|
134 rmsLimit = 0.05;
|
Chris@1313
|
135
|
Chris@1313
|
136 } else {
|
Chris@1313
|
137
|
Chris@1313
|
138 maxLimit = 0.05;
|
Chris@1313
|
139 rmsLimit = 0.01;
|
Chris@1313
|
140 }
|
Chris@1313
|
141
|
Chris@1313
|
142 } else {
|
Chris@1313
|
143
|
cannam@1315
|
144 // lossless formats (wav, aiff, flac, apple_lossless)
|
Chris@1313
|
145
|
Chris@1313
|
146 if (bitdepth >= 16 && !resampled) {
|
Chris@1313
|
147 maxLimit = 1e-3;
|
Chris@1313
|
148 rmsLimit = 3e-4;
|
Chris@1313
|
149 } else {
|
Chris@1313
|
150 maxLimit = 0.01;
|
Chris@1313
|
151 rmsLimit = 5e-3;
|
Chris@1313
|
152 }
|
Chris@1313
|
153 }
|
Chris@1313
|
154
|
Chris@1313
|
155 } else { // !normalised
|
Chris@1313
|
156
|
cannam@1315
|
157 if (format == "ogg") {
|
Chris@1313
|
158
|
Chris@1313
|
159 maxLimit = 0.06;
|
Chris@1313
|
160 rmsLimit = 0.03;
|
Chris@1313
|
161
|
Chris@1598
|
162 } else if (format == "opus") {
|
Chris@1598
|
163
|
Chris@1598
|
164 maxLimit = 0.06;
|
Chris@1598
|
165 rmsLimit = 0.015;
|
Chris@1598
|
166
|
cannam@1315
|
167 } else if (format == "aac") {
|
Chris@1313
|
168
|
Chris@1603
|
169 maxLimit = 0.2;
|
cannam@1315
|
170 rmsLimit = 0.1;
|
Chris@1313
|
171
|
Chris@1603
|
172 } else if (format == "wma") {
|
Chris@1603
|
173
|
Chris@1603
|
174 maxLimit = 0.05;
|
Chris@1603
|
175 rmsLimit = 0.01;
|
Chris@1603
|
176
|
cannam@1315
|
177 } else if (format == "mp3") {
|
Chris@1313
|
178
|
Chris@1313
|
179 // all mp3 figures are worse when not normalising
|
Chris@1313
|
180 maxLimit = 0.1;
|
Chris@1313
|
181 rmsLimit = 0.05;
|
Chris@1313
|
182
|
Chris@1313
|
183 } else {
|
Chris@1313
|
184
|
cannam@1315
|
185 // lossless formats (wav, aiff, flac, apple_lossless)
|
Chris@1313
|
186
|
Chris@1313
|
187 if (bitdepth >= 16 && !resampled) {
|
Chris@1313
|
188 maxLimit = 1e-3;
|
Chris@1313
|
189 rmsLimit = 3e-4;
|
Chris@1313
|
190 } else {
|
Chris@1313
|
191 maxLimit = 0.02;
|
Chris@1313
|
192 rmsLimit = 0.01;
|
Chris@1313
|
193 }
|
Chris@1313
|
194 }
|
Chris@1313
|
195 }
|
Chris@1313
|
196 }
|
Chris@1313
|
197
|
cannam@1315
|
198 QString testName(QString format, QString filename, int rate, bool norm, bool gapless) {
|
cannam@1315
|
199 return QString("%1/%2 at %3%4%5")
|
cannam@1315
|
200 .arg(format)
|
Chris@1313
|
201 .arg(filename)
|
Chris@1313
|
202 .arg(rate)
|
Chris@1313
|
203 .arg(norm ? " normalised": "")
|
Chris@1313
|
204 .arg(gapless ? "" : " non-gapless");
|
Chris@1313
|
205 }
|
Chris@1313
|
206
|
Chris@756
|
207 private slots:
|
Chris@756
|
208 void init()
|
Chris@756
|
209 {
|
Chris@756
|
210 if (!QDir(audioDir).exists()) {
|
Chris@1346
|
211 QString cwd = QDir::currentPath();
|
Chris@1428
|
212 SVCERR << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist (cwd = " << cwd << ")" << endl;
|
Chris@756
|
213 QVERIFY2(QDir(audioDir).exists(), "Audio test file directory not found");
|
Chris@756
|
214 }
|
Chris@1313
|
215 if (!QDir(diffDir).exists() && !QDir().mkpath(diffDir)) {
|
Chris@1428
|
216 SVCERR << "ERROR: Audio diff directory \"" << diffDir << "\" does not exist and could not be created" << endl;
|
Chris@1313
|
217 QVERIFY2(QDir(diffDir).exists(), "Audio diff directory not found and could not be created");
|
Chris@1313
|
218 }
|
Chris@756
|
219 }
|
Chris@756
|
220
|
Chris@756
|
221 void read_data()
|
Chris@756
|
222 {
|
cannam@1315
|
223 QTest::addColumn<QString>("format");
|
Chris@756
|
224 QTest::addColumn<QString>("audiofile");
|
Chris@1313
|
225 QTest::addColumn<int>("rate");
|
Chris@1313
|
226 QTest::addColumn<bool>("normalised");
|
Chris@1313
|
227 QTest::addColumn<bool>("gapless");
|
cannam@1315
|
228 QStringList dirs = QDir(audioDir).entryList(QDir::Dirs |
|
cannam@1315
|
229 QDir::NoDotAndDotDot);
|
cannam@1315
|
230 for (QString format: dirs) {
|
cannam@1315
|
231 QStringList files = QDir(QDir(audioDir).filePath(format))
|
cannam@1315
|
232 .entryList(QDir::Files);
|
cannam@1315
|
233 int readRates[] = { 44100, 48000 };
|
cannam@1315
|
234 bool norms[] = { false, true };
|
cannam@1315
|
235 bool gaplesses[] = { true, false };
|
cannam@1315
|
236 foreach (QString filename, files) {
|
cannam@1315
|
237 for (int rate: readRates) {
|
cannam@1315
|
238 for (bool norm: norms) {
|
cannam@1315
|
239 for (bool gapless: gaplesses) {
|
Chris@1313
|
240
|
Chris@1603
|
241 #ifdef Q_OS_WIN
|
Chris@1603
|
242 if (format == "aac") {
|
Chris@1603
|
243 if (gapless) {
|
Chris@1603
|
244 // Apparently no support for AAC
|
Chris@1603
|
245 // encoder delay compensation in
|
Chris@1603
|
246 // MediaFoundation, so these tests
|
Chris@1603
|
247 // are only available non-gapless
|
Chris@1603
|
248 continue;
|
Chris@1603
|
249 }
|
Chris@1603
|
250 } else if (format != "mp3") {
|
Chris@1603
|
251 if (!gapless) {
|
Chris@1603
|
252 // All other formats but mp3 are
|
Chris@1603
|
253 // intrinsically gapless, so we
|
Chris@1603
|
254 // can skip the non-gapless option
|
Chris@1603
|
255 continue;
|
Chris@1603
|
256 }
|
cannam@1315
|
257 }
|
Chris@1603
|
258 #else
|
Chris@1603
|
259 if (format != "mp3") {
|
Chris@1603
|
260 if (!gapless) {
|
Chris@1603
|
261 // All other formats but mp3 are
|
Chris@1603
|
262 // intrinsically gapless
|
Chris@1603
|
263 // everywhere except for Windows
|
Chris@1603
|
264 // (see above), so we can skip the
|
Chris@1603
|
265 // non-gapless option
|
Chris@1603
|
266 continue;
|
Chris@1603
|
267 }
|
Chris@1603
|
268 }
|
Chris@1603
|
269 #endif
|
cannam@1315
|
270
|
cannam@1315
|
271 QString desc = testName
|
cannam@1315
|
272 (format, filename, rate, norm, gapless);
|
cannam@1315
|
273
|
cannam@1315
|
274 QTest::newRow(strOf(desc))
|
cannam@1315
|
275 << format << filename << rate << norm << gapless;
|
Chris@1313
|
276 }
|
Chris@1313
|
277 }
|
Chris@1313
|
278 }
|
Chris@1313
|
279 }
|
Chris@756
|
280 }
|
Chris@756
|
281 }
|
Chris@756
|
282
|
Chris@756
|
283 void read()
|
Chris@756
|
284 {
|
cannam@1315
|
285 QFETCH(QString, format);
|
Chris@756
|
286 QFETCH(QString, audiofile);
|
Chris@1313
|
287 QFETCH(int, rate);
|
Chris@1313
|
288 QFETCH(bool, normalised);
|
Chris@1313
|
289 QFETCH(bool, gapless);
|
Chris@756
|
290
|
Chris@1313
|
291 sv_samplerate_t readRate(rate);
|
Chris@1313
|
292
|
cannam@1315
|
293 // cerr << "\naudiofile = " << audiofile << endl;
|
Chris@1313
|
294
|
Chris@1313
|
295 AudioFileReaderFactory::Parameters params;
|
Chris@1313
|
296 params.targetRate = readRate;
|
Chris@1313
|
297 params.normalisation = (normalised ?
|
Chris@1313
|
298 AudioFileReaderFactory::Normalisation::Peak :
|
Chris@1313
|
299 AudioFileReaderFactory::Normalisation::None);
|
Chris@1313
|
300 params.gaplessMode = (gapless ?
|
Chris@1313
|
301 AudioFileReaderFactory::GaplessMode::Gapless :
|
Chris@1313
|
302 AudioFileReaderFactory::GaplessMode::Gappy);
|
Chris@757
|
303
|
Chris@1429
|
304 AudioFileReader *reader =
|
Chris@1429
|
305 AudioFileReaderFactory::createReader
|
Chris@1429
|
306 (audioDir + "/" + format + "/" + audiofile, params);
|
Chris@1313
|
307
|
Chris@1429
|
308 if (!reader) {
|
Chris@1698
|
309 if (isLegitimatelyUnsupported(format)) {
|
Chris@820
|
310 #if ( QT_VERSION >= 0x050000 )
|
Chris@1698
|
311 QSKIP("Unsupported file, skipping");
|
Chris@820
|
312 #else
|
Chris@1698
|
313 QSKIP("Unsupported file, skipping", SkipSingle);
|
Chris@820
|
314 #endif
|
Chris@1698
|
315 }
|
Chris@1429
|
316 }
|
Chris@756
|
317
|
cannam@1700
|
318 QVERIFY(reader != nullptr);
|
cannam@1700
|
319
|
Chris@1313
|
320 QString extension;
|
Chris@1313
|
321 sv_samplerate_t fileRate;
|
Chris@1313
|
322 int channels;
|
Chris@1313
|
323 int fileBitdepth;
|
Chris@1313
|
324 getFileMetadata(audiofile, extension, fileRate, channels, fileBitdepth);
|
Chris@1313
|
325
|
Chris@1313
|
326 QCOMPARE((int)reader->getChannelCount(), channels);
|
Chris@1313
|
327 QCOMPARE(reader->getNativeRate(), fileRate);
|
Chris@1040
|
328 QCOMPARE(reader->getSampleRate(), readRate);
|
Chris@757
|
329
|
Chris@1429
|
330 AudioTestData tdata(readRate, channels);
|
Chris@1429
|
331
|
Chris@1429
|
332 float *reference = tdata.getInterleavedData();
|
Chris@1040
|
333 sv_frame_t refFrames = tdata.getFrameCount();
|
Chris@1429
|
334
|
Chris@1429
|
335 // The reader should give us exactly the expected number of
|
Chris@1429
|
336 // frames, except for mp3/aac files. We ask for quite a lot
|
Chris@1429
|
337 // more, though, so we can (a) check that we only get the
|
Chris@1429
|
338 // expected number back (if this is not mp3/aac) or (b) take
|
Chris@1429
|
339 // into account silence at beginning and end (if it is).
|
Chris@1429
|
340 floatvec_t test = reader->getInterleavedFrames(0, refFrames + 5000);
|
Chris@1402
|
341
|
Chris@1402
|
342 delete reader;
|
Chris@1402
|
343 reader = 0;
|
Chris@1402
|
344
|
Chris@1429
|
345 sv_frame_t read = test.size() / channels;
|
Chris@756
|
346
|
Chris@1313
|
347 bool perceptual = (extension == "mp3" ||
|
Chris@1313
|
348 extension == "aac" ||
|
Chris@1598
|
349 extension == "m4a" ||
|
Chris@1603
|
350 extension == "wma" ||
|
Chris@1598
|
351 extension == "opus");
|
Chris@1313
|
352
|
Chris@1313
|
353 if (perceptual && !gapless) {
|
Chris@1313
|
354 // allow silence at start and end
|
Chris@759
|
355 QVERIFY(read >= refFrames);
|
Chris@757
|
356 } else {
|
Chris@759
|
357 QCOMPARE(read, refFrames);
|
Chris@757
|
358 }
|
Chris@757
|
359
|
Chris@1313
|
360 bool resampled = readRate != fileRate;
|
Chris@1313
|
361 double maxLimit, rmsLimit;
|
cannam@1315
|
362 getExpectedThresholds(format,
|
cannam@1315
|
363 audiofile,
|
Chris@1313
|
364 resampled,
|
Chris@1313
|
365 gapless,
|
Chris@1313
|
366 normalised,
|
Chris@1313
|
367 maxLimit, rmsLimit);
|
Chris@1313
|
368
|
Chris@1313
|
369 double edgeLimit = maxLimit * 3; // in first or final edgeSize frames
|
Chris@1313
|
370 if (resampled && edgeLimit < 0.1) edgeLimit = 0.1;
|
Chris@759
|
371 int edgeSize = 100;
|
Chris@759
|
372
|
Chris@759
|
373 // And we ignore completely the last few frames when upsampling
|
Chris@1313
|
374 int discard = 1 + int(round(readRate / fileRate));
|
Chris@759
|
375
|
Chris@759
|
376 int offset = 0;
|
Chris@759
|
377
|
Chris@1313
|
378 if (perceptual) {
|
Chris@759
|
379
|
cannam@1314
|
380 // Look for an initial offset.
|
cannam@1314
|
381 //
|
cannam@1314
|
382 // We know the first channel has a sinusoid in it. It
|
cannam@1314
|
383 // should have a peak at 0.4ms (see AudioTestData.h) but
|
cannam@1314
|
384 // that might have been clipped, which would make it
|
cannam@1314
|
385 // imprecise. We can tell if it's clipped, though, as
|
cannam@1314
|
386 // there will be samples having exactly identical
|
cannam@1314
|
387 // values. So what we look for is the peak if it's not
|
cannam@1314
|
388 // clipped and, if it is, the first zero crossing after
|
cannam@1314
|
389 // the peak, which should be at 0.8ms.
|
cannam@1314
|
390
|
Chris@1296
|
391 int expectedPeak = int(0.0004 * readRate);
|
cannam@1314
|
392 int expectedZC = int(0.0008 * readRate);
|
cannam@1314
|
393 bool foundPeak = false;
|
cannam@1314
|
394 for (int i = 1; i+1 < read; ++i) {
|
cannam@1314
|
395 float prevSample = test[(i-1) * channels];
|
cannam@1314
|
396 float thisSample = test[i * channels];
|
cannam@1314
|
397 float nextSample = test[(i+1) * channels];
|
cannam@1314
|
398 if (thisSample > 0.8 && nextSample < thisSample) {
|
cannam@1314
|
399 foundPeak = true;
|
cannam@1314
|
400 if (thisSample > prevSample) {
|
cannam@1314
|
401 // not clipped
|
cannam@1314
|
402 offset = i - expectedPeak - 1;
|
cannam@1314
|
403 break;
|
cannam@1314
|
404 }
|
cannam@1314
|
405 }
|
cannam@1314
|
406 if (foundPeak && (thisSample >= 0.0 && nextSample < 0.0)) {
|
cannam@1315
|
407 // cerr << "thisSample = " << thisSample << ", nextSample = "
|
cannam@1315
|
408 // << nextSample << endl;
|
cannam@1314
|
409 offset = i - expectedZC - 1;
|
Chris@759
|
410 break;
|
Chris@759
|
411 }
|
Chris@759
|
412 }
|
Chris@1313
|
413
|
cannam@1315
|
414 // int fileRateEquivalent = int((offset / readRate) * fileRate);
|
cannam@1315
|
415 // std::cerr << "offset = " << offset << std::endl;
|
cannam@1315
|
416 // std::cerr << "at file rate would be " << fileRateEquivalent << std::endl;
|
Chris@1313
|
417
|
Chris@1313
|
418 // Previously our m4a test file had a fixed offset of 1024
|
Chris@1313
|
419 // at the file sample rate -- this may be because it was
|
Chris@1313
|
420 // produced by FAAC which did not write in the delay as
|
Chris@1313
|
421 // metadata? We now have an m4a produced by Core Audio
|
Chris@1313
|
422 // which gives a 0 offset. What to do...
|
Chris@1313
|
423
|
Chris@1313
|
424 // Anyway, mp3s should have 0 offset in gapless mode and
|
Chris@1313
|
425 // "something else" otherwise.
|
Chris@1313
|
426
|
Chris@1313
|
427 if (gapless) {
|
Chris@1603
|
428 if (format == "aac"
|
Chris@1603
|
429 #ifdef Q_OS_WIN
|
Chris@1603
|
430 || (format == "mp3" && (readRate != fileRate))
|
Chris@1603
|
431 #endif
|
Chris@1603
|
432 ) {
|
cannam@1315
|
433 // ouch!
|
cannam@1315
|
434 if (offset == -1) offset = 0;
|
cannam@1315
|
435 }
|
Chris@1313
|
436 QCOMPARE(offset, 0);
|
Chris@1313
|
437 }
|
Chris@759
|
438 }
|
Chris@756
|
439
|
cannam@1315
|
440 {
|
cannam@1315
|
441 // Write the diff file now, so that it's already been written
|
cannam@1315
|
442 // even if the comparison fails. We aren't checking anything
|
cannam@1315
|
443 // here except as necessary to avoid buffer overruns etc
|
cannam@1315
|
444
|
cannam@1315
|
445 QString diffFile =
|
cannam@1315
|
446 testName(format, audiofile, rate, normalised, gapless);
|
cannam@1315
|
447 diffFile.replace("/", "_");
|
cannam@1315
|
448 diffFile.replace(".", "_");
|
cannam@1315
|
449 diffFile.replace(" ", "_");
|
cannam@1315
|
450 diffFile += ".wav";
|
cannam@1315
|
451 diffFile = QDir(diffDir).filePath(diffFile);
|
cannam@1315
|
452 WavFileWriter diffWriter(diffFile, readRate, channels,
|
Chris@1359
|
453 WavFileWriter::WriteToTemporary);
|
cannam@1315
|
454 QVERIFY(diffWriter.isOK());
|
cannam@1315
|
455
|
cannam@1315
|
456 vector<vector<float>> diffs(channels);
|
cannam@1315
|
457 for (int c = 0; c < channels; ++c) {
|
cannam@1315
|
458 for (int i = 0; i < refFrames; ++i) {
|
cannam@1315
|
459 int ix = i + offset;
|
cannam@1315
|
460 if (ix < read) {
|
cannam@1315
|
461 float signeddiff =
|
cannam@1315
|
462 test[ix * channels + c] -
|
cannam@1315
|
463 reference[i * channels + c];
|
cannam@1315
|
464 diffs[c].push_back(signeddiff);
|
cannam@1315
|
465 }
|
cannam@1315
|
466 }
|
cannam@1315
|
467 }
|
cannam@1315
|
468 float **ptrs = new float*[channels];
|
cannam@1315
|
469 for (int c = 0; c < channels; ++c) {
|
cannam@1315
|
470 ptrs[c] = diffs[c].data();
|
cannam@1315
|
471 }
|
cannam@1315
|
472 diffWriter.writeSamples(ptrs, refFrames);
|
cannam@1315
|
473 delete[] ptrs;
|
cannam@1315
|
474 }
|
Chris@1313
|
475
|
Chris@1346
|
476 for (int c = 0; c < channels; ++c) {
|
Chris@1313
|
477
|
Chris@1313
|
478 double maxDiff = 0.0;
|
Chris@1313
|
479 double totalDiff = 0.0;
|
Chris@1313
|
480 double totalSqrDiff = 0.0;
|
Chris@1346
|
481 int maxIndex = 0;
|
Chris@1313
|
482
|
Chris@1346
|
483 for (int i = 0; i < refFrames; ++i) {
|
Chris@1296
|
484 int ix = i + offset;
|
Chris@1296
|
485 if (ix >= read) {
|
Chris@1428
|
486 SVCERR << "ERROR: audiofile " << audiofile << " reads truncated (read-rate reference frames " << i << " onward, of " << refFrames << ", are lost)" << endl;
|
Chris@1296
|
487 QVERIFY(ix < read);
|
Chris@1296
|
488 }
|
Chris@1313
|
489
|
Chris@1296
|
490 if (ix + discard >= read) {
|
Chris@1296
|
491 // we forgive the very edge samples when
|
Chris@1296
|
492 // resampling (discard > 0)
|
Chris@1296
|
493 continue;
|
Chris@1296
|
494 }
|
Chris@1313
|
495
|
Chris@1346
|
496 double diff = fabs(test[ix * channels + c] -
|
cannam@1315
|
497 reference[i * channels + c]);
|
Chris@1313
|
498
|
Chris@1346
|
499 totalDiff += diff;
|
Chris@1313
|
500 totalSqrDiff += diff * diff;
|
Chris@1313
|
501
|
Chris@757
|
502 // in edge areas, record this only if it exceeds edgeLimit
|
Chris@1313
|
503 if (i < edgeSize || i + edgeSize >= refFrames) {
|
Chris@1313
|
504 if (diff > edgeLimit && diff > maxDiff) {
|
Chris@1313
|
505 maxDiff = diff;
|
Chris@1313
|
506 maxIndex = i;
|
Chris@757
|
507 }
|
Chris@757
|
508 } else {
|
Chris@1313
|
509 if (diff > maxDiff) {
|
Chris@1313
|
510 maxDiff = diff;
|
Chris@1313
|
511 maxIndex = i;
|
Chris@757
|
512 }
|
Chris@1346
|
513 }
|
Chris@1346
|
514 }
|
Chris@1313
|
515
|
Chris@1346
|
516 double meanDiff = totalDiff / double(refFrames);
|
Chris@1313
|
517 double rmsDiff = sqrt(totalSqrDiff / double(refFrames));
|
cannam@1308
|
518
|
cannam@1314
|
519 /*
|
Chris@1346
|
520 cerr << "channel " << c << ": mean diff " << meanDiff << endl;
|
Chris@1429
|
521 cerr << "channel " << c << ": rms diff " << rmsDiff << endl;
|
Chris@1429
|
522 cerr << "channel " << c << ": max diff " << maxDiff << " at " << maxIndex << endl;
|
cannam@1314
|
523 */
|
Chris@1313
|
524 if (rmsDiff >= rmsLimit) {
|
Chris@1428
|
525 SVCERR << "ERROR: for audiofile " << audiofile << ": RMS diff = " << rmsDiff << " for channel " << c << " (limit = " << rmsLimit << ")" << endl;
|
Chris@1313
|
526 QVERIFY(rmsDiff < rmsLimit);
|
Chris@1313
|
527 }
|
Chris@1346
|
528 if (maxDiff >= maxLimit) {
|
Chris@1428
|
529 SVCERR << "ERROR: for audiofile " << audiofile << ": max diff = " << maxDiff << " at frame " << maxIndex << " of " << read << " on channel " << c << " (limit = " << maxLimit << ", edge limit = " << edgeLimit << ", mean diff = " << meanDiff << ", rms = " << rmsDiff << ")" << endl;
|
Chris@1346
|
530 QVERIFY(maxDiff < maxLimit);
|
Chris@1346
|
531 }
|
Chris@1313
|
532
|
Chris@1313
|
533 // and check for spurious material at end
|
Chris@1313
|
534
|
Chris@1309
|
535 for (sv_frame_t i = refFrames; i + offset < read; ++i) {
|
Chris@1309
|
536 sv_frame_t ix = i + offset;
|
Chris@1323
|
537 float quiet = 0.1f; //!!! allow some ringing - but let's come back to this, it should tail off
|
cannam@1308
|
538 float mag = fabsf(test[ix * channels + c]);
|
cannam@1308
|
539 if (mag > quiet) {
|
Chris@1428
|
540 SVCERR << "ERROR: audiofile " << audiofile << " contains spurious data after end of reference (found sample " << test[ix * channels + c] << " at index " << ix << " of channel " << c << " after reference+offset ended at " << refFrames+offset << ")" << endl;
|
cannam@1308
|
541 QVERIFY(mag < quiet);
|
cannam@1308
|
542 }
|
cannam@1308
|
543 }
|
Chris@1429
|
544 }
|
Chris@756
|
545 }
|
Chris@756
|
546 };
|
Chris@756
|
547
|
Chris@756
|
548 #endif
|