annotate data/fileio/test/EncodingTest.h @ 1698:dbd13eb7dad1

Add tests for audio file readers presented with empty or nonsense input
author Chris Cannam
date Fri, 03 May 2019 13:33:53 +0100
parents ae9849c0815f
children 83cb6e9d769b
rev   line source
Chris@1345 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@1345 2
Chris@1345 3 /*
Chris@1345 4 Sonic Visualiser
Chris@1345 5 An audio file viewer and annotation editor.
Chris@1345 6 Centre for Digital Music, Queen Mary, University of London.
Chris@1345 7
Chris@1345 8 This program is free software; you can redistribute it and/or
Chris@1345 9 modify it under the terms of the GNU General Public License as
Chris@1345 10 published by the Free Software Foundation; either version 2 of the
Chris@1345 11 License, or (at your option) any later version. See the file
Chris@1345 12 COPYING included with this distribution for more information.
Chris@1345 13 */
Chris@1345 14
Chris@1345 15 #ifndef TEST_AUDIO_ENCODINGS_H
Chris@1345 16 #define TEST_AUDIO_ENCODINGS_H
Chris@1345 17
Chris@1345 18 // Quick tests for filename encodings and encoding of ID3 data. Not a
Chris@1345 19 // test of audio codecs.
Chris@1345 20
Chris@1345 21 #include "../AudioFileReaderFactory.h"
Chris@1345 22 #include "../AudioFileReader.h"
Chris@1359 23 #include "../WavFileWriter.h"
Chris@1345 24
Chris@1698 25 #include "UnsupportedFormat.h"
Chris@1698 26
Chris@1345 27 #include <cmath>
Chris@1345 28
Chris@1345 29 #include <QObject>
Chris@1345 30 #include <QtTest>
Chris@1345 31 #include <QDir>
Chris@1345 32
Chris@1345 33 #include <iostream>
Chris@1345 34
Chris@1345 35 using namespace std;
Chris@1345 36
Chris@1346 37 const char utf8_name_cdp_1[] = "Caf\303\251 de Paris";
Chris@1346 38 const char utf8_name_cdp_2[] = "Caf\303\251 de \351\207\215\345\272\206";
Chris@1347 39 const char utf8_name_tsprk[] = "T\303\253mple of Sp\303\266rks";
Chris@1346 40 const char utf8_name_sprkt[] = "\343\202\271\343\203\235\343\203\274\343\202\257\343\201\256\345\257\272\351\231\242";
Chris@1345 41
Chris@1359 42 // Mapping between filename and expected title metadata field
Chris@1345 43 static const char *mapping[][2] = {
Chris@1346 44 { "id3v2-iso-8859-1", utf8_name_cdp_1 },
Chris@1346 45 { "id3v2-ucs-2", utf8_name_cdp_2 },
Chris@1346 46 { utf8_name_tsprk, utf8_name_tsprk },
Chris@1346 47 { utf8_name_sprkt, utf8_name_sprkt },
Chris@1345 48 };
Chris@1345 49 static const int mappingCount = 4;
Chris@1345 50
Chris@1597 51 #ifdef Q_OS_MAC
Chris@1597 52 // see note in addAudioFiles below
Chris@1593 53 static const char *testFiles[][2] = {
Chris@1593 54 { "id3v2-iso-8859-1", "mp3" },
Chris@1593 55 { "id3v2-ucs-2", "mp3" },
Chris@1593 56 { utf8_name_tsprk, "flac" },
Chris@1593 57 { utf8_name_tsprk, "m4a" },
Chris@1593 58 { utf8_name_tsprk, "mp3" },
Chris@1593 59 { utf8_name_tsprk, "ogg" },
Chris@1594 60 { utf8_name_tsprk, "opus" },
Chris@1593 61 { utf8_name_sprkt, "mp3" },
Chris@1593 62 { utf8_name_sprkt, "ogg" },
Chris@1593 63 };
Chris@1593 64 static const int testFileCount = 8;
Chris@1597 65 #endif
Chris@1593 66
Chris@1345 67 class EncodingTest : public QObject
Chris@1345 68 {
Chris@1345 69 Q_OBJECT
Chris@1345 70
Chris@1346 71 private:
Chris@1346 72 QString testDirBase;
Chris@1346 73 QString encodingDir;
Chris@1359 74 QString outDir;
Chris@1346 75
Chris@1346 76 public:
Chris@1346 77 EncodingTest(QString base) {
Chris@1346 78 if (base == "") {
Chris@1346 79 base = "svcore/data/fileio/test";
Chris@1346 80 }
Chris@1346 81 testDirBase = base;
Chris@1346 82 encodingDir = base + "/encodings";
Chris@1359 83 outDir = base + "/outfiles";
Chris@1346 84 }
Chris@1346 85
Chris@1346 86 private:
Chris@1596 87 const char *strOf(QString s) {
Chris@1596 88 return strdup(s.toLocal8Bit().data());
Chris@1596 89 }
Chris@1596 90
Chris@1359 91 void addAudioFiles() {
Chris@1596 92 QTest::addColumn<QString>("audiofile");
Chris@1596 93 #ifndef Q_OS_MAC
Chris@1596 94 // The normal case - populate the file list from the files
Chris@1596 95 // actually present in the encodings directory
Chris@1596 96 QStringList files = QDir(encodingDir).entryList(QDir::Files);
Chris@1596 97 foreach (QString filename, files) {
Chris@1596 98 QTest::newRow(strOf(filename)) << filename;
Chris@1596 99 }
Chris@1596 100 #else
Chris@1596 101 // Deviant case for Mac - populate the file list from the
Chris@1596 102 // hard-coded list of expected files in testFiles. This is
Chris@1596 103 // because QDir::entryList is currently broken on APFS (as of
Chris@1596 104 // Qt 5.12) because of variant Unicode normalisations.
Chris@1596 105 for (int i = 0; i < testFileCount; ++i) {
Chris@1596 106 std::string s = testFiles[i][0];
Chris@1596 107 s += ".";
Chris@1596 108 s += testFiles[i][1];
Chris@1596 109 QTest::newRow(strdup(s.c_str())) << QString::fromStdString(s);
Chris@1596 110 }
Chris@1596 111 #endif
Chris@1359 112 }
Chris@1359 113
Chris@1345 114 private slots:
Chris@1345 115 void init()
Chris@1345 116 {
Chris@1345 117 if (!QDir(encodingDir).exists()) {
Chris@1428 118 SVCERR << "ERROR: Audio encoding file directory \"" << encodingDir << "\" does not exist" << endl;
Chris@1345 119 QVERIFY2(QDir(encodingDir).exists(), "Audio encoding file directory not found");
Chris@1593 120 }
Chris@1359 121 if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) {
Chris@1428 122 SVCERR << "ERROR: Audio out directory \"" << outDir << "\" does not exist and could not be created" << endl;
Chris@1359 123 QVERIFY2(QDir(outDir).exists(), "Audio out directory not found and could not be created");
Chris@1345 124 }
Chris@1345 125 }
Chris@1345 126
Chris@1359 127 void readAudio_data() {
Chris@1359 128 addAudioFiles();
Chris@1359 129 }
Chris@1359 130
Chris@1359 131 void readAudio() {
Chris@1359 132
Chris@1359 133 // Ensure that we can open all the files
Chris@1359 134
Chris@1359 135 QFETCH(QString, audiofile);
Chris@1359 136
Chris@1698 137 QStringList fileAndExt = audiofile.split(".");
Chris@1698 138 QString extension = fileAndExt[1];
Chris@1698 139
Chris@1592 140 if (!AudioFileReaderFactory::isSupported(encodingDir + "/" +
Chris@1592 141 audiofile)) {
Chris@1698 142 if (isLegitimatelyUnsupported(extension)) {
Chris@1592 143 #if ( QT_VERSION >= 0x050000 )
Chris@1698 144 QSKIP("Known unsupported file, skipping");
Chris@1592 145 #else
Chris@1698 146 QSKIP("Known unsupported file, skipping", SkipSingle);
Chris@1592 147 #endif
Chris@1698 148 }
Chris@1592 149 }
Chris@1592 150
Chris@1359 151 AudioFileReaderFactory::Parameters params;
Chris@1359 152 AudioFileReader *reader =
Chris@1359 153 AudioFileReaderFactory::createReader
Chris@1359 154 (encodingDir + "/" + audiofile, params);
Chris@1359 155
Chris@1359 156 QVERIFY(reader != nullptr);
Chris@1402 157
Chris@1402 158 delete reader;
Chris@1359 159 }
Chris@1359 160
Chris@1359 161 void readMetadata_data() {
Chris@1359 162 addAudioFiles();
Chris@1359 163 }
Chris@1359 164
Chris@1359 165 void readMetadata() {
Chris@1359 166
Chris@1359 167 // All files other than WAVs should have title metadata; check
Chris@1359 168 // that the title matches whatever is in our mapping structure
Chris@1359 169 // defined at the top
Chris@1359 170
Chris@1345 171 QFETCH(QString, audiofile);
Chris@1345 172
Chris@1698 173 QStringList fileAndExt = audiofile.split(".");
Chris@1698 174 QString file = fileAndExt[0];
Chris@1698 175 QString extension = fileAndExt[1];
Chris@1698 176
Chris@1345 177 AudioFileReaderFactory::Parameters params;
Chris@1346 178 AudioFileReader *reader =
Chris@1346 179 AudioFileReaderFactory::createReader
Chris@1346 180 (encodingDir + "/" + audiofile, params);
Chris@1345 181
Chris@1592 182 if (!reader) {
Chris@1698 183 if (isLegitimatelyUnsupported(extension)) {
Chris@1592 184 #if ( QT_VERSION >= 0x050000 )
Chris@1698 185 QSKIP("Unsupported file, skipping");
Chris@1592 186 #else
Chris@1698 187 QSKIP("Unsupported file, skipping", SkipSingle);
Chris@1592 188 #endif
Chris@1698 189 }
Chris@1592 190 }
Chris@1345 191
Chris@1698 192 QVERIFY(reader != nullptr);
Chris@1345 193
Chris@1402 194 if (extension == "wav") {
Chris@1402 195
Chris@1402 196 // Nothing
Chris@1402 197
Chris@1402 198 delete reader;
Chris@1402 199
Chris@1402 200 } else {
Chris@1345 201
Chris@1359 202 auto blah = reader->getInterleavedFrames(0, 10);
Chris@1359 203
Chris@1345 204 QString title = reader->getTitle();
Chris@1345 205 QVERIFY(title != QString());
Chris@1345 206
Chris@1402 207 delete reader;
Chris@1402 208
Chris@1345 209 bool found = false;
Chris@1345 210 for (int m = 0; m < mappingCount; ++m) {
Chris@1345 211 if (file == QString::fromUtf8(mapping[m][0])) {
Chris@1345 212 found = true;
Chris@1346 213 QString expected = QString::fromUtf8(mapping[m][1]);
Chris@1346 214 if (title != expected) {
Chris@1428 215 SVCERR << "Title does not match expected: codepoints are" << endl;
Chris@1428 216 SVCERR << "Title (" << title.length() << "ch): ";
Chris@1346 217 for (int i = 0; i < title.length(); ++i) {
Chris@1428 218 SVCERR << title[i].unicode() << " ";
Chris@1346 219 }
Chris@1428 220 SVCERR << endl;
Chris@1428 221 SVCERR << "Expected (" << expected.length() << "ch): ";
Chris@1346 222 for (int i = 0; i < expected.length(); ++i) {
Chris@1428 223 SVCERR << expected[i].unicode() << " ";
Chris@1346 224 }
Chris@1428 225 SVCERR << endl;
Chris@1346 226 }
Chris@1346 227 QCOMPARE(title, expected);
Chris@1345 228 break;
Chris@1345 229 }
Chris@1345 230 }
Chris@1345 231
Chris@1345 232 if (!found) {
Chris@1359 233 // Note that this can happen legitimately on Windows,
Chris@1359 234 // where (for annoying VCS-related reasons) the test
Chris@1359 235 // files may have a different filename encoding from
Chris@1359 236 // the expected UTF-16. We check this properly in
Chris@1359 237 // readWriteAudio below, by saving out the file to a
Chris@1359 238 // name matching the metadata
Chris@1428 239 SVCERR << "Couldn't find filename \""
Chris@1345 240 << file << "\" in title mapping array" << endl;
Chris@1346 241 QSKIP("Couldn't find filename in title mapping array");
Chris@1345 242 }
Chris@1345 243 }
Chris@1345 244 }
Chris@1359 245
Chris@1359 246 void readWriteAudio_data() {
Chris@1359 247 addAudioFiles();
Chris@1359 248 }
Chris@1359 249
Chris@1359 250 void readWriteAudio()
Chris@1359 251 {
Chris@1359 252 // For those files that have title metadata (i.e. all of them
Chris@1359 253 // except the WAVs), read the title metadata and write a wav
Chris@1359 254 // file (of arbitrary content) whose name matches that. Then
Chris@1359 255 // check that we can re-read it. This is intended to exercise
Chris@1359 256 // systems on which the original test filename is miscoded (as
Chris@1359 257 // can happen on Windows).
Chris@1359 258
Chris@1359 259 QFETCH(QString, audiofile);
Chris@1359 260
Chris@1359 261 QStringList fileAndExt = audiofile.split(".");
Chris@1359 262 QString file = fileAndExt[0];
Chris@1359 263 QString extension = fileAndExt[1];
Chris@1359 264
Chris@1359 265 if (extension == "wav") {
Chris@1359 266 return;
Chris@1359 267 }
Chris@1359 268
Chris@1359 269 AudioFileReaderFactory::Parameters params;
Chris@1359 270 AudioFileReader *reader =
Chris@1359 271 AudioFileReaderFactory::createReader
Chris@1359 272 (encodingDir + "/" + audiofile, params);
Chris@1592 273
Chris@1592 274 if (!reader) {
Chris@1698 275 if (isLegitimatelyUnsupported(extension)) {
Chris@1592 276 #if ( QT_VERSION >= 0x050000 )
Chris@1698 277 QSKIP("Unsupported file, skipping");
Chris@1592 278 #else
Chris@1698 279 QSKIP("Unsupported file, skipping", SkipSingle);
Chris@1592 280 #endif
Chris@1698 281 }
Chris@1592 282 }
Chris@1359 283
Chris@1698 284 QVERIFY(reader != nullptr);
Chris@1698 285
Chris@1359 286 QString title = reader->getTitle();
Chris@1359 287 QVERIFY(title != QString());
Chris@1359 288
Chris@1359 289 for (int useTemporary = 0; useTemporary <= 1; ++useTemporary) {
Chris@1359 290
Chris@1359 291 QString outfile = outDir + "/" + file + ".wav";
Chris@1359 292 WavFileWriter writer(outfile,
Chris@1359 293 reader->getSampleRate(),
Chris@1359 294 1,
Chris@1359 295 useTemporary ?
Chris@1359 296 WavFileWriter::WriteToTemporary :
Chris@1359 297 WavFileWriter::WriteToTarget);
Chris@1359 298
Chris@1359 299 QVERIFY(writer.isOK());
Chris@1359 300
Chris@1359 301 floatvec_t data { 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0 };
Chris@1359 302 const float *samples = data.data();
Chris@1359 303 bool ok = writer.writeSamples(&samples, 8);
Chris@1359 304 QVERIFY(ok);
Chris@1359 305
Chris@1359 306 ok = writer.close();
Chris@1359 307 QVERIFY(ok);
Chris@1359 308
Chris@1359 309 AudioFileReader *rereader =
Chris@1359 310 AudioFileReaderFactory::createReader(outfile, params);
Chris@1359 311 QVERIFY(rereader != nullptr);
Chris@1359 312
Chris@1359 313 floatvec_t readFrames = rereader->getInterleavedFrames(0, 8);
Chris@1359 314 QCOMPARE(readFrames, data);
Chris@1359 315
Chris@1359 316 delete rereader;
Chris@1359 317 }
Chris@1359 318
Chris@1359 319 delete reader;
Chris@1359 320 }
Chris@1345 321 };
Chris@1345 322
Chris@1345 323 #endif