# HG changeset patch # User Chris Cannam # Date 1548254623 0 # Node ID ce185d4dd408323d9232b423c4991493a2d72518 # Parent d2555df635ec7e65edcd8ef7646c8ce49f72dabe# Parent 841b2a3e606d567ec64cb7b5c078081b5bae525d Merge from default branch diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/AudioFileReader.h --- a/data/fileio/AudioFileReader.h Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/AudioFileReader.h Wed Jan 23 14:43:43 2019 +0000 @@ -79,14 +79,14 @@ * may be implemented by subclasses that support file tagging. * This is not the same thing as the file name. */ - virtual QString getTitle() const { return ""; } + virtual QString getTitle() const = 0; /** * Return the "maker" of the work in the audio file, if known. * This could represent almost anything (band, composer, * conductor, artist etc). */ - virtual QString getMaker() const { return ""; } + virtual QString getMaker() const = 0; /** * Return the local file path of the audio data. This is the diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/AudioFileReaderFactory.cpp --- a/data/fileio/AudioFileReaderFactory.cpp Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/AudioFileReaderFactory.cpp Wed Jan 23 14:43:43 2019 +0000 @@ -17,9 +17,8 @@ #include "WavFileReader.h" #include "DecodingWavFileReader.h" -#include "OggVorbisFileReader.h" #include "MP3FileReader.h" -#include "CoreAudioFileReader.h" +#include "BQAFileReader.h" #include "AudioFileSizeEstimator.h" #include "base/StorageAdviser.h" @@ -28,26 +27,21 @@ #include #include +using namespace std; + QString AudioFileReaderFactory::getKnownExtensions() { - std::set extensions; + set extensions; WavFileReader::getSupportedExtensions(extensions); #ifdef HAVE_MAD MP3FileReader::getSupportedExtensions(extensions); #endif -#ifdef HAVE_OGGZ -#ifdef HAVE_FISHSOUND - OggVorbisFileReader::getSupportedExtensions(extensions); -#endif -#endif -#ifdef HAVE_COREAUDIO - CoreAudioFileReader::getSupportedExtensions(extensions); -#endif + BQAFileReader::getSupportedExtensions(extensions); QString rv; - for (std::set::const_iterator i = extensions.begin(); + for (set::const_iterator i = extensions.begin(); i != extensions.end(); ++i) { if (i != extensions.begin()) rv += " "; rv += "*." + *i; @@ -56,6 +50,23 @@ return rv; } +bool +AudioFileReaderFactory::isSupported(FileSource source) +{ +#ifdef HAVE_MAD + if (MP3FileReader::supports(source)) { + return true; + } +#endif + if (WavFileReader::supports(source)) { + return true; + } + if (BQAFileReader::supports(source)) { + return true; + } + return false; +} + AudioFileReader * AudioFileReaderFactory::createReader(FileSource source, Parameters params, @@ -126,27 +137,35 @@ SVDEBUG << "AudioFileReaderFactory: Source not officially handled by any reader, trying again with each reader in turn" << endl; } - -#ifdef HAVE_OGGZ -#ifdef HAVE_FISHSOUND - // If we have the "real" Ogg reader, use that first. Otherwise - // the WavFileReader will likely accept Ogg files (as - // libsndfile supports them) but it has no ability to return - // file metadata, so we get a slightly less useful result. - if (anyReader || OggVorbisFileReader::supports(source)) { - reader = new OggVorbisFileReader - (source, decodeMode, cacheMode, targetRate, normalised, reporter); +#ifdef HAVE_MAD + // Having said we'll try any reader on the second pass, we + // actually don't want to try the mp3 reader for anything not + // identified as an mp3 - it can't identify files by header, + // it'll try to read any data and then fail with + // synchronisation errors - causing misleading and potentially + // alarming warning messages at the least + if (!anyReader) { + if (MP3FileReader::supports(source)) { - if (reader->isOK()) { - SVDEBUG << "AudioFileReaderFactory: Ogg file reader is OK, returning it" << endl; - return reader; - } else { - delete reader; + MP3FileReader::GaplessMode gapless = + params.gaplessMode == GaplessMode::Gapless ? + MP3FileReader::GaplessMode::Gapless : + MP3FileReader::GaplessMode::Gappy; + + reader = new MP3FileReader + (source, decodeMode, cacheMode, gapless, + targetRate, normalised, reporter); + + if (reader->isOK()) { + SVDEBUG << "AudioFileReaderFactory: MP3 file reader is OK, returning it" << endl; + return reader; + } else { + delete reader; + } } } #endif -#endif if (anyReader || WavFileReader::supports(source)) { @@ -178,43 +197,20 @@ delete reader; } } + + if (anyReader || BQAFileReader::supports(source)) { -#ifdef HAVE_MAD - if (anyReader || MP3FileReader::supports(source)) { - - MP3FileReader::GaplessMode gapless = - params.gaplessMode == GaplessMode::Gapless ? - MP3FileReader::GaplessMode::Gapless : - MP3FileReader::GaplessMode::Gappy; - - reader = new MP3FileReader - (source, decodeMode, cacheMode, gapless, + reader = new BQAFileReader + (source, decodeMode, cacheMode, targetRate, normalised, reporter); if (reader->isOK()) { - SVDEBUG << "AudioFileReaderFactory: MP3 file reader is OK, returning it" << endl; + SVDEBUG << "AudioFileReaderFactory: BQA reader is OK, returning it" << endl; return reader; } else { delete reader; } } -#endif - -#ifdef HAVE_COREAUDIO - if (anyReader || CoreAudioFileReader::supports(source)) { - - reader = new CoreAudioFileReader - (source, decodeMode, cacheMode, targetRate, normalised, reporter); - - if (reader->isOK()) { - SVDEBUG << "AudioFileReaderFactory: CoreAudio reader is OK, returning it" << endl; - return reader; - } else { - delete reader; - } - } -#endif - } SVCERR << "AudioFileReaderFactory::Failed to create a reader for " diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/AudioFileReaderFactory.h --- a/data/fileio/AudioFileReaderFactory.h Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/AudioFileReaderFactory.h Wed Jan 23 14:43:43 2019 +0000 @@ -141,6 +141,15 @@ static AudioFileReader *createReader(FileSource source, Parameters parameters, ProgressReporter *reporter = 0); + + /** + * Return true if the given source has a file extension that + * indicates a supported file type. This does not necessarily mean + * that it can be opened; conversely it may theoretically be + * possible to open some files without supported extensions, + * depending on the readers available. + */ + static bool isSupported(FileSource source); }; #endif diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/AudioFileSizeEstimator.cpp --- a/data/fileio/AudioFileSizeEstimator.cpp Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/AudioFileSizeEstimator.cpp Wed Jan 23 14:43:43 2019 +0000 @@ -83,7 +83,7 @@ if (extension == "ogg" || extension == "oga" || extension == "m4a" || extension == "mp3" || - extension == "wma") { + extension == "wma" || extension == "opus") { // Usually a lossy file. Compression ratios can vary // dramatically, but don't usually exceed about 20x compared diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/BQAFileReader.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/BQAFileReader.cpp Wed Jan 23 14:43:43 2019 +0000 @@ -0,0 +1,203 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#include "BQAFileReader.h" + +#include +#include +#include + +#include "base/Profiler.h" +#include "base/ProgressReporter.h" + +#include + +using namespace std; + +BQAFileReader::BQAFileReader(FileSource source, + DecodeMode decodeMode, + CacheMode mode, + sv_samplerate_t targetRate, + bool normalised, + ProgressReporter *reporter) : + CodedAudioFileReader(mode, targetRate, normalised), + m_source(source), + m_path(source.getLocalFilename()), + m_cancelled(false), + m_completion(0), + m_reporter(reporter), + m_decodeThread(0) +{ + SVDEBUG << "BQAFileReader: local path: \"" << m_path + << "\", decode mode: " << decodeMode << " (" + << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded") + << ")" << endl; + + m_channelCount = 0; + m_fileRate = 0; + + Profiler profiler("BQAFileReader::BQAFileReader"); + + try { + m_stream = breakfastquay::AudioReadStreamFactory::createReadStream + (m_path.toUtf8().data()); + } catch (const std::exception &e) { + m_error = e.what(); + SVDEBUG << "BQAFileReader: createReadStream failed: " << m_error << endl; + m_stream = 0; + return; + } + + m_channelCount = int(m_stream->getChannelCount()); + m_fileRate = sv_samplerate_t(m_stream->getSampleRate()); + m_title = QString::fromUtf8(m_stream->getTrackName().c_str()); + m_maker = QString::fromUtf8(m_stream->getArtistName().c_str()); + + initialiseDecodeCache(); + + if (decodeMode == DecodeAtOnce) { + + if (m_reporter) { + connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled())); + m_reporter->setMessage + (tr("Decoding %1...").arg(QFileInfo(m_path).fileName())); + } + + sv_frame_t blockSize = 65536; + floatvec_t block(blockSize * m_channelCount, 0.f); + + while (true) { + try { + sv_frame_t retrieved = + m_stream->getInterleavedFrames(blockSize, block.data()); + + addSamplesToDecodeCache(block.data(), retrieved); + + if (retrieved < blockSize) { + break; + } + } catch (const breakfastquay::InvalidFileFormat &f) { + m_error = f.what(); + SVDEBUG << "BQAFileReader: init failed: " << m_error << endl; + break; + } + + if (m_cancelled) break; + } + + if (isDecodeCacheInitialised()) finishDecodeCache(); + endSerialised(); + + if (m_reporter) m_reporter->setProgress(100); + + delete m_stream; + m_stream = 0; + + } else { + + if (m_reporter) m_reporter->setProgress(100); + + m_decodeThread = new DecodeThread(this); + m_decodeThread->start(); + } +} + +BQAFileReader::~BQAFileReader() +{ + if (m_decodeThread) { + m_cancelled = true; + m_decodeThread->wait(); + delete m_decodeThread; + } + + delete m_stream; +} + +void +BQAFileReader::cancelled() +{ + m_cancelled = true; +} + +void +BQAFileReader::DecodeThread::run() +{ + if (m_reader->m_cacheMode == CacheInTemporaryFile) { + m_reader->startSerialised("BQAFileReader::Decode"); + } + + sv_frame_t blockSize = 65536; + floatvec_t block(blockSize * m_reader->getChannelCount(), 0.f); + + while (true) { + try { + sv_frame_t retrieved = + m_reader->m_stream->getInterleavedFrames + (blockSize, block.data()); + + m_reader->addSamplesToDecodeCache(block.data(), retrieved); + + if (retrieved < blockSize) { + break; + } + } catch (const breakfastquay::InvalidFileFormat &f) { + m_reader->m_error = f.what(); + SVDEBUG << "BQAFileReader: decode failed: " << m_reader->m_error << endl; + break; + } + + if (m_reader->m_cancelled) break; + } + + if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); + m_reader->m_completion = 100; + + m_reader->endSerialised(); + + delete m_reader->m_stream; + m_reader->m_stream = 0; +} + +void +BQAFileReader::getSupportedExtensions(set &extensions) +{ + vector exts = + breakfastquay::AudioReadStreamFactory::getSupportedFileExtensions(); + for (auto e: exts) { + extensions.insert(QString::fromUtf8(e.c_str())); + } +} + +bool +BQAFileReader::supportsExtension(QString extension) +{ + set extensions; + getSupportedExtensions(extensions); + return (extensions.find(extension.toLower()) != extensions.end()); +} + +bool +BQAFileReader::supportsContentType(QString) +{ +//!!! todo + return false; +} + +bool +BQAFileReader::supports(FileSource &source) +{ + return (supportsExtension(source.getExtension()) || + supportsContentType(source.getContentType())); +} + diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/BQAFileReader.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/data/fileio/BQAFileReader.h Wed Jan 23 14:43:43 2019 +0000 @@ -0,0 +1,87 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef SV_BQA_FILE_READER_H +#define SV_BQA_FILE_READER_H + +#include + +#include "CodedAudioFileReader.h" +#include "base/Thread.h" + +#include + +class ProgressReporter; + +/** + * Audio file reader using bqaudiostream library AudioReadStream + * classes. + */ +class BQAFileReader : public CodedAudioFileReader +{ + Q_OBJECT + +public: + BQAFileReader(FileSource source, + DecodeMode decodeMode, + CacheMode cacheMode, + sv_samplerate_t targetRate = 0, + bool normalised = false, + ProgressReporter *reporter = 0); + virtual ~BQAFileReader(); + + QString getError() const override { return m_error; } + QString getLocation() const override { return m_source.getLocation(); } + QString getTitle() const override { return m_title; } + QString getMaker() const override { return m_maker; } + + static void getSupportedExtensions(std::set &extensions); + static bool supportsExtension(QString ext); + static bool supportsContentType(QString type); + static bool supports(FileSource &source); + + int getDecodeCompletion() const override { return m_completion; } + + bool isUpdating() const override { + return m_decodeThread && m_decodeThread->isRunning(); + } + +public slots: + void cancelled(); + +protected: + FileSource m_source; + QString m_path; + QString m_error; + QString m_title; + QString m_maker; + + breakfastquay::AudioReadStream *m_stream; + + bool m_cancelled; + int m_completion; + ProgressReporter *m_reporter; + + class DecodeThread : public Thread { + public: + DecodeThread(BQAFileReader *reader) : m_reader(reader) { } + virtual void run(); + protected: + BQAFileReader *m_reader; + }; + DecodeThread *m_decodeThread; +}; + +#endif + diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/CoreAudioFileReader.cpp --- a/data/fileio/CoreAudioFileReader.cpp Wed Jan 09 15:24:38 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,251 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. - */ - -#ifdef HAVE_COREAUDIO - -#include "CoreAudioFileReader.h" -#include "base/Profiler.h" -#include "base/ProgressReporter.h" -#include "system/System.h" - -#include - -#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) -#include -#include -#else -#include "AudioToolbox.h" -#include "ExtendedAudioFile.h" -#endif - -class CoreAudioFileReader::D -{ -public: - D() : blockSize(1024), valid(false) { } - - ExtAudioFileRef file; - AudioBufferList buffer; - OSStatus err; - AudioStreamBasicDescription asbd; - int blockSize; - bool valid; -}; - -static QString -codestr(OSStatus err) -{ - char text[5]; - UInt32 uerr = err; - text[0] = (uerr >> 24) & 0xff; - text[1] = (uerr >> 16) & 0xff; - text[2] = (uerr >> 8) & 0xff; - text[3] = (uerr) & 0xff; - text[4] = '\0'; - return QString("%1 (%2)").arg(err).arg(QString::fromLocal8Bit(text)); -} - -CoreAudioFileReader::CoreAudioFileReader(FileSource source, - DecodeMode /* decodeMode */, - CacheMode mode, - sv_samplerate_t targetRate, - bool normalised, - ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate, normalised), - m_source(source), - m_path(source.getLocalFilename()), - m_d(new D), - m_reporter(reporter), - m_cancelled(false), - m_completion(0), - m_decodeThread(0) -{ - SVDEBUG << "CoreAudioFileReader: local path: \"" << m_path << "\"" << endl; - - m_channelCount = 0; - m_fileRate = 0; - - m_d->buffer.mBuffers[0].mData = 0; - - Profiler profiler("CoreAudioFileReader::CoreAudioFileReader", true); - - QByteArray ba = m_path.toLocal8Bit(); - - CFURLRef url = CFURLCreateFromFileSystemRepresentation - (kCFAllocatorDefault, - (const UInt8 *)ba.data(), - (CFIndex)ba.length(), - false); - - //!!! how do we find out if the file open fails because of DRM protection? - -//#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040) -// FSRef fsref; -// if (!CFURLGetFSRef(url, &fsref)) { // returns Boolean, not error code -// m_error = "CoreAudioReadStream: Error looking up FS ref (file not found?)"; -// return; -// } -// m_d->err = ExtAudioFileOpen(&fsref, &m_d->file); -//#else - m_d->err = ExtAudioFileOpenURL(url, &m_d->file); -//#endif - - CFRelease(url); - - if (m_d->err) { - m_error = "CoreAudioReadStream: Error opening file: code " + codestr(m_d->err); - return; - } - if (!m_d->file) { - m_error = "CoreAudioReadStream: Failed to open file, but no error reported!"; - return; - } - - UInt32 propsize = sizeof(AudioStreamBasicDescription); - m_d->err = ExtAudioFileGetProperty - (m_d->file, kExtAudioFileProperty_FileDataFormat, &propsize, &m_d->asbd); - - if (m_d->err) { - m_error = "CoreAudioReadStream: Error in getting basic description: code " + codestr(m_d->err); - ExtAudioFileDispose(m_d->file); - return; - } - - m_channelCount = m_d->asbd.mChannelsPerFrame; - m_fileRate = m_d->asbd.mSampleRate; - - SVDEBUG << "CoreAudioFileReader: " << m_channelCount << " channels, " << m_fileRate << " Hz" << endl; - - m_d->asbd.mFormatID = kAudioFormatLinearPCM; - m_d->asbd.mFormatFlags = - kAudioFormatFlagIsFloat | - kAudioFormatFlagIsPacked | - kAudioFormatFlagsNativeEndian; - m_d->asbd.mBitsPerChannel = sizeof(float) * 8; - m_d->asbd.mBytesPerFrame = sizeof(float) * m_channelCount; - m_d->asbd.mBytesPerPacket = sizeof(float) * m_channelCount; - m_d->asbd.mFramesPerPacket = 1; - m_d->asbd.mReserved = 0; - - m_d->err = ExtAudioFileSetProperty - (m_d->file, kExtAudioFileProperty_ClientDataFormat, propsize, &m_d->asbd); - - if (m_d->err) { - m_error = "CoreAudioReadStream: Error in setting client format: code " + codestr(m_d->err); - ExtAudioFileDispose(m_d->file); - return; - } - - m_d->buffer.mNumberBuffers = 1; - m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount; - m_d->buffer.mBuffers[0].mDataByteSize = sizeof(float) * m_channelCount * m_d->blockSize; - m_d->buffer.mBuffers[0].mData = new float[m_channelCount * m_d->blockSize]; - - m_d->valid = true; - - initialiseDecodeCache(); - - if (m_reporter) { - connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled())); - m_reporter->setMessage - (tr("Decoding %1...").arg(QFileInfo(m_path).fileName())); - } - - while (1) { - - UInt32 framesRead = m_d->blockSize; - m_d->err = ExtAudioFileRead(m_d->file, &framesRead, &m_d->buffer); - - if (m_d->err) { - m_error = QString("Error in CoreAudio decoding: code %1") - .arg(m_d->err); - break; - } - - //!!! progress? - - // cerr << "Read " << framesRead << " frames (block size " << m_d->blockSize << ")" << endl; - - // buffers are interleaved unless specified otherwise - addSamplesToDecodeCache((float *)m_d->buffer.mBuffers[0].mData, framesRead); - - if ((int)framesRead < m_d->blockSize) break; - } - - finishDecodeCache(); - endSerialised(); - - m_completion = 100; -} - - -CoreAudioFileReader::~CoreAudioFileReader() -{ - SVDEBUG << "CoreAudioFileReader::~CoreAudioFileReader" << endl; - - if (m_d->valid) { - ExtAudioFileDispose(m_d->file); - delete[] (float *)(m_d->buffer.mBuffers[0].mData); - } - - delete m_d; -} - -void -CoreAudioFileReader::cancelled() -{ - m_cancelled = true; -} - -void -CoreAudioFileReader::getSupportedExtensions(std::set &extensions) -{ - extensions.insert("aiff"); - extensions.insert("aif"); - extensions.insert("au"); - extensions.insert("m4a"); - extensions.insert("m4b"); - extensions.insert("m4p"); - extensions.insert("mp3"); - extensions.insert("mp4"); - extensions.insert("wav"); -} - -bool -CoreAudioFileReader::supportsExtension(QString extension) -{ - std::set extensions; - getSupportedExtensions(extensions); - return (extensions.find(extension.toLower()) != extensions.end()); -} - -bool -CoreAudioFileReader::supportsContentType(QString type) -{ - return (type == "audio/x-aiff" || - type == "audio/x-wav" || - type == "audio/mpeg" || - type == "audio/basic" || - type == "audio/x-aac"); -} - -bool -CoreAudioFileReader::supports(FileSource &source) -{ - return (supportsExtension(source.getExtension()) || - supportsContentType(source.getContentType())); -} - -#endif - diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/CoreAudioFileReader.h --- a/data/fileio/CoreAudioFileReader.h Wed Jan 09 15:24:38 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006-2012 Chris Cannam and QMUL. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef SV_COREAUDIO_FILE_READER_H -#define SV_COREAUDIO_FILE_READER_H - -#ifdef HAVE_COREAUDIO - -#include "CodedAudioFileReader.h" - -#include "base/Thread.h" - -#include - -class ProgressReporter; - -class CoreAudioFileReader : public CodedAudioFileReader -{ - Q_OBJECT - -public: - CoreAudioFileReader(FileSource source, - DecodeMode decodeMode, - CacheMode cacheMode, - sv_samplerate_t targetRate = 0, - bool normalised = false, - ProgressReporter *reporter = nullptr); - virtual ~CoreAudioFileReader(); - - virtual QString getError() const { return m_error; } - virtual QString getLocation() const { return m_source.getLocation(); } - virtual QString getTitle() const { return m_title; } - - static void getSupportedExtensions(std::set &extensions); - static bool supportsExtension(QString ext); - static bool supportsContentType(QString type); - static bool supports(FileSource &source); - - virtual int getDecodeCompletion() const { return m_completion; } - - virtual bool isUpdating() const { - return m_decodeThread && m_decodeThread->isRunning(); - } - -public slots: - void cancelled(); - -protected: - FileSource m_source; - QString m_path; - QString m_error; - QString m_title; - - class D; - D *m_d; - - ProgressReporter *m_reporter; - bool m_cancelled; - int m_completion; - - class DecodeThread : public Thread - { - public: - // DecodeThread(QuickTimeFileReader *reader) : m_reader(reader) { } - virtual void run(); - - protected: - // QuickTimeFileReader *m_reader; - }; - - DecodeThread *m_decodeThread; -}; - -#endif - -#endif diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/DecodingWavFileReader.cpp --- a/data/fileio/DecodingWavFileReader.cpp Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/DecodingWavFileReader.cpp Wed Jan 23 14:43:43 2019 +0000 @@ -58,6 +58,9 @@ m_channelCount = m_original->getChannelCount(); m_fileRate = m_original->getSampleRate(); + m_title = m_original->getTitle(); + m_maker = m_original->getMaker(); + initialiseDecodeCache(); if (decodeMode == DecodeAtOnce) { diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/DecodingWavFileReader.h --- a/data/fileio/DecodingWavFileReader.h Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/DecodingWavFileReader.h Wed Jan 23 14:43:43 2019 +0000 @@ -37,8 +37,12 @@ ProgressReporter *reporter = 0); virtual ~DecodingWavFileReader(); + QString getTitle() const override { return m_title; } + QString getMaker() const override { return m_maker; } + QString getError() const override { return m_error; } QString getLocation() const override { return m_source.getLocation(); } + static void getSupportedExtensions(std::set &extensions); static bool supportsExtension(QString ext); static bool supportsContentType(QString type); @@ -55,6 +59,8 @@ protected: FileSource m_source; + QString m_title; + QString m_maker; QString m_path; QString m_error; bool m_cancelled; diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/OggVorbisFileReader.cpp --- a/data/fileio/OggVorbisFileReader.cpp Wed Jan 09 15:24:38 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,283 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifdef HAVE_OGGZ -#ifdef HAVE_FISHSOUND - -#include "OggVorbisFileReader.h" - -#include "base/ProgressReporter.h" -#include "base/Profiler.h" -#include "system/System.h" - -#include -#include -#include -#include - -#include - -//static int instances = 0; - -OggVorbisFileReader::OggVorbisFileReader(FileSource source, - DecodeMode decodeMode, - CacheMode mode, - sv_samplerate_t targetRate, - bool normalised, - ProgressReporter *reporter) : - CodedAudioFileReader(mode, targetRate, normalised), - m_source(source), - m_path(source.getLocalFilename()), - m_qfile(nullptr), - m_ffile(nullptr), - m_oggz(nullptr), - m_fishSound(nullptr), - m_reporter(reporter), - m_fileSize(0), - m_bytesRead(0), - m_commentsRead(false), - m_cancelled(false), - m_completion(0), - m_decodeThread(nullptr) -{ - SVDEBUG << "OggVorbisFileReader: local path: \"" << m_path - << "\", decode mode: " << decodeMode << " (" - << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded") - << ")" << endl; - - m_channelCount = 0; - m_fileRate = 0; - -// SVDEBUG << "OggVorbisFileReader::OggVorbisFileReader(" << m_path << "): now have " << (++instances) << " instances" << endl; - - Profiler profiler("OggVorbisFileReader::OggVorbisFileReader"); - - // These shenanigans are to avoid using oggz_open(..) with a local - // codepage on Windows (make sure proper filename encoding is used) - - m_qfile = new QFile(m_path); - if (!m_qfile->open(QIODevice::ReadOnly)) { - m_error = QString("Failed to open file %1 for reading.").arg(m_path); - SVDEBUG << "OggVorbisFileReader: " << m_error << endl; - delete m_qfile; - m_qfile = nullptr; - return; - } - - m_fileSize = m_qfile->size(); - - m_ffile = fdopen(dup(m_qfile->handle()), "rb"); - if (!m_ffile) { - m_error = QString("Failed to open file pointer for file %1").arg(m_path); - SVDEBUG << "OggVorbisFileReader: " << m_error << endl; - delete m_qfile; - m_qfile = nullptr; - return; - } - - if (!(m_oggz = oggz_open_stdio(m_ffile, OGGZ_READ))) { - m_error = QString("File %1 is not an OGG file.").arg(m_path); - fclose(m_ffile); - m_ffile = nullptr; - delete m_qfile; - m_qfile = nullptr; - return; - } - - FishSoundInfo fsinfo; - m_fishSound = fish_sound_new(FISH_SOUND_DECODE, &fsinfo); - - fish_sound_set_decoded_callback(m_fishSound, acceptFrames, this); - oggz_set_read_callback(m_oggz, -1, (OggzReadPacket)readPacket, this); - - if (decodeMode == DecodeAtOnce) { - - if (m_reporter) { - connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled())); - m_reporter->setMessage - (tr("Decoding %1...").arg(QFileInfo(m_path).fileName())); - } - - while (oggz_read(m_oggz, 1024) > 0); - - fish_sound_delete(m_fishSound); - m_fishSound = nullptr; - oggz_close(m_oggz); - m_oggz = nullptr; - - if (isDecodeCacheInitialised()) finishDecodeCache(); - endSerialised(); - - } else { - - if (m_reporter) m_reporter->setProgress(100); - - while (oggz_read(m_oggz, 1024) > 0 && - (m_channelCount == 0 || m_fileRate == 0 || m_sampleRate == 0)); - - if (m_channelCount > 0) { - m_decodeThread = new DecodeThread(this); - m_decodeThread->start(); - } - } -} - -OggVorbisFileReader::~OggVorbisFileReader() -{ -// SVDEBUG << "OggVorbisFileReader::~OggVorbisFileReader(" << m_path << "): now have " << (--instances) << " instances" << endl; - if (m_decodeThread) { - m_cancelled = true; - m_decodeThread->wait(); - delete m_decodeThread; - } - if (m_qfile) { - // don't fclose m_ffile; oggz_close did that - delete m_qfile; - m_qfile = nullptr; - } -} - -void -OggVorbisFileReader::cancelled() -{ - m_cancelled = true; -} - -void -OggVorbisFileReader::DecodeThread::run() -{ - if (m_reader->m_cacheMode == CacheInTemporaryFile) { - m_reader->m_completion = 1; - m_reader->startSerialised("OggVorbisFileReader::Decode"); - } - - while (oggz_read(m_reader->m_oggz, 1024) > 0); - - fish_sound_delete(m_reader->m_fishSound); - m_reader->m_fishSound = nullptr; - - oggz_close(m_reader->m_oggz); - m_reader->m_oggz = nullptr; - - // don't fclose m_ffile; oggz_close did that - - delete m_reader->m_qfile; - m_reader->m_qfile = nullptr; - - if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache(); - m_reader->m_completion = 100; - - m_reader->endSerialised(); -} - -int -OggVorbisFileReader::readPacket(OGGZ *, ogg_packet *packet, long, void *data) -{ - OggVorbisFileReader *reader = (OggVorbisFileReader *)data; - FishSound *fs = reader->m_fishSound; - - fish_sound_prepare_truncation(fs, packet->granulepos, int(packet->e_o_s)); - fish_sound_decode(fs, packet->packet, packet->bytes); - - reader->m_bytesRead += packet->bytes; - - // The number of bytes read by this function is smaller than - // the file size because of the packet headers - int p = int(lrint(double(reader->m_bytesRead) * 114 / - double(reader->m_fileSize))); - if (p > 99) p = 99; - reader->m_completion = p; - reader->progress(p); - - if (reader->m_fileSize > 0 && reader->m_reporter) { - reader->m_reporter->setProgress(p); - } - - if (reader->m_cancelled) return 1; - return 0; -} - -int -OggVorbisFileReader::acceptFrames(FishSound *fs, float **frames, long nframes, - void *data) -{ - OggVorbisFileReader *reader = (OggVorbisFileReader *)data; - - if (!reader->m_commentsRead) { - const FishSoundComment *comment; - comment = fish_sound_comment_first_byname(fs, (char *)"TITLE"); - if (comment && comment->value) { - reader->m_title = QString::fromUtf8(comment->value); - } - comment = fish_sound_comment_first_byname(fs, (char *)"ARTIST"); - if (comment && comment->value) { - reader->m_maker = QString::fromUtf8(comment->value); - } - comment = fish_sound_comment_first(fs); - while (comment) { - reader->m_tags[QString::fromUtf8(comment->name).toUpper()] = - QString::fromUtf8(comment->value); - comment = fish_sound_comment_next(fs, comment); - } - reader->m_commentsRead = true; - } - - if (reader->m_channelCount == 0) { - FishSoundInfo fsinfo; - fish_sound_command(fs, FISH_SOUND_GET_INFO, - &fsinfo, sizeof(FishSoundInfo)); - reader->m_fileRate = fsinfo.samplerate; - reader->m_channelCount = fsinfo.channels; - reader->initialiseDecodeCache(); - } - - if (nframes > 0) { - reader->addSamplesToDecodeCache(frames, nframes); - } - - if (reader->m_cancelled) return 1; - return 0; -} - -void -OggVorbisFileReader::getSupportedExtensions(std::set &extensions) -{ - extensions.insert("ogg"); - extensions.insert("oga"); -} - -bool -OggVorbisFileReader::supportsExtension(QString extension) -{ - std::set extensions; - getSupportedExtensions(extensions); - return (extensions.find(extension.toLower()) != extensions.end()); -} - -bool -OggVorbisFileReader::supportsContentType(QString type) -{ - return (type == "application/ogg"); -} - -bool -OggVorbisFileReader::supports(FileSource &source) -{ - return (supportsExtension(source.getExtension()) || - supportsContentType(source.getContentType())); -} - -#endif -#endif diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/OggVorbisFileReader.h --- a/data/fileio/OggVorbisFileReader.h Wed Jan 09 15:24:38 2019 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,106 +0,0 @@ -/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ - -/* - Sonic Visualiser - An audio file viewer and annotation editor. - Centre for Digital Music, Queen Mary, University of London. - This file copyright 2006 Chris Cannam. - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of the - License, or (at your option) any later version. See the file - COPYING included with this distribution for more information. -*/ - -#ifndef SV_OGG_VORBIS_FILE_READER_H -#define SV_OGG_VORBIS_FILE_READER_H - -#ifdef HAVE_OGGZ -#ifdef HAVE_FISHSOUND - -#include "CodedAudioFileReader.h" - -#include "base/Thread.h" -#include -#include - -#include - -#include - -class ProgressReporter; - -class OggVorbisFileReader : public CodedAudioFileReader -{ - Q_OBJECT - -public: - OggVorbisFileReader(FileSource source, - DecodeMode decodeMode, - CacheMode cacheMode, - sv_samplerate_t targetRate = 0, - bool normalised = false, - ProgressReporter *reporter = nullptr); - virtual ~OggVorbisFileReader(); - - QString getError() const override { return m_error; } - - QString getLocation() const override { return m_source.getLocation(); } - QString getTitle() const override { return m_title; } - QString getMaker() const override { return m_maker; } - TagMap getTags() const override { return m_tags; } - - static void getSupportedExtensions(std::set &extensions); - static bool supportsExtension(QString ext); - static bool supportsContentType(QString type); - static bool supports(FileSource &source); - - int getDecodeCompletion() const override { return m_completion; } - - bool isUpdating() const override { - return m_decodeThread && m_decodeThread->isRunning(); - } - -public slots: - void cancelled(); - -protected: - FileSource m_source; - QString m_path; - QString m_error; - QString m_title; - QString m_maker; - TagMap m_tags; - - QFile *m_qfile; - FILE *m_ffile; - OGGZ *m_oggz; - FishSound *m_fishSound; - ProgressReporter *m_reporter; - sv_frame_t m_fileSize; - sv_frame_t m_bytesRead; - bool m_commentsRead; - bool m_cancelled; - int m_completion; - - static int readPacket(OGGZ *, ogg_packet *, long, void *); - static int acceptFrames(FishSound *, float **, long, void *); - - class DecodeThread : public Thread - { - public: - DecodeThread(OggVorbisFileReader *reader) : m_reader(reader) { } - void run() override; - - protected: - OggVorbisFileReader *m_reader; - }; - - DecodeThread *m_decodeThread; -}; - -#endif -#endif - -#endif diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/WavFileReader.cpp --- a/data/fileio/WavFileReader.cpp Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/WavFileReader.cpp Wed Jan 23 14:43:43 2019 +0000 @@ -95,6 +95,15 @@ if (m_normalisation != Normalisation::None && !m_updating) { m_max = getMax(); } + + const char *str = sf_get_string(m_file, SF_STR_TITLE); + if (str) { + m_title = str; + } + str = sf_get_string(m_file, SF_STR_ARTIST); + if (str) { + m_maker = str; + } } SVDEBUG << "WavFileReader: Filename " << m_path << ", frame count " << m_frameCount << ", channel count " << m_channelCount << ", sample rate " << m_sampleRate << ", format " << m_fileInfo.format << ", seekable " << m_fileInfo.seekable << " adjusted to " << m_seekable << ", normalisation " << int(m_normalisation) << endl; diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/WavFileReader.h --- a/data/fileio/WavFileReader.h Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/WavFileReader.h Wed Jan 23 14:43:43 2019 +0000 @@ -51,6 +51,9 @@ QString getLocation() const override { return m_source.getLocation(); } QString getError() const override { return m_error; } + QString getTitle() const override { return m_title; } + QString getMaker() const override { return m_maker; } + QString getLocalFilename() const override { return m_path; } bool isQuicklySeekable() const override { return m_seekable; } @@ -80,6 +83,8 @@ FileSource m_source; QString m_path; QString m_error; + QString m_title; + QString m_maker; bool m_seekable; diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/test/AudioFileReaderTest.h --- a/data/fileio/test/AudioFileReaderTest.h Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/test/AudioFileReaderTest.h Wed Jan 23 14:43:43 2019 +0000 @@ -98,6 +98,11 @@ maxLimit = 0.1; rmsLimit = 0.03; + } else if (format == "opus") { + + maxLimit = 0.06; + rmsLimit = 0.015; + } else if (format == "aac") { // Terrible performance for this test, load of spill @@ -148,6 +153,11 @@ maxLimit = 0.06; rmsLimit = 0.03; + } else if (format == "opus") { + + maxLimit = 0.06; + rmsLimit = 0.015; + } else if (format == "aac") { maxLimit = 0.1; @@ -295,7 +305,8 @@ bool perceptual = (extension == "mp3" || extension == "aac" || - extension == "m4a"); + extension == "m4a" || + extension == "opus"); if (perceptual && !gapless) { // allow silence at start and end diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/test/EncodingTest.h --- a/data/fileio/test/EncodingTest.h Wed Jan 09 15:24:38 2019 +0000 +++ b/data/fileio/test/EncodingTest.h Wed Jan 23 14:43:43 2019 +0000 @@ -46,6 +46,22 @@ }; static const int mappingCount = 4; +#ifdef Q_OS_MAC +// see note in addAudioFiles below +static const char *testFiles[][2] = { + { "id3v2-iso-8859-1", "mp3" }, + { "id3v2-ucs-2", "mp3" }, + { utf8_name_tsprk, "flac" }, + { utf8_name_tsprk, "m4a" }, + { utf8_name_tsprk, "mp3" }, + { utf8_name_tsprk, "ogg" }, + { utf8_name_tsprk, "opus" }, + { utf8_name_sprkt, "mp3" }, + { utf8_name_sprkt, "ogg" }, +}; +static const int testFileCount = 8; +#endif + class EncodingTest : public QObject { Q_OBJECT @@ -71,11 +87,26 @@ } void addAudioFiles() { - QTest::addColumn("audiofile"); - QStringList files = QDir(encodingDir).entryList(QDir::Files); - foreach (QString filename, files) { - QTest::newRow(strOf(filename)) << filename; - } + QTest::addColumn("audiofile"); +#ifndef Q_OS_MAC + // The normal case - populate the file list from the files + // actually present in the encodings directory + QStringList files = QDir(encodingDir).entryList(QDir::Files); + foreach (QString filename, files) { + QTest::newRow(strOf(filename)) << filename; + } +#else + // Deviant case for Mac - populate the file list from the + // hard-coded list of expected files in testFiles. This is + // because QDir::entryList is currently broken on APFS (as of + // Qt 5.12) because of variant Unicode normalisations. + for (int i = 0; i < testFileCount; ++i) { + std::string s = testFiles[i][0]; + s += "."; + s += testFiles[i][1]; + QTest::newRow(strdup(s.c_str())) << QString::fromStdString(s); + } +#endif } private slots: @@ -84,7 +115,7 @@ if (!QDir(encodingDir).exists()) { SVCERR << "ERROR: Audio encoding file directory \"" << encodingDir << "\" does not exist" << endl; QVERIFY2(QDir(encodingDir).exists(), "Audio encoding file directory not found"); - } + } if (!QDir(outDir).exists() && !QDir().mkpath(outDir)) { SVCERR << "ERROR: Audio out directory \"" << outDir << "\" does not exist and could not be created" << endl; QVERIFY2(QDir(outDir).exists(), "Audio out directory not found and could not be created"); @@ -101,6 +132,15 @@ QFETCH(QString, audiofile); + if (!AudioFileReaderFactory::isSupported(encodingDir + "/" + + audiofile)) { +#if ( QT_VERSION >= 0x050000 ) + QSKIP("Known unsupported file, skipping"); +#else + QSKIP("Known unsupported file, skipping", SkipSingle); +#endif + } + AudioFileReaderFactory::Parameters params; AudioFileReader *reader = AudioFileReaderFactory::createReader @@ -128,7 +168,13 @@ AudioFileReaderFactory::createReader (encodingDir + "/" + audiofile, params); - QVERIFY(reader != nullptr); + if (!reader) { +#if ( QT_VERSION >= 0x050000 ) + QSKIP("Unsupported file, skipping"); +#else + QSKIP("Unsupported file, skipping", SkipSingle); +#endif + } QStringList fileAndExt = audiofile.split("."); QString file = fileAndExt[0]; @@ -142,11 +188,11 @@ } else { -#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND)) - if (extension == "ogg") { - QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata"); - } -#endif +//#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND)) +// if (extension == "ogg") { +// QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata"); +// } +//#endif auto blah = reader->getInterleavedFrames(0, 10); @@ -215,17 +261,24 @@ return; } -#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND)) - if (extension == "ogg") { - QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata"); - } -#endif +//#if (!defined (HAVE_OGGZ) || !defined(HAVE_FISHSOUND)) +// if (extension == "ogg") { +// QSKIP("Lack native Ogg Vorbis reader, so won't be getting metadata"); +// } +//#endif AudioFileReaderFactory::Parameters params; AudioFileReader *reader = AudioFileReaderFactory::createReader (encodingDir + "/" + audiofile, params); - QVERIFY(reader != nullptr); + + if (!reader) { +#if ( QT_VERSION >= 0x050000 ) + QSKIP("Unsupported file, skipping"); +#else + QSKIP("Unsupported file, skipping", SkipSingle); +#endif + } QString title = reader->getTitle(); QVERIFY(title != QString()); diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/test/audio/opus/48000-2.opus Binary file data/fileio/test/audio/opus/48000-2.opus has changed diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/test/encodings/Tëmple of Spörks.flac Binary file data/fileio/test/encodings/Tëmple of Spörks.flac has changed diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/test/encodings/Tëmple of Spörks.m4a Binary file data/fileio/test/encodings/Tëmple of Spörks.m4a has changed diff -r 841b2a3e606d -r ce185d4dd408 data/fileio/test/encodings/Tëmple of Spörks.opus Binary file data/fileio/test/encodings/Tëmple of Spörks.opus has changed diff -r 841b2a3e606d -r ce185d4dd408 files.pri --- a/files.pri Wed Jan 09 15:24:38 2019 +0000 +++ b/files.pri Wed Jan 23 14:43:43 2019 +0000 @@ -47,6 +47,7 @@ data/fileio/AudioFileReader.h \ data/fileio/AudioFileReaderFactory.h \ data/fileio/AudioFileSizeEstimator.h \ + data/fileio/BQAFileReader.h \ data/fileio/BZipFileDevice.h \ data/fileio/CachedFile.h \ data/fileio/CodedAudioFileReader.h \ @@ -62,9 +63,7 @@ data/fileio/MIDIFileReader.h \ data/fileio/MIDIFileWriter.h \ data/fileio/MP3FileReader.h \ - data/fileio/OggVorbisFileReader.h \ data/fileio/PlaylistFileReader.h \ - data/fileio/CoreAudioFileReader.h \ data/fileio/DecodingWavFileReader.h \ data/fileio/WavFileReader.h \ data/fileio/WavFileWriter.h \ @@ -182,6 +181,7 @@ data/fileio/AudioFileReader.cpp \ data/fileio/AudioFileReaderFactory.cpp \ data/fileio/AudioFileSizeEstimator.cpp \ + data/fileio/BQAFileReader.cpp \ data/fileio/BZipFileDevice.cpp \ data/fileio/CachedFile.cpp \ data/fileio/CodedAudioFileReader.cpp \ @@ -194,9 +194,7 @@ data/fileio/MIDIFileReader.cpp \ data/fileio/MIDIFileWriter.cpp \ data/fileio/MP3FileReader.cpp \ - data/fileio/OggVorbisFileReader.cpp \ data/fileio/PlaylistFileReader.cpp \ - data/fileio/CoreAudioFileReader.cpp \ data/fileio/DecodingWavFileReader.cpp \ data/fileio/WavFileReader.cpp \ data/fileio/WavFileWriter.cpp \