Chris@1345: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@1345: Chris@1345: /* Chris@1345: Sonic Visualiser Chris@1345: An audio file viewer and annotation editor. Chris@1345: Centre for Digital Music, Queen Mary, University of London. Chris@1345: Chris@1345: This program is free software; you can redistribute it and/or Chris@1345: modify it under the terms of the GNU General Public License as Chris@1345: published by the Free Software Foundation; either version 2 of the Chris@1345: License, or (at your option) any later version. See the file Chris@1345: COPYING included with this distribution for more information. Chris@1345: */ Chris@1345: Chris@1345: #ifndef TEST_AUDIO_ENCODINGS_H Chris@1345: #define TEST_AUDIO_ENCODINGS_H Chris@1345: Chris@1345: // Quick tests for filename encodings and encoding of ID3 data. Not a Chris@1345: // test of audio codecs. Chris@1345: Chris@1345: #include "../AudioFileReaderFactory.h" Chris@1345: #include "../AudioFileReader.h" Chris@1359: #include "../WavFileWriter.h" Chris@1345: Chris@1698: #include "UnsupportedFormat.h" Chris@1698: Chris@1345: #include Chris@1345: Chris@1345: #include Chris@1345: #include Chris@1345: #include Chris@1345: Chris@1345: #include Chris@1345: Chris@1345: using namespace std; Chris@1345: Chris@1346: const char utf8_name_cdp_1[] = "Caf\303\251 de Paris"; Chris@1346: const char utf8_name_cdp_2[] = "Caf\303\251 de \351\207\215\345\272\206"; Chris@1347: const char utf8_name_tsprk[] = "T\303\253mple of Sp\303\266rks"; Chris@1346: 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: Chris@1359: // Mapping between filename and expected title metadata field Chris@1345: static const char *mapping[][2] = { Chris@1346: { "id3v2-iso-8859-1", utf8_name_cdp_1 }, Chris@1346: { "id3v2-ucs-2", utf8_name_cdp_2 }, Chris@1346: { utf8_name_tsprk, utf8_name_tsprk }, Chris@1346: { utf8_name_sprkt, utf8_name_sprkt }, Chris@1345: }; Chris@1345: static const int mappingCount = 4; Chris@1345: Chris@1597: #ifdef Q_OS_MAC Chris@1597: // see note in addAudioFiles below Chris@1593: static const char *testFiles[][2] = { Chris@1593: { "id3v2-iso-8859-1", "mp3" }, Chris@1593: { "id3v2-ucs-2", "mp3" }, Chris@1593: { utf8_name_tsprk, "flac" }, Chris@1593: { utf8_name_tsprk, "m4a" }, Chris@1593: { utf8_name_tsprk, "mp3" }, Chris@1593: { utf8_name_tsprk, "ogg" }, Chris@1594: { utf8_name_tsprk, "opus" }, Chris@1593: { utf8_name_sprkt, "mp3" }, Chris@1593: { utf8_name_sprkt, "ogg" }, Chris@1593: }; Chris@1593: static const int testFileCount = 8; Chris@1597: #endif Chris@1593: Chris@1345: class EncodingTest : public QObject Chris@1345: { Chris@1345: Q_OBJECT Chris@1345: Chris@1346: private: Chris@1346: QString testDirBase; Chris@1346: QString encodingDir; Chris@1359: QString outDir; Chris@1346: Chris@1346: public: Chris@1346: EncodingTest(QString base) { Chris@1346: if (base == "") { Chris@1346: base = "svcore/data/fileio/test"; Chris@1346: } Chris@1346: testDirBase = base; Chris@1346: encodingDir = base + "/encodings"; Chris@1359: outDir = base + "/outfiles"; Chris@1346: } Chris@1346: Chris@1346: private: Chris@1596: const char *strOf(QString s) { Chris@1596: return strdup(s.toLocal8Bit().data()); Chris@1596: } Chris@1596: Chris@1359: void addAudioFiles() { Chris@1596: QTest::addColumn("audiofile"); Chris@1596: #ifndef Q_OS_MAC Chris@1596: // The normal case - populate the file list from the files Chris@1596: // actually present in the encodings directory Chris@1596: QStringList files = QDir(encodingDir).entryList(QDir::Files); Chris@1596: foreach (QString filename, files) { Chris@1596: QTest::newRow(strOf(filename)) << filename; Chris@1596: } Chris@1596: #else Chris@1596: // Deviant case for Mac - populate the file list from the Chris@1596: // hard-coded list of expected files in testFiles. This is Chris@1596: // because QDir::entryList is currently broken on APFS (as of Chris@1596: // Qt 5.12) because of variant Unicode normalisations. Chris@1596: for (int i = 0; i < testFileCount; ++i) { Chris@1596: std::string s = testFiles[i][0]; Chris@1596: s += "."; Chris@1596: s += testFiles[i][1]; Chris@1596: QTest::newRow(strdup(s.c_str())) << QString::fromStdString(s); Chris@1596: } Chris@1596: #endif Chris@1359: } Chris@1359: Chris@1345: private slots: Chris@1345: void init() Chris@1345: { Chris@1345: if (!QDir(encodingDir).exists()) { Chris@1428: SVCERR << "ERROR: Audio encoding file directory \"" << encodingDir << "\" does not exist" << endl; Chris@1345: QVERIFY2(QDir(encodingDir).exists(), "Audio encoding file directory not found"); Chris@1593: } Chris@1359: if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) { Chris@1428: SVCERR << "ERROR: Audio out directory \"" << outDir << "\" does not exist and could not be created" << endl; Chris@1359: QVERIFY2(QDir(outDir).exists(), "Audio out directory not found and could not be created"); Chris@1345: } Chris@1345: } Chris@1345: Chris@1359: void readAudio_data() { Chris@1359: addAudioFiles(); Chris@1359: } Chris@1359: Chris@1359: void readAudio() { Chris@1359: Chris@1359: // Ensure that we can open all the files Chris@1359: Chris@1359: QFETCH(QString, audiofile); Chris@1359: Chris@1698: QStringList fileAndExt = audiofile.split("."); Chris@1698: QString extension = fileAndExt[1]; Chris@1698: Chris@1592: if (!AudioFileReaderFactory::isSupported(encodingDir + "/" + Chris@1592: audiofile)) { Chris@1714: if (UnsupportedFormat::isLegitimatelyUnsupported(extension)) { Chris@1592: #if ( QT_VERSION >= 0x050000 ) Chris@1698: QSKIP("Known unsupported file, skipping"); Chris@1592: #else Chris@1698: QSKIP("Known unsupported file, skipping", SkipSingle); Chris@1592: #endif Chris@1698: } Chris@1592: } Chris@1592: Chris@1359: AudioFileReaderFactory::Parameters params; Chris@1359: AudioFileReader *reader = Chris@1359: AudioFileReaderFactory::createReader Chris@1359: (encodingDir + "/" + audiofile, params); Chris@1359: Chris@1359: QVERIFY(reader != nullptr); Chris@1402: Chris@1402: delete reader; Chris@1359: } Chris@1359: Chris@1359: void readMetadata_data() { Chris@1359: addAudioFiles(); Chris@1359: } Chris@1359: Chris@1359: void readMetadata() { Chris@1359: Chris@1359: // All files other than WAVs should have title metadata; check Chris@1359: // that the title matches whatever is in our mapping structure Chris@1359: // defined at the top Chris@1359: Chris@1345: QFETCH(QString, audiofile); Chris@1345: Chris@1698: QStringList fileAndExt = audiofile.split("."); Chris@1698: QString file = fileAndExt[0]; Chris@1698: QString extension = fileAndExt[1]; Chris@1698: Chris@1345: AudioFileReaderFactory::Parameters params; Chris@1346: AudioFileReader *reader = Chris@1346: AudioFileReaderFactory::createReader Chris@1346: (encodingDir + "/" + audiofile, params); Chris@1345: Chris@1592: if (!reader) { Chris@1714: if (UnsupportedFormat::isLegitimatelyUnsupported(extension)) { Chris@1592: #if ( QT_VERSION >= 0x050000 ) Chris@1698: QSKIP("Unsupported file, skipping"); Chris@1592: #else Chris@1698: QSKIP("Unsupported file, skipping", SkipSingle); Chris@1592: #endif Chris@1698: } Chris@1592: } Chris@1345: Chris@1698: QVERIFY(reader != nullptr); Chris@1345: Chris@1402: if (extension == "wav") { Chris@1402: Chris@1402: // Nothing Chris@1402: Chris@1402: delete reader; Chris@1402: Chris@1402: } else { Chris@1345: Chris@1359: auto blah = reader->getInterleavedFrames(0, 10); Chris@1359: Chris@1345: QString title = reader->getTitle(); Chris@1345: QVERIFY(title != QString()); Chris@1345: Chris@1402: delete reader; Chris@1402: Chris@1345: bool found = false; Chris@1345: for (int m = 0; m < mappingCount; ++m) { Chris@1345: if (file == QString::fromUtf8(mapping[m][0])) { Chris@1345: found = true; Chris@1346: QString expected = QString::fromUtf8(mapping[m][1]); Chris@1346: if (title != expected) { Chris@1428: SVCERR << "Title does not match expected: codepoints are" << endl; Chris@1428: SVCERR << "Title (" << title.length() << "ch): "; Chris@1346: for (int i = 0; i < title.length(); ++i) { Chris@1428: SVCERR << title[i].unicode() << " "; Chris@1346: } Chris@1428: SVCERR << endl; Chris@1428: SVCERR << "Expected (" << expected.length() << "ch): "; Chris@1346: for (int i = 0; i < expected.length(); ++i) { Chris@1428: SVCERR << expected[i].unicode() << " "; Chris@1346: } Chris@1428: SVCERR << endl; Chris@1346: } Chris@1346: QCOMPARE(title, expected); Chris@1345: break; Chris@1345: } Chris@1345: } Chris@1345: Chris@1345: if (!found) { Chris@1359: // Note that this can happen legitimately on Windows, Chris@1359: // where (for annoying VCS-related reasons) the test Chris@1359: // files may have a different filename encoding from Chris@1359: // the expected UTF-16. We check this properly in Chris@1359: // readWriteAudio below, by saving out the file to a Chris@1359: // name matching the metadata Chris@1428: SVCERR << "Couldn't find filename \"" Chris@1345: << file << "\" in title mapping array" << endl; Chris@1346: QSKIP("Couldn't find filename in title mapping array"); Chris@1345: } Chris@1345: } Chris@1345: } Chris@1359: Chris@1359: void readWriteAudio_data() { Chris@1359: addAudioFiles(); Chris@1359: } Chris@1359: Chris@1359: void readWriteAudio() Chris@1359: { Chris@1359: // For those files that have title metadata (i.e. all of them Chris@1359: // except the WAVs), read the title metadata and write a wav Chris@1359: // file (of arbitrary content) whose name matches that. Then Chris@1359: // check that we can re-read it. This is intended to exercise Chris@1359: // systems on which the original test filename is miscoded (as Chris@1359: // can happen on Windows). Chris@1359: Chris@1359: QFETCH(QString, audiofile); Chris@1359: Chris@1359: QStringList fileAndExt = audiofile.split("."); Chris@1359: QString file = fileAndExt[0]; Chris@1359: QString extension = fileAndExt[1]; Chris@1359: Chris@1359: if (extension == "wav") { Chris@1359: return; Chris@1359: } Chris@1359: Chris@1359: AudioFileReaderFactory::Parameters params; Chris@1359: AudioFileReader *reader = Chris@1359: AudioFileReaderFactory::createReader Chris@1359: (encodingDir + "/" + audiofile, params); Chris@1592: Chris@1592: if (!reader) { Chris@1714: if (UnsupportedFormat::isLegitimatelyUnsupported(extension)) { Chris@1592: #if ( QT_VERSION >= 0x050000 ) Chris@1698: QSKIP("Unsupported file, skipping"); Chris@1592: #else Chris@1698: QSKIP("Unsupported file, skipping", SkipSingle); Chris@1592: #endif Chris@1698: } Chris@1592: } Chris@1359: Chris@1698: QVERIFY(reader != nullptr); Chris@1698: Chris@1359: QString title = reader->getTitle(); Chris@1359: QVERIFY(title != QString()); Chris@1359: Chris@1359: for (int useTemporary = 0; useTemporary <= 1; ++useTemporary) { Chris@1359: Chris@1359: QString outfile = outDir + "/" + file + ".wav"; Chris@1359: WavFileWriter writer(outfile, Chris@1359: reader->getSampleRate(), Chris@1359: 1, Chris@1359: useTemporary ? Chris@1359: WavFileWriter::WriteToTemporary : Chris@1359: WavFileWriter::WriteToTarget); Chris@1359: Chris@1359: QVERIFY(writer.isOK()); Chris@1359: Chris@1359: floatvec_t data { 0.0, 1.0, 0.0, -1.0, 0.0, 1.0, 0.0, -1.0 }; Chris@1359: const float *samples = data.data(); Chris@1359: bool ok = writer.writeSamples(&samples, 8); Chris@1359: QVERIFY(ok); Chris@1359: Chris@1359: ok = writer.close(); Chris@1359: QVERIFY(ok); Chris@1359: Chris@1359: AudioFileReader *rereader = Chris@1359: AudioFileReaderFactory::createReader(outfile, params); Chris@1359: QVERIFY(rereader != nullptr); Chris@1359: Chris@1359: floatvec_t readFrames = rereader->getInterleavedFrames(0, 8); Chris@1359: QCOMPARE(readFrames, data); Chris@1359: Chris@1359: delete rereader; Chris@1359: } Chris@1359: Chris@1359: delete reader; Chris@1359: } Chris@1345: }; Chris@1345: Chris@1345: #endif