Chris@756: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@756: 
Chris@756: /*
Chris@756:     Sonic Visualiser
Chris@756:     An audio file viewer and annotation editor.
Chris@756:     Centre for Digital Music, Queen Mary, University of London.
Chris@756:     This file copyright 2013 Chris Cannam.
Chris@756:     
Chris@756:     This program is free software; you can redistribute it and/or
Chris@756:     modify it under the terms of the GNU General Public License as
Chris@756:     published by the Free Software Foundation; either version 2 of the
Chris@756:     License, or (at your option) any later version.  See the file
Chris@756:     COPYING included with this distribution for more information.
Chris@756: */
Chris@756: 
Chris@756: #ifndef TEST_AUDIO_FILE_READER_H
Chris@756: #define TEST_AUDIO_FILE_READER_H
Chris@756: 
Chris@756: #include "../AudioFileReaderFactory.h"
Chris@756: #include "../AudioFileReader.h"
Chris@756: 
Chris@756: #include "AudioTestData.h"
Chris@756: 
Chris@756: #include <cmath>
Chris@756: 
Chris@756: #include <QObject>
Chris@756: #include <QtTest>
Chris@756: #include <QDir>
Chris@756: 
Chris@756: #include <iostream>
Chris@756: 
Chris@756: using namespace std;
Chris@756: 
Chris@1263: static QString audioDir = "svcore/data/fileio/test/testfiles";
Chris@756: 
Chris@756: class AudioFileReaderTest : public QObject
Chris@756: {
Chris@756:     Q_OBJECT
Chris@756: 
Chris@756:     const char *strOf(QString s) {
Chris@756:         return strdup(s.toLocal8Bit().data());
Chris@756:     }
Chris@756: 
Chris@756: private slots:
Chris@756:     void init()
Chris@756:     {
Chris@756:         if (!QDir(audioDir).exists()) {
Chris@756:             cerr << "ERROR: Audio test file directory \"" << audioDir << "\" does not exist" << endl;
Chris@756:             QVERIFY2(QDir(audioDir).exists(), "Audio test file directory not found");
Chris@756:         }
Chris@756:     }
Chris@756: 
Chris@756:     void read_data()
Chris@756:     {
Chris@756:         QTest::addColumn<QString>("audiofile");
Chris@756:         QStringList files = QDir(audioDir).entryList(QDir::Files);
Chris@756:         foreach (QString filename, files) {
Chris@756:             QTest::newRow(strOf(filename)) << filename;
Chris@756:         }
Chris@756:     }
Chris@756: 
Chris@756:     void read()
Chris@756:     {
Chris@756:         QFETCH(QString, audiofile);
Chris@756: 
Chris@1040:         sv_samplerate_t readRate = 48000;
Chris@757: 
Chris@756: 	AudioFileReader *reader =
Chris@756: 	    AudioFileReaderFactory::createReader
Chris@757: 	    (audioDir + "/" + audiofile, readRate);
Chris@757: 
Chris@757:         QStringList fileAndExt = audiofile.split(".");
Chris@757:         QStringList bits = fileAndExt[0].split("-");
Chris@757:         QString extension = fileAndExt[1];
Chris@1040:         sv_samplerate_t nominalRate = bits[0].toInt();
Chris@757:         int nominalChannels = bits[1].toInt();
Chris@757:         int nominalDepth = 16;
Chris@757:         if (bits.length() > 2) nominalDepth = bits[2].toInt();
Chris@756: 
Chris@756: 	if (!reader) {
Chris@820: #if ( QT_VERSION >= 0x050000 )
Chris@763: 	    QSKIP("Unsupported file, skipping");
Chris@820: #else
Chris@820: 	    QSKIP("Unsupported file, skipping", SkipSingle);
Chris@820: #endif
Chris@756: 	}
Chris@756: 
Chris@757:         QCOMPARE((int)reader->getChannelCount(), nominalChannels);
Chris@1040:         QCOMPARE(reader->getNativeRate(), nominalRate);
Chris@1040:         QCOMPARE(reader->getSampleRate(), readRate);
Chris@757: 
Chris@756: 	int channels = reader->getChannelCount();
Chris@757: 	AudioTestData tdata(readRate, channels);
Chris@756: 	
Chris@756: 	float *reference = tdata.getInterleavedData();
Chris@1040:         sv_frame_t refFrames = tdata.getFrameCount();
Chris@756: 	
Chris@756: 	// The reader should give us exactly the expected number of
Chris@759: 	// frames, except for mp3/aac files. We ask for quite a lot
Chris@759: 	// more, though, so we can (a) check that we only get the
Chris@759: 	// expected number back (if this is not mp3/aac) or (b) take
Chris@759: 	// into account silence at beginning and end (if it is).
Chris@1041: 	vector<float> test = reader->getInterleavedFrames(0, refFrames + 5000);
Chris@1040: 	sv_frame_t read = test.size() / channels;
Chris@756: 
Chris@759:         if (extension == "mp3" || extension == "aac" || extension == "m4a") {
Chris@759:             // mp3s and aacs can have silence at start and end
Chris@759:             QVERIFY(read >= refFrames);
Chris@757:         } else {
Chris@759:             QCOMPARE(read, refFrames);
Chris@757:         }
Chris@757: 
Chris@757:         // Our limits are pretty relaxed -- we're not testing decoder
Chris@757:         // or resampler quality here, just whether the results are
Chris@757:         // plainly wrong (e.g. at wrong samplerate or with an offset)
Chris@757: 
Chris@1040: 	double limit = 0.01;
Chris@1040:         double edgeLimit = limit * 10; // in first or final edgeSize frames
Chris@759:         int edgeSize = 100; 
Chris@759: 
Chris@757:         if (nominalDepth < 16) {
Chris@757:             limit = 0.02;
Chris@757:         }
Chris@759:         if (extension == "ogg" || extension == "mp3" ||
Chris@759:             extension == "aac" || extension == "m4a") {
Chris@1296:             limit = 0.1;
Chris@759:             edgeLimit = limit * 3;
Chris@757:         }
Chris@757: 
Chris@759:         // And we ignore completely the last few frames when upsampling
Chris@1040:         int discard = 1 + int(round(readRate / nominalRate));
Chris@759: 
Chris@759:         int offset = 0;
Chris@759: 
Chris@759:         if (extension == "aac" || extension == "m4a") {
Chris@759:             // our m4a file appears to have a fixed offset of 1024 (at
Chris@759:             // file sample rate)
Chris@1040:             offset = int(round((1024 / nominalRate) * readRate));
Chris@759:         }
Chris@759: 
Chris@759:         if (extension == "mp3") {
Chris@1296:             // ...while mp3s appear to vary. What we're looking for is
Chris@1296:             // the first peak of the sinusoid in the first channel
Chris@1296:             // (since we may have only the one channel). This should
Chris@1296:             // appear at 0.4ms (see AudioTestData.h)
Chris@1296:             int expectedPeak = int(0.0004 * readRate);
Chris@1296: //            std::cerr << "expectedPeak = " << expectedPeak << std::endl;
Chris@1296:             for (int i = 1; i < read; ++i) {
Chris@1296:                 if (test[i * channels] > 0.8 &&
Chris@1296:                     test[(i+1) * channels] < test[i * channels]) {
Chris@1296:                     offset = i - expectedPeak - 1;
Chris@1296: //                    std::cerr << "actual peak = " << i-1 << std::endl;
Chris@759:                     break;
Chris@759:                 }
Chris@759:             }
Chris@759: //            std::cerr << "offset = " << offset << std::endl;
Chris@759:         }
Chris@756: 
Chris@756: 	for (int c = 0; c < channels; ++c) {
Chris@756: 	    float maxdiff = 0.f;
Chris@756: 	    int maxAt = 0;
Chris@756: 	    float totdiff = 0.f;
Chris@1296: 	    for (int i = 0; i < refFrames; ++i) {
Chris@1296:                 int ix = i + offset;
Chris@1296:                 if (ix >= read) {
Chris@1296:                     cerr << "ERROR: audiofile " << audiofile << " reads truncated (read-rate reference frames " << i << " onward are lost)" << endl;
Chris@1296:                     QVERIFY(ix < read);
Chris@1296:                 }
Chris@1296:                 if (ix + discard >= read) {
Chris@1296:                     // we forgive the very edge samples when
Chris@1296:                     // resampling (discard > 0)
Chris@1296:                     continue;
Chris@1296:                 }
Chris@1296: 		float diff = fabsf(test[(ix) * channels + c] -
Chris@756: 				   reference[i * channels + c]);
Chris@756: 		totdiff += diff;
Chris@757:                 // in edge areas, record this only if it exceeds edgeLimit
Chris@759:                 if (i < edgeSize || i + edgeSize >= read - offset) {
Chris@968:                     if (diff > edgeLimit && diff > maxdiff) {
Chris@757:                         maxdiff = diff;
Chris@757:                         maxAt = i;
Chris@757:                     }
Chris@757:                 } else {
Chris@757:                     if (diff > maxdiff) {
Chris@757:                         maxdiff = diff;
Chris@757:                         maxAt = i;
Chris@757:                     }
Chris@756: 		}
Chris@756: 	    }
Chris@1040: 	    float meandiff = totdiff / float(read);
Chris@756: //	    cerr << "meandiff on channel " << c << ": " << meandiff << endl;
Chris@756: //	    cerr << "maxdiff on channel " << c << ": " << maxdiff << " at " << maxAt << endl;
Chris@759:             if (meandiff >= limit) {
Chris@759: 		cerr << "ERROR: for audiofile " << audiofile << ": mean diff = " << meandiff << " for channel " << c << endl;
Chris@759:                 QVERIFY(meandiff < limit);
Chris@759:             }
Chris@756: 	    if (maxdiff >= limit) {
Chris@759: 		cerr << "ERROR: for audiofile " << audiofile << ": max diff = " << maxdiff << " at frame " << maxAt << " of " << read << " on channel " << c << " (mean diff = " << meandiff << ")" << endl;
Chris@756: 		QVERIFY(maxdiff < limit);
Chris@756: 	    }
Chris@756: 	}
Chris@756:     }
Chris@756: };
Chris@756: 
Chris@756: #endif