diff options
author | IOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org> | 2019-07-15 20:43:29 +0200 |
---|---|---|
committer | IOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org> | 2019-07-15 20:43:29 +0200 |
commit | 8c5b9f24a637ebe4f79883ed32c7ba27b8ab9e0c (patch) | |
tree | 46d4084e0ae1aa35fe6a68c98c739bfdb33dfd5c /bqaudiostream | |
parent | c1c08a7a391491913e4a10c3c55673bd81129cf7 (diff) |
New upstream version 3.3
Diffstat (limited to 'bqaudiostream')
56 files changed, 4497 insertions, 0 deletions
diff --git a/bqaudiostream/.hgignore b/bqaudiostream/.hgignore new file mode 100644 index 0000000..5c8de1b --- /dev/null +++ b/bqaudiostream/.hgignore @@ -0,0 +1,7 @@ +syntax: glob +*.o +moc_* +*~ +*.a +*.bak +*.orig diff --git a/bqaudiostream/.hgtags b/bqaudiostream/.hgtags new file mode 100644 index 0000000..53a3f50 --- /dev/null +++ b/bqaudiostream/.hgtags @@ -0,0 +1,12 @@ +3428bcfe3072558f043ad307923272bf37a7daed rbap_1.8_osx +3428bcfe3072558f043ad307923272bf37a7daed rbap_1.8_osx_appstore +4462bc173c7176ce8e67a2f441be169f1de52bba rbap_1.2_osx +4462bc173c7176ce8e67a2f441be169f1de52bba rubberband_v1.6 +81492626af635ca239cca662e8e53262a5f59e08 rbap_1.8.1_win32 +81492626af635ca239cca662e8e53262a5f59e08 rbap_1.8.5_win32 +b6c53b11ce9a4831a5f2796cb0273d08ef3cbfdc rbap_1.8_win32_appup_0 +b6c53b11ce9a4831a5f2796cb0273d08ef3cbfdc rbap_1.8_win32_appup_1 +f5ce5b3e869b74ad18181a660cc42d16500b2854 rbap_1.7_osx +f5ce5b3e869b74ad18181a660cc42d16500b2854 rubberband_v1.7 +fc167e3fc726f47d77cc73c11631e6c92e454676 rbap_1.2.1_osx +fc167e3fc726f47d77cc73c11631e6c92e454676 rbap_1.2.1_osx_appstore diff --git a/bqaudiostream/COPYING b/bqaudiostream/COPYING new file mode 100644 index 0000000..cb112c0 --- /dev/null +++ b/bqaudiostream/COPYING @@ -0,0 +1,26 @@ + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. diff --git a/bqaudiostream/Makefile b/bqaudiostream/Makefile new file mode 100644 index 0000000..e87eb78 --- /dev/null +++ b/bqaudiostream/Makefile @@ -0,0 +1,50 @@ + +SOURCES := src/AudioReadStream.cpp src/AudioReadStreamFactory.cpp src/AudioWriteStreamFactory.cpp src/AudioStreamExceptions.cpp +HEADERS := $(wildcard src/*.h) $(wildcard bqaudiostream/*.h) +OBJECTS := $(patsubst %.cpp,%.o,$(SOURCES)) +LIBRARY := libbqaudiostream.a + +CXXFLAGS := -std=c++98 -DHAVE_LIBSNDFILE -DHAVE_OGGZ -DHAVE_FISHSOUND -I../bqvec -I../bqthingfactory -I../bqresample -I./bqaudiostream -fpic + +all: $(LIBRARY) + +$(LIBRARY): $(OBJECTS) + ar cr $@ $^ + +clean: + rm -f $(OBJECTS) + +distclean: clean + rm -f $(LIBRARY) + +depend: + makedepend -Y -fMakefile -I./bqaudiostream $(SOURCES) $(HEADERS) + + +# DO NOT DELETE + +src/AudioReadStream.o: ./bqaudiostream/AudioReadStream.h +src/AudioReadStreamFactory.o: ./bqaudiostream/AudioReadStreamFactory.h +src/AudioReadStreamFactory.o: ./bqaudiostream/AudioReadStream.h +src/AudioReadStreamFactory.o: ./bqaudiostream/Exceptions.h +src/AudioReadStreamFactory.o: src/WavFileReadStream.cpp +src/AudioReadStreamFactory.o: src/OggVorbisReadStream.cpp +src/AudioReadStreamFactory.o: src/MediaFoundationReadStream.cpp +src/AudioReadStreamFactory.o: src/CoreAudioReadStream.cpp +src/AudioWriteStreamFactory.o: ./bqaudiostream/AudioWriteStreamFactory.h +src/AudioWriteStreamFactory.o: ./bqaudiostream/AudioWriteStream.h +src/AudioWriteStreamFactory.o: ./bqaudiostream/Exceptions.h +src/AudioWriteStreamFactory.o: ./bqaudiostream/AudioReadStreamFactory.h +src/AudioWriteStreamFactory.o: src/WavFileWriteStream.cpp +src/AudioWriteStreamFactory.o: src/SimpleWavFileWriteStream.cpp +src/AudioWriteStreamFactory.o: src/SimpleWavFileWriteStream.h +src/AudioWriteStreamFactory.o: src/CoreAudioWriteStream.cpp +src/AudioWriteStreamFactory.o: src/CoreAudioWriteStream.h +src/Exceptions.o: ./bqaudiostream/Exceptions.h +src/MediaFoundationReadStream.o: ./bqaudiostream/AudioReadStream.h +src/WavFileWriteStream.o: ./bqaudiostream/AudioWriteStream.h +src/SimpleWavFileWriteStream.o: ./bqaudiostream/AudioWriteStream.h +src/WavFileReadStream.o: ./bqaudiostream/AudioReadStream.h +src/CoreAudioWriteStream.o: ./bqaudiostream/AudioWriteStream.h +src/CoreAudioReadStream.o: ./bqaudiostream/AudioReadStream.h +src/OggVorbisReadStream.o: ./bqaudiostream/AudioReadStream.h diff --git a/bqaudiostream/README.txt b/bqaudiostream/README.txt new file mode 100644 index 0000000..d8e1eb2 --- /dev/null +++ b/bqaudiostream/README.txt @@ -0,0 +1,14 @@ + +bqaudiostream +============= + +A small library wrapping various audio file read/write implementations +in C++. + +Status: A bit messy. + +C++ standard required: C++98 (does not use C++11) + +Copyright 2007-2019 Particular Programs Ltd. Under a permissive +BSD/MIT-style licence: see the file COPYING for details. + diff --git a/bqaudiostream/bqaudiostream/AudioReadStream.h b/bqaudiostream/bqaudiostream/AudioReadStream.h new file mode 100644 index 0000000..d0f1711 --- /dev/null +++ b/bqaudiostream/bqaudiostream/AudioReadStream.h @@ -0,0 +1,110 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_AUDIO_READ_STREAM_H +#define BQ_AUDIO_READ_STREAM_H + +#include <bqthingfactory/ThingFactory.h> +#include <bqvec/RingBuffer.h> + +#include <string> +#include <vector> + +namespace breakfastquay { + +class Resampler; + +/* Not thread-safe -- one per thread please. */ + +class AudioReadStream +{ +public: + virtual ~AudioReadStream(); + + virtual std::string getError() const { return ""; } + + size_t getChannelCount() const { return m_channelCount; } + size_t getSampleRate() const { return m_sampleRate; } // source stream rate + + void setRetrievalSampleRate(size_t); + size_t getRetrievalSampleRate() const; + + virtual std::string getTrackName() const = 0; + virtual std::string getArtistName() const = 0; + + /** + * Retrieve \count frames of audio data (that is, \count * + * getChannelCount() samples) from the source and store in + * \frames. Return the number of frames actually retrieved; this + * will differ from \count only when the end of stream is reached. + * The region pointed to by \frames must contain enough space for + * \count * getChannelCount() values. + * + * If a retrieval sample rate has been set, the audio will be + * resampled to that rate (and \count refers to the number of + * frames at the retrieval rate rather than the file's original + * rate). + * + * May throw InvalidFileFormat if decoding fails. + */ + size_t getInterleavedFrames(size_t count, float *frames); + +protected: + AudioReadStream(); + virtual size_t getFrames(size_t count, float *frames) = 0; + int getResampledChunk(int count, float *frames); + size_t m_channelCount; + size_t m_sampleRate; + size_t m_retrievalRate; + size_t m_totalFileFrames; + size_t m_totalRetrievedFrames; + Resampler *m_resampler; + RingBuffer<float> *m_resampleBuffer; +}; + +template <typename T> +class AudioReadStreamBuilder : + public ConcreteThingBuilder<T, AudioReadStream, std::string> +{ +public: + AudioReadStreamBuilder(std::string uri, std::vector<std::string> extensions) : + ConcreteThingBuilder<T, AudioReadStream, std::string>(uri, extensions) { +// std::cerr << "Registering stream builder: " << uri << std::endl; + } +}; + +} + +#endif diff --git a/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h b/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h new file mode 100644 index 0000000..280a983 --- /dev/null +++ b/bqaudiostream/bqaudiostream/AudioReadStreamFactory.h @@ -0,0 +1,93 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_AUDIO_READ_STREAM_FACTORY_H +#define BQ_AUDIO_READ_STREAM_FACTORY_H + +#include <string> +#include <vector> + +namespace breakfastquay { + +class AudioReadStream; + +class AudioReadStreamFactory +{ +public: + /** + * Create and return a read stream object for the given audio file + * name, if possible. The file name should be UTF-8 encoded. The + * audio format will be deduced from the file extension. + * + * May throw FileNotFound, FileOpenFailed, + * AudioReadStream::FileDRMProtected, InvalidFileFormat, + * FileOperationFailed, or UnknownFileType. + * + * This function never returns NULL; it will always throw an + * exception instead. (If there is simply no read stream + * registered for the file extension, it will throw + * UnknownFileType.) + */ + static AudioReadStream *createReadStream(std::string fileName); + + /** + * Return a list of the file extensions supported by registered + * readers (e.g. "wav", "aiff", "mp3"). + */ + static std::vector<std::string> getSupportedFileExtensions(); + + /** + * Return true if the extension of the given filename (e.g. "wav" + * extension for filename "A.WAV") is supported by a registered + * reader. + */ + static bool isExtensionSupportedFor(std::string fileName); + + /** + * Return a string containing the file extensions supported by + * registered readers, in a format suitable for use as a file + * dialog filter (e.g. "*.wav *.aiff *.mp3"). + */ + static std::string getFileFilter(); + + /** + * Return the extension of a given filename (e.g. "wav" for "A.WAV"). + */ + static std::string extensionOf(std::string fileName); +}; + +} + +#endif diff --git a/bqaudiostream/bqaudiostream/AudioWriteStream.h b/bqaudiostream/bqaudiostream/AudioWriteStream.h new file mode 100644 index 0000000..2288582 --- /dev/null +++ b/bqaudiostream/bqaudiostream/AudioWriteStream.h @@ -0,0 +1,98 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_AUDIO_FILE_WRITE_STREAM_H +#define BQ_AUDIO_FILE_WRITE_STREAM_H + +#include "bqthingfactory/ThingFactory.h" + +#include <string> + +namespace breakfastquay { + +/* Not thread-safe -- one per thread please. */ + +class AudioWriteStream +{ +public: + class Target { + public: + Target(std::string path, size_t channelCount, size_t sampleRate) : + m_path(path), m_channelCount(channelCount), m_sampleRate(sampleRate) + { } + + std::string getPath() const { return m_path; } + size_t getChannelCount() const { return m_channelCount; } + size_t getSampleRate() const { return m_sampleRate; } + + private: + std::string m_path; + size_t m_channelCount; + size_t m_sampleRate; + }; + + virtual ~AudioWriteStream() { } + + virtual std::string getError() const { return ""; } + + std::string getPath() const { return m_target.getPath(); } + size_t getChannelCount() const { return m_target.getChannelCount(); } + size_t getSampleRate() const { return m_target.getSampleRate(); } + + /** + * May throw FileOperationFailed if encoding fails. + */ + virtual void putInterleavedFrames(size_t frameCount, float *frames) = 0; + +protected: + AudioWriteStream(Target t) : m_target(t) { } + Target m_target; +}; + +template <typename T> +class AudioWriteStreamBuilder : +public ConcreteThingBuilder<T, AudioWriteStream, AudioWriteStream::Target> +{ +public: + AudioWriteStreamBuilder(std::string uri, std::vector<std::string> extensions) : + ConcreteThingBuilder<T, AudioWriteStream, AudioWriteStream::Target> + (uri, extensions) { +// std::cerr << "Registering stream builder: " << uri << std::endl; + } +}; + +} + +#endif diff --git a/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h b/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h new file mode 100644 index 0000000..90d5df5 --- /dev/null +++ b/bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h @@ -0,0 +1,97 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_AUDIO_WRITE_STREAM_FACTORY_H +#define BQ_AUDIO_WRITE_STREAM_FACTORY_H + +#include <string> +#include <vector> + +namespace breakfastquay { + +class AudioWriteStream; + +class AudioWriteStreamFactory +{ +public: + /** + * Create and return a write stream object for the given audio + * file name, if possible. The audio file format will be deduced + * from the file extension. If the file already exists, it will be + * silently overwritten. + * + * May throw FailedToWriteFile, FileOperationFailed, or + * UnknownFileType. + * + * This function never returns NULL; it will always throw an + * exception instead. (If there is simply no write stream + * registered for the file extension, it will throw + * UnknownFileType.) + */ + static AudioWriteStream *createWriteStream(std::string fileName, + size_t channelCount, + size_t sampleRate); + + static std::vector<std::string> getSupportedFileExtensions(); + + static bool isExtensionSupportedFor(std::string fileName); + + /** + * Return a "preferred" and definitely supported file extension + * for writing uncompressed audio files. + * + * Returns an empty string if no sufficiently mainstream + * uncompressed format is supported. + */ + static std::string getDefaultUncompressedFileExtension(); + + /** + * Return a "preferred" and definitely supported file extension + * for writing lossily compressed audio files. + * + * Returns an empty string if no sufficiently mainstream lossy + * format is supported. + */ + static std::string getDefaultLossyFileExtension(); + + /** + * Return the extension of a given filename (e.g. "wav" for "A.WAV"). + */ + static std::string extensionOf(std::string fileName); +}; + +} + +#endif diff --git a/bqaudiostream/bqaudiostream/Exceptions.h b/bqaudiostream/bqaudiostream/Exceptions.h new file mode 100644 index 0000000..c031d0e --- /dev/null +++ b/bqaudiostream/bqaudiostream/Exceptions.h @@ -0,0 +1,143 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQAUDIOSTREAM_EXCEPTIONS_H +#define BQAUDIOSTREAM_EXCEPTIONS_H + +#include <exception> +#include <string> + +namespace breakfastquay { + +/** + * Failed to open a file for reading, because the file did not exist. + */ +class FileNotFound : virtual public std::exception +{ +public: + FileNotFound(std::string file) throw(); + virtual ~FileNotFound() throw() { } + virtual const char *what() const throw() { return m_what.c_str(); } + +protected: + std::string m_file; + std::string m_what; +}; + +/** + * Failed to read a file, although the file existed. May mean we did + * not have read permission. + */ +class FileReadFailed : virtual public std::exception +{ +public: + FileReadFailed(std::string file) throw(); + virtual ~FileReadFailed() throw() { } + virtual const char *what() const throw() { return m_what.c_str(); } + +protected: + std::string m_file; + std::string m_what; +}; + +/** + * Failed to read a file because it did not seem to have the expected + * format or contained errors. + */ +class InvalidFileFormat : virtual public std::exception +{ +public: + InvalidFileFormat(std::string file, std::string how) throw(); + virtual ~InvalidFileFormat() throw() { } + virtual const char *what() const throw() { return m_what.c_str(); } + +protected: + std::string m_what; +}; + +/** + * Failed to read or write a file because we do not have a reader, + * writer, decoder, or encoder for the requested file type. + */ +class UnknownFileType : virtual public std::exception +{ +public: + UnknownFileType(std::string file) throw(); + virtual ~UnknownFileType() throw() { } + virtual const char *what() const throw() { return m_what.c_str(); } + +protected: + std::string m_what; +}; + +/** + * Failed to open a file for writing. Possibly the containing + * directory did not exist, or lacked write permission. + */ +class FailedToWriteFile : virtual public std::exception +{ +public: + FailedToWriteFile(std::string file) throw(); + virtual ~FailedToWriteFile() throw() { } + virtual const char *what() const throw() { return m_what.c_str(); } + +protected: + std::string m_file; + std::string m_what; +}; + +/** + * Failed to read, write, or manipulate a file in some way not + * adequately described by any of the other exception types. This may + * also indicate an internal error in an encoder or decoder library. + */ +class FileOperationFailed : virtual public std::exception +{ +public: + FileOperationFailed(std::string file, std::string operation) throw(); + FileOperationFailed(std::string file, std::string operation, + std::string explanation) throw(); + virtual ~FileOperationFailed() throw() { } + virtual const char *what() const throw() { return m_what.c_str(); } + +protected: + std::string m_file; + std::string m_operation; + std::string m_explanation; + std::string m_what; +}; + +} +#endif diff --git a/bqaudiostream/src/AudioReadStream.cpp b/bqaudiostream/src/AudioReadStream.cpp new file mode 100644 index 0000000..089ec8e --- /dev/null +++ b/bqaudiostream/src/AudioReadStream.cpp @@ -0,0 +1,197 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#include "AudioReadStream.h" + +#include "bqresample/Resampler.h" + +#include <cmath> + +using namespace std; + +namespace breakfastquay +{ + +AudioReadStream::AudioReadStream() : + m_channelCount(0), + m_sampleRate(0), + m_retrievalRate(0), + m_totalFileFrames(0), + m_totalRetrievedFrames(0), + m_resampler(0), + m_resampleBuffer(0) +{ +} + +AudioReadStream::~AudioReadStream() +{ + delete m_resampler; + delete m_resampleBuffer; +} + +void +AudioReadStream::setRetrievalSampleRate(size_t rate) +{ + static size_t max = 1536000; + if (rate > max) { + cerr << "WARNING: unsupported sample rate " << rate + << ", clamping to " << max << endl; + rate = max; + } + m_retrievalRate = rate; +} + +size_t +AudioReadStream::getRetrievalSampleRate() const +{ + if (m_retrievalRate == 0) return m_sampleRate; + else return m_retrievalRate; +} + +size_t +AudioReadStream::getInterleavedFrames(size_t count, float *frames) +{ + if (m_retrievalRate == 0 || + m_retrievalRate == m_sampleRate || + m_channelCount == 0) { + return getFrames(count, frames); + } + + // The resampler API works in ints - so we may have to do this in + // chunks if count is very large. But that's not an unreasonable + // way to do it anyway. + static size_t chunkSizeSamples = 1000000; + + size_t chunkFrames = chunkSizeSamples / m_channelCount; + size_t frameOffset = 0; + + while (frameOffset < count) { + + size_t n = count - frameOffset; + if (n > chunkFrames) n = chunkFrames; + + int framesObtained = getResampledChunk + (int(n), frames + m_channelCount * frameOffset); + + if (framesObtained <= 0) { + return frameOffset; + } + + frameOffset += size_t(framesObtained); + + if (size_t(framesObtained) < n) { + return frameOffset; + } + } + + return count; +} + +int +AudioReadStream::getResampledChunk(int frameCount, float *frames) +{ + int channels = int(m_channelCount); + + if (!m_resampler) { + Resampler::Parameters params; + params.quality = Resampler::FastestTolerable; + params.initialSampleRate = int(m_sampleRate); + m_resampler = new Resampler(params, channels); + m_resampleBuffer = new RingBuffer<float>(frameCount * channels); + } + + double ratio = double(m_retrievalRate) / double(m_sampleRate); + int fileFrames = int(ceil(frameCount / ratio)); + + float *in = allocate<float>(fileFrames * channels); + float *out = allocate<float>((frameCount + 1) * channels); + + int samples = frameCount * channels; + bool finished = false; + + while (m_resampleBuffer->getReadSpace() < samples) { + + if (finished) { + int zeros = samples - m_resampleBuffer->getReadSpace(); + if (m_resampleBuffer->getWriteSpace() < zeros) { + m_resampleBuffer = m_resampleBuffer->resized + (m_resampleBuffer->getSize() + samples); + } + m_resampleBuffer->zero(zeros); + continue; + } + + int fileFramesToGet = + int(ceil((samples - m_resampleBuffer->getReadSpace()) + / (channels * ratio))); + + int got = 0; + + if (!finished) { + got = int(getFrames(fileFramesToGet, in)); + m_totalFileFrames += got; + if (got < fileFramesToGet) { + finished = true; + } + } else { + v_zero(in, fileFramesToGet * channels); + got = fileFramesToGet; + } + + if (got > 0) { + int resampled = m_resampler->resampleInterleaved + (out, frameCount + 1, in, got, ratio, finished); + if (m_resampleBuffer->getWriteSpace() < resampled * channels) { + int resizeTo = (m_resampleBuffer->getSize() + + resampled * channels); + m_resampleBuffer = m_resampleBuffer->resized(resizeTo); + } + m_resampleBuffer->write(out, resampled * channels); + } + } + + deallocate(in); + deallocate(out); + + int toReturn = samples; + int available = int(double(m_totalFileFrames) * ratio - + double(m_totalRetrievedFrames)) * channels; + if (toReturn > available) toReturn = available; + int actual = m_resampleBuffer->read(frames, toReturn) / channels; + m_totalRetrievedFrames += actual; + return actual; +} + +} + diff --git a/bqaudiostream/src/AudioReadStreamFactory.cpp b/bqaudiostream/src/AudioReadStreamFactory.cpp new file mode 100644 index 0000000..f8b3595 --- /dev/null +++ b/bqaudiostream/src/AudioReadStreamFactory.cpp @@ -0,0 +1,131 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#include "AudioReadStreamFactory.h" +#include "AudioReadStream.h" +#include "Exceptions.h" + +#include <bqthingfactory/ThingFactory.h> + +#define DEBUG_AUDIO_READ_STREAM_FACTORY 1 + +using namespace std; + +namespace breakfastquay { + +typedef ThingFactory<AudioReadStream, string> +AudioReadStreamFactoryImpl; + +string +AudioReadStreamFactory::extensionOf(string audioFileName) +{ + string::size_type pos = audioFileName.rfind('.'); + if (pos == string::npos) return ""; + string ext; + for (string::size_type i = pos + 1; i < audioFileName.size(); ++i) { + ext += (char)tolower((unsigned char)audioFileName[i]); + } + return ext; +} + +AudioReadStream * +AudioReadStreamFactory::createReadStream(string audioFileName) +{ + string extension = extensionOf(audioFileName); + + AudioReadStreamFactoryImpl *f = AudioReadStreamFactoryImpl::getInstance(); + + // Earlier versions of this code would first try to use a reader + // that had actually registered an interest in this extension, + // then fall back (if that failed) to trying every reader in + // order. But we rely on extensions so much anyway, it's probably + // more predictable always to use only the reader that has + // registered the extension (if there is one). + + try { + AudioReadStream *stream = f->createFor(extension, audioFileName); + if (!stream) throw UnknownFileType(audioFileName); + return stream; + } catch (const UnknownTagException &) { + throw UnknownFileType(audioFileName); + } +} + +vector<string> +AudioReadStreamFactory::getSupportedFileExtensions() +{ + return AudioReadStreamFactoryImpl::getInstance()->getTags(); +} + +bool +AudioReadStreamFactory::isExtensionSupportedFor(string fileName) +{ + vector<string> supported = getSupportedFileExtensions(); + set<string> sset(supported.begin(), supported.end()); + return sset.find(extensionOf(fileName)) != sset.end(); +} + +string +AudioReadStreamFactory::getFileFilter() +{ + vector<string> extensions = getSupportedFileExtensions(); + string filter; + for (size_t i = 0; i < extensions.size(); ++i) { + string ext = extensions[i]; + if (filter != "") filter += " "; + filter += "*." + ext; + } + return filter; +} + +} + +// We rather eccentrically include the C++ files here, not the +// headers. This file actually doesn't need any includes in order to +// compile, but we are building it into a static archive, from which +// only those object files that are referenced in the code that uses +// the archive will be extracted for linkage. Since no code refers +// directly to the stream implementations (they are self-registering), +// this means they will not be linked in. So we include them directly +// into this object file instead, and it's not necessary to build them +// separately in the project. In each case the code is completely +// #ifdef'd out if the implementation is not selected, so there is no +// overhead. + +#include "WavFileReadStream.cpp" +#include "OggVorbisReadStream.cpp" +#include "MediaFoundationReadStream.cpp" +#include "CoreAudioReadStream.cpp" +#include "OpusReadStream.cpp" + diff --git a/bqaudiostream/src/AudioStreamExceptions.cpp b/bqaudiostream/src/AudioStreamExceptions.cpp new file mode 100644 index 0000000..fd04ecb --- /dev/null +++ b/bqaudiostream/src/AudioStreamExceptions.cpp @@ -0,0 +1,88 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#include "Exceptions.h" + +using std::string; + +namespace breakfastquay { + +FileNotFound::FileNotFound(string file) throw() : + m_file(file) +{ + m_what = "File \"" + file + "\" not found"; +} + +FailedToWriteFile::FailedToWriteFile(string file) throw() : + m_file(file) +{ + m_what = "Failed to write file \"" + file + "\""; +} + +FileReadFailed::FileReadFailed(string file) throw() : + m_file(file) +{ + m_what = "Failed to read file \"" + file + "\""; +} + +FileOperationFailed::FileOperationFailed(string file, string op) throw() : + m_file(file), + m_operation(op), + m_explanation("") +{ + m_what = "File operation \"" + op + "\" failed for file \"" + file + "\""; +} + +FileOperationFailed::FileOperationFailed(string file, string op, string exp) throw() : + m_file(file), + m_operation(op), + m_explanation(exp) +{ + m_what = "File operation \"" + op + "\" failed for file \"" + file + "\""; + if (m_explanation != "") m_what += ": " + m_explanation; +} + +InvalidFileFormat::InvalidFileFormat(string file, string how) throw() +{ + m_what = "Invalid file format for file \"" + file + "\": " + how; +} + +UnknownFileType::UnknownFileType(string file) throw() +{ + m_what = "Unknown file type for file \"" + file + "\""; +} + +} + diff --git a/bqaudiostream/src/AudioWriteStreamFactory.cpp b/bqaudiostream/src/AudioWriteStreamFactory.cpp new file mode 100644 index 0000000..4a88e53 --- /dev/null +++ b/bqaudiostream/src/AudioWriteStreamFactory.cpp @@ -0,0 +1,142 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#include "AudioWriteStreamFactory.h" +#include "AudioWriteStream.h" + +#include "bqthingfactory/ThingFactory.h" +#include "Exceptions.h" + +#include "AudioReadStreamFactory.h" + +using namespace std; + +namespace breakfastquay { + +typedef ThingFactory<AudioWriteStream, AudioWriteStream::Target> +AudioWriteStreamFactoryImpl; + +//template <> +//AudioWriteStreamFactoryImpl * +//AudioWriteStreamFactoryImpl::m_instance = 0; + +AudioWriteStream * +AudioWriteStreamFactory::createWriteStream(string audioFileName, + size_t channelCount, + size_t sampleRate) +{ + AudioWriteStream *s = 0; + + string extension = AudioReadStreamFactory::extensionOf(audioFileName); + + AudioWriteStream::Target target(audioFileName, channelCount, sampleRate); + + AudioWriteStreamFactoryImpl *f = AudioWriteStreamFactoryImpl::getInstance(); + + try { + AudioWriteStream *stream = f->createFor(extension, target); + if (!stream) throw UnknownFileType(audioFileName); + return stream; + } catch (UnknownTagException) { + throw UnknownFileType(audioFileName); + } +} + +vector<string> +AudioWriteStreamFactory::getSupportedFileExtensions() +{ + return AudioWriteStreamFactoryImpl::getInstance()->getTags(); +} + +string +AudioWriteStreamFactory::getDefaultUncompressedFileExtension() +{ + vector<string> candidates; + candidates.push_back("wav"); + candidates.push_back("aiff"); + vector<string> supported = getSupportedFileExtensions(); + set<string> sset(supported.begin(), supported.end()); + for (size_t i = 0; i < candidates.size(); ++i) { + if (sset.find(candidates[i]) != sset.end()) { + return candidates[i]; + } + } + return ""; +} + +string +AudioWriteStreamFactory::getDefaultLossyFileExtension() +{ + vector<string> candidates; + candidates.push_back("mp3"); + candidates.push_back("m4a"); + candidates.push_back("ogg"); + candidates.push_back("oga"); + vector<string> supported = getSupportedFileExtensions(); + set<string> sset(supported.begin(), supported.end()); + for (size_t i = 0; i < candidates.size(); ++i) { + if (sset.find(candidates[i]) != sset.end()) { + return candidates[i]; + } + } + return ""; +} + +bool +AudioWriteStreamFactory::isExtensionSupportedFor(string fileName) +{ + vector<string> supported = getSupportedFileExtensions(); + set<string> sset(supported.begin(), supported.end()); + string ext = AudioReadStreamFactory::extensionOf(fileName); + return sset.find(ext) != sset.end(); +} + +} + +// We rather eccentrically include the C++ files here, not the +// headers. This file actually doesn't need any includes in order to +// compile, but we are building it into a static archive, from which +// only those object files that are referenced in the code that uses +// the archive will be extracted for linkage. Since no code refers +// directly to the stream implementations (they are self-registering), +// this means they will not be linked in. So we include them directly +// into this object file instead, and it's not necessary to build them +// separately in the project. In each case the code is completely +// #ifdef'd out if the implementation is not selected, so there is no +// overhead. + +#include "WavFileWriteStream.cpp" +#include "SimpleWavFileWriteStream.cpp" +#include "CoreAudioWriteStream.cpp" + diff --git a/bqaudiostream/src/CoreAudioReadStream.cpp b/bqaudiostream/src/CoreAudioReadStream.cpp new file mode 100644 index 0000000..bcfe733 --- /dev/null +++ b/bqaudiostream/src/CoreAudioReadStream.cpp @@ -0,0 +1,273 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifdef HAVE_COREAUDIO + +// OS/X system headers don't cope with DEBUG +#ifdef DEBUG +#undef DEBUG +#endif + +#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) +#include <AudioToolbox/AudioToolbox.h> +#include <AudioToolbox/ExtendedAudioFile.h> +#else +#include "AudioToolbox.h" +#include "ExtendedAudioFile.h" +#endif + +#include "CoreAudioReadStream.h" + +#include <sstream> + +namespace breakfastquay +{ + +static vector<string> +getCoreAudioExtensions() +{ + vector<string> extensions; + extensions.push_back("aiff"); + extensions.push_back("aif"); + extensions.push_back("au"); + extensions.push_back("avi"); + extensions.push_back("m4a"); + extensions.push_back("m4b"); + extensions.push_back("m4p"); + extensions.push_back("m4v"); + extensions.push_back("mov"); + extensions.push_back("mp3"); + extensions.push_back("mp4"); + extensions.push_back("wav"); + return extensions; +} + +static +AudioReadStreamBuilder<CoreAudioReadStream> +coreaudiobuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/CoreAudioReadStream"), + getCoreAudioExtensions() + ); + +class CoreAudioReadStream::D +{ +public: + D() { } + + ExtAudioFileRef file; + AudioBufferList buffer; + OSStatus err; + AudioStreamBasicDescription asbd; +}; + +static string +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'; + ostringstream os; + os << err << " (" << text << ")"; + return os.str(); +} + +CoreAudioReadStream::CoreAudioReadStream(string path) : + m_path(path), + m_d(new D) +{ + m_channelCount = 0; + m_sampleRate = 0; + + CFURLRef url = CFURLCreateFromFileSystemRepresentation + (kCFAllocatorDefault, + (const UInt8 *)path.c_str(), + (CFIndex)path.size(), + false); + + UInt32 propsize; + OSStatus noncritical; + + m_d->err = ExtAudioFileOpenURL(url, &m_d->file); + + CFRelease(url); + + if (m_d->err == kAudio_FileNotFoundError) { + throw FileNotFound(m_path); + } + + if (m_d->err) { + m_error = "CoreAudioReadStream: Error opening file: code " + codestr(m_d->err); + throw InvalidFileFormat(path, "failed to open audio file"); + } + if (!m_d->file) { + m_error = "CoreAudioReadStream: Failed to open file, but no error reported!"; + throw InvalidFileFormat(path, "failed to open audio file"); + } + + // Retrieve metadata through the underlying AudioFile API if possible + + AudioFileID audioFile = 0; + propsize = sizeof(AudioFileID); + noncritical = ExtAudioFileGetProperty + (m_d->file, kExtAudioFileProperty_AudioFile, &propsize, &audioFile); + + if (noncritical == noErr) { + + CFDictionaryRef dict = nil; + UInt32 dataSize = sizeof(dict); + noncritical = AudioFileGetProperty + (audioFile, kAudioFilePropertyInfoDictionary, &dataSize, &dict); + + if (noncritical == noErr) { + + CFIndex count = CFDictionaryGetCount(dict); + const void **kk = new const void *[count]; + const void **vv = new const void *[count]; + CFDictionaryGetKeysAndValues(dict, kk, vv); + + int bufsize = 10240; + char *buffer = new char[bufsize]; + + for (int i = 0; i < count; ++i) { + if (CFGetTypeID(kk[i]) == CFStringGetTypeID() && + CFGetTypeID(vv[i]) == CFStringGetTypeID()) { + CFStringRef key = reinterpret_cast<CFStringRef>(kk[i]); + CFStringRef value = reinterpret_cast<CFStringRef>(vv[i]); + if (CFStringGetCString(key, buffer, bufsize, + kCFStringEncodingUTF8)) { + string kstr = buffer; + if (CFStringGetCString(value, buffer, bufsize, + kCFStringEncodingUTF8)) { + if (kstr == kAFInfoDictionary_Title) { + m_track = buffer; + } else if (kstr == kAFInfoDictionary_Artist) { + m_artist = buffer; + } + } + } + } + } + + delete[] buffer; + delete[] kk; + delete[] vv; + + CFRelease(dict); + } + } + + 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); + throw FileOperationFailed(m_path, "get basic description", codestr(m_d->err)); + } + + m_channelCount = m_d->asbd.mChannelsPerFrame; + m_sampleRate = m_d->asbd.mSampleRate; + + m_d->asbd.mSampleRate = getSampleRate(); + 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); + throw FileOperationFailed(m_path, "set client format", codestr(m_d->err)); + } + + m_d->buffer.mNumberBuffers = 1; + m_d->buffer.mBuffers[0].mNumberChannels = m_channelCount; + m_d->buffer.mBuffers[0].mDataByteSize = 0; + m_d->buffer.mBuffers[0].mData = 0; +} + +size_t +CoreAudioReadStream::getFrames(size_t count, float *frames) +{ + if (!m_channelCount) return 0; + if (count == 0) return 0; + + m_d->buffer.mBuffers[0].mDataByteSize = + sizeof(float) * m_channelCount * count; + + m_d->buffer.mBuffers[0].mData = frames; + + UInt32 framesRead = count; + + m_d->err = ExtAudioFileRead(m_d->file, &framesRead, &m_d->buffer); + if (m_d->err) { + m_error = "CoreAudioReadStream: Error in decoder: code " + codestr(m_d->err); + throw InvalidFileFormat(m_path, "error in decoder"); + } + + // cerr << "CoreAudioReadStream::getFrames: " << count << " frames requested across " << m_channelCount << " channel(s), " << framesRead << " frames actually read" << std::endl; + + return framesRead; +} + +CoreAudioReadStream::~CoreAudioReadStream() +{ +// cerr << "CoreAudioReadStream::~CoreAudioReadStream" << std::endl; + + if (m_channelCount) { + ExtAudioFileDispose(m_d->file); + } + + m_channelCount = 0; + + delete m_d; +} + +} + +#endif + diff --git a/bqaudiostream/src/CoreAudioReadStream.h b/bqaudiostream/src/CoreAudioReadStream.h new file mode 100644 index 0000000..ee505b9 --- /dev/null +++ b/bqaudiostream/src/CoreAudioReadStream.h @@ -0,0 +1,72 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_CORE_AUDIO_READ_STREAM_H_ +#define BQ_CORE_AUDIO_READ_STREAM_H_ + +#include "AudioReadStream.h" + +#ifdef HAVE_COREAUDIO + +namespace breakfastquay +{ + +class CoreAudioReadStream : public AudioReadStream +{ +public: + CoreAudioReadStream(std::string path); + virtual ~CoreAudioReadStream(); + + virtual std::string getTrackName() const { return m_track; } + virtual std::string getArtistName() const { return m_artist; } + + virtual std::string getError() const { return m_error; } + +protected: + virtual size_t getFrames(size_t count, float *frames); + + std::string m_path; + std::string m_error; + std::string m_track; + std::string m_artist; + + class D; + D *m_d; +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/src/CoreAudioWriteStream.cpp b/bqaudiostream/src/CoreAudioWriteStream.cpp new file mode 100644 index 0000000..d019b83 --- /dev/null +++ b/bqaudiostream/src/CoreAudioWriteStream.cpp @@ -0,0 +1,255 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#include "CoreAudioWriteStream.h" + +#ifdef HAVE_COREAUDIO + +// OS/X system headers don't cope with DEBUG +#ifdef DEBUG +#undef DEBUG +#endif + +#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) +#include <AudioToolbox/AudioToolbox.h> +#include <AudioToolbox/ExtendedAudioFile.h> +#else +#include "AudioToolbox.h" +#include "ExtendedAudioFile.h" +#endif + +#include <iostream> + +#include <QDir> + +using namespace std; + +namespace breakfastquay +{ + +static +AudioWriteStreamBuilder<CoreAudioWriteStream> +coreaudiowritebuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/CoreAudioWriteStream"), + vector<string>() << "m4a" + ); + +class CoreAudioWriteStream::D +{ +public: + D() : file(0) { } + + AudioStreamBasicDescription asbd; + ExtAudioFileRef file; + AudioBufferList buffer; + OSStatus err; +}; + +static string +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 string("%1 (%2)").arg(err).arg(QString::fromLatin1(text)); +} + +CoreAudioWriteStream::CoreAudioWriteStream(Target target) : + AudioWriteStream(target), + m_d(new D) +{ + cerr << "CoreAudioWriteStream: file is " << getPath() << ", channel count is " << getChannelCount() << ", sample rate " << getSampleRate() << endl; + + UInt32 propsize = sizeof(AudioStreamBasicDescription); + + memset(&m_d->asbd, 0, sizeof(AudioStreamBasicDescription)); + m_d->asbd.mFormatID = kAudioFormatMPEG4AAC; + m_d->asbd.mSampleRate = getSampleRate(); + m_d->asbd.mChannelsPerFrame = getChannelCount(); + m_d->err = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, 0, + &propsize, &m_d->asbd); + +#if (MACOSX_DEPLOYMENT_TARGET <= 1040 && MAC_OS_X_VERSION_MIN_REQUIRED <= 1040) + + // Unlike ExtAudioFileCreateWithURL, ExtAudioFileCreateNew + // apparently cannot be told to overwrite an existing file + QFile qfi(getPath()); + if (qfi.exists()) qfi.remove(); + + QDir dir = QFileInfo(getPath()).dir(); + QByteArray dba = dir.absolutePath().toLocal8Bit(); + + CFURLRef durl = CFURLCreateFromFileSystemRepresentation + (kCFAllocatorDefault, + (const UInt8 *)dba.data(), + (CFIndex)dba.length(), + false); + + FSRef dref; + if (!CFURLGetFSRef(durl, &dref)) { // returns Boolean, not error code + m_error = "CoreAudioReadStream: Error looking up FS ref (directory not found?)"; + cerr << m_error << endl; + throw FailedToWriteFile(getPath()); + } + + QByteArray fba = QFileInfo(getPath()).fileName().toUtf8(); + CFStringRef filename = CFStringCreateWithBytes + (kCFAllocatorDefault, + (const UInt8 *)fba.data(), + (CFIndex)fba.length(), + kCFStringEncodingUTF8, + false); + + m_d->err = ExtAudioFileCreateNew + (&dref, + filename, + kAudioFileM4AType, + &m_d->asbd, + 0, + &m_d->file); + + CFRelease(durl); + CFRelease(filename); +#else + QByteArray ba = getPath().toLocal8Bit(); + + CFURLRef url = CFURLCreateFromFileSystemRepresentation + (kCFAllocatorDefault, + (const UInt8 *)ba.data(), + (CFIndex)ba.length(), + false); + + UInt32 flags = kAudioFileFlags_EraseFile; + + m_d->err = ExtAudioFileCreateWithURL + (url, + kAudioFileM4AType, + &m_d->asbd, + 0, + flags, + &m_d->file); + + CFRelease(url); +#endif + + if (m_d->err) { + m_error = "CoreAudioWriteStream: Failed to create file: code " + codestr(m_d->err); + cerr << m_error << endl; + throw FailedToWriteFile(getPath()); + } + + memset(&m_d->asbd, 0, sizeof(AudioStreamBasicDescription)); + propsize = sizeof(AudioStreamBasicDescription); + m_d->asbd.mSampleRate = getSampleRate(); + m_d->asbd.mFormatID = kAudioFormatLinearPCM; + m_d->asbd.mChannelsPerFrame = getChannelCount(); + m_d->asbd.mFormatFlags = + kAudioFormatFlagIsFloat | + kAudioFormatFlagIsPacked | + kAudioFormatFlagsNativeEndian; + m_d->asbd.mFramesPerPacket = 1; + m_d->asbd.mBitsPerChannel = sizeof(float) * 8; + m_d->asbd.mBytesPerFrame = sizeof(float) * getChannelCount(); + m_d->asbd.mBytesPerPacket = sizeof(float) * getChannelCount(); + +/* + cerr << "Client format contains:" << endl; + for (int i = 0; i < sizeof(AudioStreamBasicDescription); ++i) { + if (i % 8 == 0) cerr << endl; + cerr << int(((char *)(&m_d->asbd))[i]) << " "; + } + cerr << endl; +*/ + m_d->err = ExtAudioFileSetProperty + (m_d->file, kExtAudioFileProperty_ClientDataFormat, + sizeof(AudioStreamBasicDescription), &m_d->asbd); + + if (m_d->err) { + m_error = "CoreAudioWriteStream: Error in setting client format: code " + codestr(m_d->err); + cerr << m_error << endl; + ExtAudioFileDispose(m_d->file); + throw FileOperationFailed(getPath(), "set client format"); + } + + // Initialise writes + m_d->err = ExtAudioFileWriteAsync(m_d->file, 0, 0); + + if (m_d->err) { + m_error = "CoreAudioWriteStream: Error in initialising file writes: code " + codestr(m_d->err); + cerr << m_error << endl; + ExtAudioFileDispose(m_d->file); + throw FileOperationFailed(getPath(), "initialise file writes"); + } + + m_d->buffer.mNumberBuffers = 1; + m_d->buffer.mBuffers[0].mNumberChannels = getChannelCount(); + m_d->buffer.mBuffers[0].mDataByteSize = 0; + m_d->buffer.mBuffers[0].mData = 0; +} + +CoreAudioWriteStream::~CoreAudioWriteStream() +{ + if (m_d->file) { + cerr << "CoreAudioWriteStream::~CoreAudioWriteStream: disposing" << endl; + ExtAudioFileDispose(m_d->file); + } +} + +void +CoreAudioWriteStream::putInterleavedFrames(size_t count, float *frames) +{ + if (count == 0) return; + + m_d->buffer.mBuffers[0].mDataByteSize = + sizeof(float) * getChannelCount() * count; + + m_d->buffer.mBuffers[0].mData = frames; + + UInt32 framesToWrite = count; + + m_d->err = ExtAudioFileWrite(m_d->file, framesToWrite, &m_d->buffer); + if (m_d->err) { + m_error = "CoreAudioWriteStream: Error in encoder: code " + codestr(m_d->err); + cerr << m_error << endl; + throw FileOperationFailed(getPath(), "encode"); + } +} + +} + +#endif diff --git a/bqaudiostream/src/CoreAudioWriteStream.h b/bqaudiostream/src/CoreAudioWriteStream.h new file mode 100644 index 0000000..f43d97b --- /dev/null +++ b/bqaudiostream/src/CoreAudioWriteStream.h @@ -0,0 +1,66 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_CORE_AUDIO_WRITE_STREAM_H_ +#define BQ_CORE_AUDIO_WRITE_STREAM_H_ + +#include "AudioWriteStream.h" + +#ifdef HAVE_COREAUDIO + +namespace breakfastquay +{ + +class CoreAudioWriteStream : public AudioWriteStream +{ +public: + CoreAudioWriteStream(Target target); + virtual ~CoreAudioWriteStream(); + + virtual std::string getError() const { return m_error; } + + virtual void putInterleavedFrames(size_t count, float *frames); + +protected: + std::string m_error; + + class D; + D *m_d; +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/src/MediaFoundationReadStream.cpp b/bqaudiostream/src/MediaFoundationReadStream.cpp new file mode 100644 index 0000000..104b41f --- /dev/null +++ b/bqaudiostream/src/MediaFoundationReadStream.cpp @@ -0,0 +1,479 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifdef HAVE_MEDIAFOUNDATION + +#define WINVER 0x0601 // _WIN32_WINNT_WIN7, earliest version to define MF API + +#include "MediaFoundationReadStream.h" + +#include <windows.h> +#include <mfapi.h> +#include <mfidl.h> +#include <mfreadwrite.h> +#include <mferror.h> +#include <propvarutil.h> +#include <propkey.h> +#include <shobjidl_core.h> +#include <stdio.h> +#include <VersionHelpers.h> + +#include <iostream> +#include <algorithm> + +using namespace std; + +namespace breakfastquay +{ + +static vector<string> +getMediaFoundationExtensions() +{ + vector<string> extensions; + + extensions.push_back("mp3"); + extensions.push_back("wav"); + extensions.push_back("wma"); + extensions.push_back("avi"); + extensions.push_back("m4a"); + extensions.push_back("m4v"); + extensions.push_back("mov"); + extensions.push_back("mp4"); + extensions.push_back("aac"); + + return extensions; +} + +static +AudioReadStreamBuilder<MediaFoundationReadStream> +mediafoundationbuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/MediaFoundationReadStream"), + getMediaFoundationExtensions() + ); + + +static const int maxBufferSize = 1048575; + +class MediaFoundationReadStream::D +{ +public: + D(MediaFoundationReadStream *s) : + refCount(0), + stream(s), + channelCount(0), + bitDepth(0), + sampleRate(0), + isFloat(false), + reader(0), + mediaType(0), + mediaBuffer(0), + mediaBufferIndex(0), + err(S_OK), + complete(false) + { } + + ~D() { + if (mediaBuffer) { + mediaBuffer->Release(); + } + + if (reader) { + reader->Release(); + } + + if (mediaType) { + mediaType->Release(); + } + } + + ULONG APIENTRY AddRef() { + return ++refCount; + } + + ULONG APIENTRY Release() { + return --refCount; + } + + ULONG refCount; + + MediaFoundationReadStream *stream; + + int channelCount; + int bitDepth; + int sampleRate; + bool isFloat; + + IMFSourceReader *reader; + IMFMediaType *mediaType; + IMFMediaBuffer *mediaBuffer; + int mediaBufferIndex; + + HRESULT err; + + bool complete; + + string trackName; + string artistName; + + int getFrames(int count, float *frames); + void convertSamples(const unsigned char *in, int inbufsize, float *out); + float convertSample(const unsigned char *); + void fillBuffer(); +}; + +static string +wideStringToString(LPWSTR wstr) +{ + if (!wstr) return ""; + int wlen = wcslen(wstr); + int len = WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, 0, 0, 0, 0); + if (len < 0) return ""; + char *conv = new char[len + 1]; + (void)WideCharToMultiByte(CP_UTF8, 0, wstr, wlen, conv, len, 0, 0); + conv[len] = '\0'; + string s = string(conv, len); + delete[] conv; + return s; +} + +MediaFoundationReadStream::MediaFoundationReadStream(string path) : + m_path(path), + m_d(new D(this)) +{ + m_channelCount = 0; + m_sampleRate = 0; + + // References: + // http://msdn.microsoft.com/en-gb/library/windows/desktop/dd757929%28v=vs.85%29.aspx + + // Note: CoInitializeEx must already have been called + + IPropertyStore *store = NULL; + IMFMediaType *partialType = 0; + int wlen = 0; + wchar_t *wpath = NULL, *wfullpath = NULL; + string errorLocation; + + m_d->err = MFStartup(MF_VERSION); + if (FAILED(m_d->err)) { + m_error = "MediaFoundationReadStream: Failed to initialise MediaFoundation"; + errorLocation = "initialise"; + goto fail; + } + + wlen = MultiByteToWideChar(CP_UTF8, 0, path.c_str(), int(path.size()), 0, 0); + if (wlen < 0) { + m_error = "MediaFoundationReadStream: Failed to convert path to wide chars"; + errorLocation = "convert path"; + goto fail; + } + wpath = new wchar_t[wlen+1]; + (void)MultiByteToWideChar(CP_UTF8, 0, path.c_str(), int(path.size()), wpath, wlen); + wpath[wlen] = L'\0'; + + wfullpath = _wfullpath(NULL, wpath, 0); + + if (SUCCEEDED(SHGetPropertyStoreFromParsingName + (wfullpath, NULL, GPS_BESTEFFORT, IID_PPV_ARGS(&store)))) { + vector<wchar_t> buf(10000, L'\0'); + PROPVARIANT v; + if (SUCCEEDED(store->GetValue(PKEY_Title, &v)) && + SUCCEEDED(PropVariantToString(v, buf.data(), buf.size()-1))) { + m_d->trackName = wideStringToString(buf.data()); + } + if (SUCCEEDED(store->GetValue(PKEY_Music_Artist, &v)) && + SUCCEEDED(PropVariantToString(v, buf.data(), buf.size()-1))) { + m_d->artistName = wideStringToString(buf.data()); + } + store->Release(); + } + + m_d->err = MFCreateSourceReaderFromURL(wfullpath, NULL, &m_d->reader); + + delete[] wpath; + wpath = NULL; + + free(wfullpath); + wfullpath = NULL; + + if (FAILED(m_d->err)) { + m_error = "MediaFoundationReadStream: Failed to create source reader"; + errorLocation = "create source reader"; + goto fail; + } + + m_d->err = m_d->reader->SetStreamSelection + ((DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE); + if (SUCCEEDED(m_d->err)) { + m_d->err = m_d->reader->SetStreamSelection + ((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE); + } + if (FAILED(m_d->err)) { + m_error = "MediaFoundationReadStream: Failed to select first audio stream"; + errorLocation = "select stream"; + goto fail; + } + + // Create a partial media type that specifies uncompressed PCM audio + + m_d->err = MFCreateMediaType(&partialType); + if (SUCCEEDED(m_d->err)) { + m_d->err = partialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); + } + if (SUCCEEDED(m_d->err)) { + m_d->err = partialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); + } + if (SUCCEEDED(m_d->err)) { + m_d->err = m_d->reader->SetCurrentMediaType + ((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, partialType); + } + if (SUCCEEDED(m_d->err)) { + m_d->err = m_d->reader->GetCurrentMediaType + ((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, &m_d->mediaType); + } + if (SUCCEEDED(m_d->err)) { + // surely this is redundant, as we did it already? but they do + // it twice in the MS example... well, presumably no harm anyway + m_d->err = m_d->reader->SetStreamSelection + ((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE); + } + if (partialType) { + partialType->Release(); + partialType = 0; + } + + if (SUCCEEDED(m_d->err)) { + UINT32 depth; + m_d->err = m_d->mediaType->GetUINT32 + (MF_MT_AUDIO_BITS_PER_SAMPLE, &depth); + m_d->bitDepth = depth; + } + + if (SUCCEEDED(m_d->err)) { + UINT32 rate; + m_d->err = m_d->mediaType->GetUINT32 + (MF_MT_AUDIO_SAMPLES_PER_SECOND, &rate); + m_sampleRate = m_d->sampleRate = rate; + } + + if (SUCCEEDED(m_d->err)) { + UINT32 chans; + m_d->err = m_d->mediaType->GetUINT32 + (MF_MT_AUDIO_NUM_CHANNELS, &chans); + m_channelCount = m_d->channelCount = chans; + } + + if (FAILED(m_d->err)) { + m_error = "MediaFoundationReadStream: File format could not be converted to PCM stream"; + errorLocation = "media type selection"; + goto fail; + } + + return; + +fail: + delete m_d; + MFShutdown(); + throw FileOperationFailed(m_path, string("MediaFoundation ") + errorLocation); +} + +MediaFoundationReadStream::~MediaFoundationReadStream() +{ + delete m_d; + MFShutdown(); // "Call this function once for every call to MFStartup" + // - i.e. they are allowed to nest +} + +size_t +MediaFoundationReadStream::getFrames(size_t count, float *frames) +{ + return m_d->getFrames(int(count), frames); +} + +string +MediaFoundationReadStream::getTrackName() const +{ + return m_d->trackName; +} + +string +MediaFoundationReadStream::getArtistName() const +{ + return m_d->artistName; +} + +int +MediaFoundationReadStream::D::getFrames(int framesRequired, float *frames) +{ + if (!mediaBuffer) { + fillBuffer(); + if (!mediaBuffer) { + // legitimate end of file + return 0; + } + } + + BYTE *data = 0; + DWORD length = 0; + + err = mediaBuffer->Lock(&data, 0, &length); + + if (FAILED(err)) { + stream->m_error = "MediaFoundationReadStream: Failed to lock media buffer?!"; + throw FileOperationFailed(stream->m_path, "Read from audio file"); + } + + int bytesPerFrame = channelCount * (bitDepth / 8); + int framesAvailable = (length - mediaBufferIndex) / bytesPerFrame; + int framesToGet = std::min(framesRequired, framesAvailable); + + if (framesToGet > 0) { + // have something in the buffer, not necessarily all we need + convertSamples(data + mediaBufferIndex, + framesToGet * bytesPerFrame, + frames); + mediaBufferIndex += framesToGet * bytesPerFrame; + } + + mediaBuffer->Unlock(); + + if (framesToGet == framesRequired) { + // we got enough! rah + return framesToGet; + } + + // otherwise, we ran out: this buffer has nothing left, release it + // and call again for the next part + + mediaBuffer->Release(); + mediaBuffer = 0; + mediaBufferIndex = 0; + + return framesToGet + + getFrames(framesRequired - framesToGet, + frames + framesToGet * channelCount); +} + +void +MediaFoundationReadStream::D::fillBuffer() +{ + // assumes mediaBuffer is currently null + + DWORD flags = 0; + IMFSample *sample = 0; + + while (!sample) { + + // "In some cases a call to ReadSample does not generate data, + // in which case the IMFSample pointer is NULL" (hence the loop) + + err = reader->ReadSample((DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, + 0, 0, &flags, 0, &sample); + + if (FAILED(err)) { + stream->m_error = "MediaFoundationReadStream: Failed to read sample from stream"; + throw FileOperationFailed(stream->m_path, "Read from audio file"); + } + + if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { + return; + } + } + + err = sample->ConvertToContiguousBuffer(&mediaBuffer); + if (FAILED(err)) { + stream->m_error = "MediaFoundationReadStream: Failed to convert sample to buffer"; + throw FileOperationFailed(stream->m_path, "Read from audio file"); + } +} + +void +MediaFoundationReadStream::D::convertSamples(const unsigned char *inbuf, + int inbufbytes, + float *out) +{ + int inix = 0; + int bytesPerSample = bitDepth / 8; + while (inix < inbufbytes) { + *out = convertSample(inbuf + inix); + out += 1; + inix += bytesPerSample; + } +} + +float +MediaFoundationReadStream::D::convertSample(const unsigned char *c) +{ + if (isFloat) { + return *(float *)c; + } + + switch (bitDepth) { + + case 8: { + // WAV stores 8-bit samples unsigned, other sizes signed. + return (float)(c[0] - 128.0) / 128.0; + } + + case 16: { + // Two's complement little-endian 16-bit integer. + // We convert endianness (if necessary) but assume 16-bit short. + unsigned char b2 = c[0]; + unsigned char b1 = c[1]; + unsigned int bits = (b1 << 8) + b2; + return float(double(short(bits)) / 32768.0); + } + + case 24: { + // Two's complement little-endian 24-bit integer. + // Again, convert endianness but assume 32-bit int. + unsigned char b3 = c[0]; + unsigned char b2 = c[1]; + unsigned char b1 = c[2]; + // Rotate 8 bits too far in order to get the sign bit + // in the right place; this gives us a 32-bit value, + // hence the larger float divisor + unsigned int bits = (b1 << 24) + (b2 << 16) + (b3 << 8); + return float(double(int(bits)) / 2147483648.0); + } + + default: + return 0.0f; + } +} + +} + +#endif + diff --git a/bqaudiostream/src/MediaFoundationReadStream.h b/bqaudiostream/src/MediaFoundationReadStream.h new file mode 100644 index 0000000..6aedf26 --- /dev/null +++ b/bqaudiostream/src/MediaFoundationReadStream.h @@ -0,0 +1,70 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef MEDIAFOUNDATION_READ_STREAM_H +#define MEDIAFOUNDATION_READ_STREAM_H + +#include "AudioReadStream.h" + +#ifdef HAVE_MEDIAFOUNDATION + +namespace breakfastquay +{ + +class MediaFoundationReadStream : public AudioReadStream +{ +public: + MediaFoundationReadStream(std::string path); + virtual ~MediaFoundationReadStream(); + + virtual std::string getTrackName() const; + virtual std::string getArtistName() const; + + virtual std::string getError() const { return m_error; } + +protected: + virtual size_t getFrames(size_t count, float *frames); + + std::string m_path; + std::string m_error; + + class D; + D *m_d; +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/src/OggVorbisReadStream.cpp b/bqaudiostream/src/OggVorbisReadStream.cpp new file mode 100644 index 0000000..c410646 --- /dev/null +++ b/bqaudiostream/src/OggVorbisReadStream.cpp @@ -0,0 +1,250 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + +#include "OggVorbisReadStream.h" + +#include <bqvec/RingBuffer.h> + +#include <oggz/oggz.h> +#include <fishsound/fishsound.h> + +#ifndef __GNUC__ +#include <alloca.h> +#endif + +namespace breakfastquay +{ + +static vector<string> +getOggExtensions() +{ + vector<string> extensions; + extensions.push_back("ogg"); + extensions.push_back("oga"); + return extensions; +} + +static +AudioReadStreamBuilder<OggVorbisReadStream> +oggbuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/OggVorbisReadStream"), + getOggExtensions() + ); + +class OggVorbisReadStream::D +{ +public: + D(OggVorbisReadStream *rs) : + m_rs(rs), + m_oggz(0), + m_fishSound(0), + m_buffer(0), + m_namesRead(false), + m_finished(false) { } + ~D() { + if (m_fishSound) fish_sound_delete(m_fishSound); + if (m_oggz) oggz_close(m_oggz); + delete m_buffer; + } + + OggVorbisReadStream *m_rs; + OGGZ *m_oggz; + FishSound *m_fishSound; + RingBuffer<float> *m_buffer; + bool m_namesRead; + bool m_finished; + + string m_trackName; + string m_artistName; + + bool isFinished() const { + return m_finished; + } + + string getTrackName() const { + return m_trackName; + } + + string getArtistName() const { + return m_artistName; + } + + int getAvailableFrameCount() const { + if (!m_buffer) return 0; + return m_buffer->getReadSpace() / int(m_rs->getChannelCount()); + } + + void readNextBlock() { + if (m_finished) return; + if (oggz_read(m_oggz, 1024) <= 0) { + m_finished = true; + } + } + + void sizeBuffer(int minFrames) { + int samples = minFrames * int(m_rs->getChannelCount()); + if (!m_buffer) { + m_buffer = new RingBuffer<float>(samples); + } else if (m_buffer->getSize() < samples) { + m_buffer = m_buffer->resized(samples); + } + } + + int acceptPacket(ogg_packet *p) { + fish_sound_prepare_truncation + (m_fishSound, p->granulepos, int(p->e_o_s)); + fish_sound_decode(m_fishSound, p->packet, p->bytes); + return 0; + } + + int acceptFrames(float **frames, long n) { + + if (!m_namesRead) { + const FishSoundComment *c; + c = fish_sound_comment_first_byname(m_fishSound, (char *)"TITLE"); + if (c && c->value) { + m_trackName = string(c->value); + } + c = fish_sound_comment_first_byname(m_fishSound, (char *)"ARTIST"); + if (c && c->value) { + m_artistName = string(c->value); + } + m_namesRead = true; + } + + if (m_rs->getChannelCount() == 0) { + FishSoundInfo fsinfo; + fish_sound_command(m_fishSound, FISH_SOUND_GET_INFO, + &fsinfo, sizeof(FishSoundInfo)); + m_rs->m_channelCount = fsinfo.channels; + m_rs->m_sampleRate = fsinfo.samplerate; + } + + sizeBuffer(getAvailableFrameCount() + int(n)); + int channels = int(m_rs->getChannelCount()); + float *interleaved = allocate<float>(n * channels); + v_interleave(interleaved, frames, channels, int(n)); + m_buffer->write(interleaved, int(n) * channels); + deallocate(interleaved); + return 0; + } + + static int acceptPacketStatic(OGGZ *, ogg_packet *packet, long, void *data) { + D *d = (D *)data; + return d->acceptPacket(packet); + } + + static int acceptFramesStatic(FishSound *, float **frames, long n, void *data) { + D *d = (D *)data; + return d->acceptFrames(frames, n); + } +}; + +OggVorbisReadStream::OggVorbisReadStream(string path) : + m_path(path), + m_d(new D(this)) +{ + m_channelCount = 0; + m_sampleRate = 0; + + //!!! todo: accommodate Windows UTF16 + + if (!(m_d->m_oggz = oggz_open(m_path.c_str(), OGGZ_READ))) { + m_error = string("File \"") + m_path + "\" is not an Ogg file."; + throw InvalidFileFormat(m_path, m_error); + } + + FishSoundInfo fsinfo; + m_d->m_fishSound = fish_sound_new(FISH_SOUND_DECODE, &fsinfo); + + fish_sound_set_decoded_callback(m_d->m_fishSound, D::acceptFramesStatic, m_d); + oggz_set_read_callback + (m_d->m_oggz, -1, (OggzReadPacket)D::acceptPacketStatic, m_d); + + // initialise m_channelCount + while (m_channelCount == 0 && !m_d->m_finished) { + m_d->readNextBlock(); + } + + if (m_channelCount == 0) { + m_error = string("File \"") + m_path + "\" is not a valid Ogg Vorbis file."; + throw InvalidFileFormat(m_path, m_error); + } +} + +OggVorbisReadStream::~OggVorbisReadStream() +{ + delete m_d; +} + +string +OggVorbisReadStream::getTrackName() const +{ + return m_d->getTrackName(); +} + +string +OggVorbisReadStream::getArtistName() const +{ + return m_d->getArtistName(); +} + +size_t +OggVorbisReadStream::getFrames(size_t count, float *frames) +{ + if (!m_channelCount) return 0; + if (count == 0) return 0; + + while (size_t(m_d->getAvailableFrameCount()) < count) { + if (m_d->isFinished()) break; + m_d->readNextBlock(); + } + + int available = m_d->getAvailableFrameCount(); + if (size_t(available) < count) { + count = available; + } + + //!!! handle (count * m_channelCount) > INT_MAX + int n = m_d->m_buffer->read(frames, int(count * m_channelCount)); + return n / m_channelCount; +} + +} + +#endif +#endif diff --git a/bqaudiostream/src/OggVorbisReadStream.h b/bqaudiostream/src/OggVorbisReadStream.h new file mode 100644 index 0000000..717bf5d --- /dev/null +++ b/bqaudiostream/src/OggVorbisReadStream.h @@ -0,0 +1,72 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_OGG_VORBIS_READ_STREAM_H_ +#define BQ_OGG_VORBIS_READ_STREAM_H_ + +#include "AudioReadStream.h" + +#ifdef HAVE_OGGZ +#ifdef HAVE_FISHSOUND + +namespace breakfastquay +{ + +class OggVorbisReadStream : public AudioReadStream +{ +public: + OggVorbisReadStream(std::string path); + virtual ~OggVorbisReadStream(); + + virtual std::string getTrackName() const; + virtual std::string getArtistName() const; + + virtual std::string getError() const { return m_error; } + +protected: + virtual size_t getFrames(size_t count, float *frames); + + std::string m_path; + std::string m_error; + + class D; + D *m_d; +}; + +} + +#endif +#endif + +#endif diff --git a/bqaudiostream/src/OpusReadStream.cpp b/bqaudiostream/src/OpusReadStream.cpp new file mode 100644 index 0000000..d77594c --- /dev/null +++ b/bqaudiostream/src/OpusReadStream.cpp @@ -0,0 +1,222 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifdef HAVE_OPUS + +#include <opus/opusfile.h> + +#include "OpusReadStream.h" + +#include <sstream> + +namespace breakfastquay +{ + +static vector<string> +getOpusExtensions() +{ + vector<string> extensions; + extensions.push_back("opus"); + return extensions; +} + +static +AudioReadStreamBuilder<OpusReadStream> +opusbuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/OpusReadStream"), + getOpusExtensions() + ); + +class OpusReadStream::D +{ +public: + D() : file(0) { } + + OggOpusFile *file; +}; + +OpusReadStream::OpusReadStream(string path) : + m_path(path), + m_d(new D) +{ + ostringstream os; + + m_channelCount = 0; + m_sampleRate = 0; + + int err = 0; + m_d->file = op_open_file(path.c_str(), &err); + + if (err || !m_d->file) { + os << "OpusReadStream: Unable to open file (error code " << err << ")"; + m_error = os.str(); + m_d->file = 0; + if (err == OP_EFAULT) { + throw FileNotFound(m_path); + } else { + throw InvalidFileFormat(m_path, "failed to open audio file"); + } + } + + const OpusTags *tags = op_tags(m_d->file, -1); //!!! who owns this? + if (tags) { + for (int i = 0; i < tags->comments; ++i) { + string comment = tags->user_comments[i]; + for (size_t c = 0; c < comment.size(); ++c) { + if (comment[c] == '=') { + string key = comment.substr(0, c); + string value = comment.substr(c + 1, std::string::npos); + if (key == "title") { + m_track = value; + } else if (key == "artist") { + m_artist = value; + } + break; + } + } + } + } + + m_channelCount = op_channel_count(m_d->file, -1); + m_sampleRate = 48000; // libopusfile always decodes to 48kHz! I like that +} + +size_t +OpusReadStream::getFrames(size_t count, float *frames) +{ +// cerr << "getFrames: count = " << count << endl; + + if (!m_d->file) return 0; + if (count == 0) return 0; + +// cerr << "getFrames: working" << endl; + + int totalRequired = int(count); + int totalObtained = 0; + + int channelsRequired = int(m_channelCount); + + float *fptr = frames; + + while (totalObtained < totalRequired) { + + int required = totalRequired - totalObtained; + + int likelyChannelCount = channelsRequired; + const OpusHead *linkHead = op_head(m_d->file, -1); + if (linkHead) { + likelyChannelCount = linkHead->channel_count; + } + if (likelyChannelCount < channelsRequired) { + // need to avoid overrun/truncation when reconfiguring later - + // as our target contains enough space for more frames at a + // lower channel count, so opusfile will return more than we + // can then accommodate after reconfiguration + required = (required / channelsRequired) * likelyChannelCount; + } + + int li = -1; + int obtained = op_read_float + (m_d->file, fptr, required * channelsRequired, &li); + +// cerr << "required = " << required << ", obtained = " << obtained << endl; + + if (obtained == OP_HOLE) { + continue; + } + + if (obtained == 0) { + break; + } + + if (obtained < 0) { + ostringstream os; + os << "OpusReadStream: Failed to read from file (error code " + << obtained << ")"; + m_error = os.str(); + throw InvalidFileFormat(m_path, "error in decoder"); + } + + // obtained > 0 + + int channelsRead = channelsRequired; + linkHead = op_head(m_d->file, li); + if (linkHead) { + channelsRead = linkHead->channel_count; + } + + if (channelsRead != channelsRequired) { + + if (obtained > (totalRequired - totalObtained)) { + // could happen if channel count is unexpected? + // despite precaution earlier - truncate if so + obtained = (totalRequired - totalObtained); + } + + float **read = + allocate_channels<float>(channelsRead, obtained); + float **toWrite = + allocate_channels<float>(channelsRequired, obtained); + + v_deinterleave(read, fptr, channelsRead, obtained); + v_reconfigure_channels(toWrite, channelsRequired, + read, channelsRead, + obtained); + + v_interleave(fptr, toWrite, channelsRequired, obtained); + + deallocate_channels(read, channelsRead); + deallocate_channels(toWrite, channelsRequired); + } + + totalObtained += obtained; + fptr += obtained * channelsRequired; + } + + return totalObtained; +} + +OpusReadStream::~OpusReadStream() +{ + if (m_d->file) { + op_free(m_d->file); + } + + delete m_d; +} + +} + +#endif + diff --git a/bqaudiostream/src/OpusReadStream.h b/bqaudiostream/src/OpusReadStream.h new file mode 100644 index 0000000..4a0e6c3 --- /dev/null +++ b/bqaudiostream/src/OpusReadStream.h @@ -0,0 +1,72 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_OPUS_READ_STREAM_H +#define BQ_OPUS_READ_STREAM_H + +#include "AudioReadStream.h" + +#ifdef HAVE_OPUS + +namespace breakfastquay +{ + +class OpusReadStream : public AudioReadStream +{ +public: + OpusReadStream(std::string path); + virtual ~OpusReadStream(); + + virtual std::string getTrackName() const { return m_track; } + virtual std::string getArtistName() const { return m_artist; } + + virtual std::string getError() const { return m_error; } + +protected: + virtual size_t getFrames(size_t count, float *frames); + + std::string m_path; + std::string m_error; + std::string m_track; + std::string m_artist; + + class D; + D *m_d; +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/src/SimpleWavFileWriteStream.cpp b/bqaudiostream/src/SimpleWavFileWriteStream.cpp new file mode 100644 index 0000000..e8e5be2 --- /dev/null +++ b/bqaudiostream/src/SimpleWavFileWriteStream.cpp @@ -0,0 +1,212 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#include "SimpleWavFileWriteStream.h" + +#if ! (defined(HAVE_LIBSNDFILE) || defined(HAVE_SNDFILE)) + +#include "Exceptions.h" +#include <iostream> + +using namespace std; + +namespace breakfastquay +{ + +static vector<string> extensions() { + vector<string> ee; + ee.push_back("wav"); + return ee; +} + +static +AudioWriteStreamBuilder<SimpleWavFileWriteStream> +simplewavbuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/SimpleWavFileWriteStream"), + extensions() + ); + +SimpleWavFileWriteStream::SimpleWavFileWriteStream(Target target) : + AudioWriteStream(target), + m_bitDepth(24), + m_file(0) +{ + m_file = new ofstream(getPath().c_str(), ios::out | std::ios::binary); + + if (!*m_file) { + delete m_file; + m_file = 0; + cerr << "SimpleWavFileWriteStream: Failed to open output file for writing" << endl; + m_error = string("Failed to open audio file '") + + getPath() + "' for writing"; + throw FailedToWriteFile(getPath()); + } + + writeFormatChunk(); +} + +SimpleWavFileWriteStream::~SimpleWavFileWriteStream() +{ + if (!m_file) { + return; + } + + m_file->seekp(0, ios::end); + unsigned int totalSize = m_file->tellp(); + + // seek to first length position + m_file->seekp(4, ios::beg); + + // write complete file size minus 8 bytes to here + putBytes(int2le(totalSize - 8, 4)); + + // reseek from start forward 40 + m_file->seekp(40, ios::beg); + + // write the data chunk size to end + putBytes(int2le(totalSize - 44, 4)); + + m_file->close(); + + delete m_file; + m_file = 0; +} + +void +SimpleWavFileWriteStream::putBytes(string s) +{ + if (!m_file) return; + for (unsigned int i = 0; i < s.length(); i++) { + *m_file << (unsigned char)s[i]; + } +} + +void +SimpleWavFileWriteStream::putBytes(const unsigned char *buffer, size_t n) +{ + if (!m_file) return; + m_file->write((const char *)buffer, n); +} + +string +SimpleWavFileWriteStream::int2le(unsigned int value, unsigned int length) +{ + string r; + + do { + r += (unsigned char)((long)((value >> (8 * r.length())) & 0xff)); + } while (r.length() < length); + + return r; +} + +void +SimpleWavFileWriteStream::writeFormatChunk() +{ + if (!m_file) return; + + string outString; + + outString += "RIFF"; + outString += "0000"; + outString += "WAVE"; + outString += "fmt "; + + // length + outString += int2le(0x10, 4); + + // 1 for PCM, 3 for float + outString += int2le(0x01, 2); + + // channels + outString += int2le(getChannelCount(), 2); + + // sample rate + outString += int2le(getSampleRate(), 4); + + // bytes per second + outString += int2le((m_bitDepth / 8) * getChannelCount() * getSampleRate(), 4); + + // bytes per frame + outString += int2le((m_bitDepth / 8) * getChannelCount(), 2); + + // bits per sample + outString += int2le(m_bitDepth, 2); + + outString += "data"; + outString += "0000"; + + putBytes(outString); +} + +void +SimpleWavFileWriteStream::putInterleavedFrames(size_t count, float *frames) +{ + if (count == 0) return; + + for (size_t i = 0; i < count; ++i) { + for (size_t c = 0; c < getChannelCount(); ++c) { + + double f = frames[i * getChannelCount() + c]; + unsigned int u = 0; + unsigned char ubuf[4]; + if (f < -1.0) f = -1.0; + if (f > 1.0) f = 1.0; + + switch (m_bitDepth) { + + case 24: + f = f * 2147483647.0; + u = (unsigned int)(int(f)); + u >>= 8; + ubuf[0] = (u & 0xff); + u >>= 8; + ubuf[1] = (u & 0xff); + u >>= 8; + ubuf[2] = (u & 0xff); + break; + + default: + ubuf[0] = ubuf[1] = ubuf[2] = ubuf[3] = '\0'; + break; + } + + putBytes(ubuf, m_bitDepth / 8); + } + } +} + +} + +#endif diff --git a/bqaudiostream/src/SimpleWavFileWriteStream.h b/bqaudiostream/src/SimpleWavFileWriteStream.h new file mode 100644 index 0000000..6a18d4e --- /dev/null +++ b/bqaudiostream/src/SimpleWavFileWriteStream.h @@ -0,0 +1,74 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_SIMPLE_WAV_FILE_WRITE_STREAM_H_ +#define BQ_SIMPLE_WAV_FILE_WRITE_STREAM_H_ + +#include "AudioWriteStream.h" + +// If we have libsndfile, we shouldn't be using this class +#if ! (defined(HAVE_LIBSNDFILE) || defined(HAVE_SNDFILE)) + +#include <fstream> +#include <string> + +namespace breakfastquay +{ + +class SimpleWavFileWriteStream : public AudioWriteStream +{ +public: + SimpleWavFileWriteStream(Target target); + virtual ~SimpleWavFileWriteStream(); + + virtual std::string getError() const { return m_error; } + + virtual void putInterleavedFrames(size_t count, float *frames); + +protected: + int m_bitDepth; + std::string m_error; + std::ofstream *m_file; + + void writeFormatChunk(); + std::string int2le(unsigned int value, unsigned int length); + void putBytes(std::string); + void putBytes(const unsigned char *, size_t); +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/src/WavFileReadStream.cpp b/bqaudiostream/src/WavFileReadStream.cpp new file mode 100644 index 0000000..de0337a --- /dev/null +++ b/bqaudiostream/src/WavFileReadStream.cpp @@ -0,0 +1,166 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#if defined(HAVE_LIBSNDFILE) || defined(HAVE_SNDFILE) + +#include "WavFileReadStream.h" +#include "Exceptions.h" + +#include <iostream> + +using namespace std; + +namespace breakfastquay +{ + +static vector<string> +getWavReaderExtensions() +{ + vector<string> extensions; + int count; + + if (sf_command(0, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(count))) { + extensions.push_back("wav"); + extensions.push_back("aiff"); + extensions.push_back("aifc"); + extensions.push_back("aif"); + return extensions; + } + + SF_FORMAT_INFO info; + for (int i = 0; i < count; ++i) { + info.format = i; + if (!sf_command(0, SFC_GET_FORMAT_MAJOR, &info, sizeof(info))) { + extensions.push_back(string(info.extension)); + } + } + + return extensions; +} + +static +AudioReadStreamBuilder<WavFileReadStream> +wavbuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/WavFileReadStream"), + getWavReaderExtensions() + ); + +WavFileReadStream::WavFileReadStream(string path) : + m_file(0), + m_path(path), + m_offset(0) +{ + m_channelCount = 0; + m_sampleRate = 0; + + m_fileInfo.format = 0; + m_fileInfo.frames = 0; + +#ifdef _WIN32 + int wlen = MultiByteToWideChar + (CP_UTF8, 0, m_path.c_str(), m_path.length(), 0, 0); + if (wlen > 0) { + wchar_t *buf = new wchar_t[wlen+1]; + (void)MultiByteToWideChar + (CP_UTF8, 0, m_path.c_str(), m_path.length(), buf, wlen); + buf[wlen] = L'\0'; + m_file = sf_wchar_open(buf, SFM_READ, &m_fileInfo); + delete[] buf; + } +#else + m_file = sf_open(m_path.c_str(), SFM_READ, &m_fileInfo); +#endif + + if (!m_file || m_fileInfo.frames <= 0 || m_fileInfo.channels <= 0) { +// cerr << "WavFileReadStream::initialize: Failed to open file \"" +// << path << "\" (" << sf_strerror(m_file) << ")" << endl; + if (sf_error(m_file) == SF_ERR_SYSTEM) { + m_error = string("Couldn't load audio file '") + + m_path + "':\n" + sf_strerror(m_file); + throw FileNotFound(m_path); + } + if (m_file) { + m_error = string("Couldn't load audio file '") + + m_path + "':\n" + sf_strerror(m_file); + } else { + m_error = string("Failed to open audio file '") + + m_path + "'"; + } + throw InvalidFileFormat(m_path, m_error); + } + + m_channelCount = m_fileInfo.channels; + m_sampleRate = m_fileInfo.samplerate; + + const char *str = sf_get_string(m_file, SF_STR_TITLE); + if (str) { + m_track = str; + } + str = sf_get_string(m_file, SF_STR_ARTIST); + if (str) { + m_artist = str; + } + + sf_seek(m_file, 0, SEEK_SET); +} + +WavFileReadStream::~WavFileReadStream() +{ + if (m_file) sf_close(m_file); +} + +size_t +WavFileReadStream::getFrames(size_t count, float *frames) +{ + if (!m_file || !m_channelCount) return 0; + if (count == 0) return 0; + + if ((long)m_offset >= m_fileInfo.frames) { + return 0; + } + + sf_count_t readCount = sf_readf_float(m_file, frames, count); + + if (readCount < 0) { + return 0; + } + + m_offset = m_offset + readCount; + + return readCount; +} + +} + +#endif diff --git a/bqaudiostream/src/WavFileReadStream.h b/bqaudiostream/src/WavFileReadStream.h new file mode 100644 index 0000000..517f956 --- /dev/null +++ b/bqaudiostream/src/WavFileReadStream.h @@ -0,0 +1,81 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_WAV_FILE_READ_STREAM_H_ +#define BQ_WAV_FILE_READ_STREAM_H_ + +#include "AudioReadStream.h" + +#if defined(HAVE_LIBSNDFILE) || defined(HAVE_SNDFILE) + +#ifdef _WIN32 +#include <windows.h> +#define ENABLE_SNDFILE_WINDOWS_PROTOTYPES 1 +#endif + +#include <sndfile.h> + +namespace breakfastquay +{ + +class WavFileReadStream : public AudioReadStream +{ +public: + WavFileReadStream(std::string path); + virtual ~WavFileReadStream(); + + virtual std::string getTrackName() const { return m_track; } + virtual std::string getArtistName() const { return m_artist; } + + virtual std::string getError() const { return m_error; } + +protected: + virtual size_t getFrames(size_t count, float *frames); + + SF_INFO m_fileInfo; + SNDFILE *m_file; + + std::string m_path; + std::string m_error; + std::string m_track; + std::string m_artist; + + size_t m_offset; +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/src/WavFileWriteStream.cpp b/bqaudiostream/src/WavFileWriteStream.cpp new file mode 100644 index 0000000..d7d5ead --- /dev/null +++ b/bqaudiostream/src/WavFileWriteStream.cpp @@ -0,0 +1,101 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#if defined(HAVE_LIBSNDFILE) || defined(HAVE_SNDFILE) + +#include "WavFileWriteStream.h" +#include "Exceptions.h" + +#include <cstring> + +using namespace std; + +namespace breakfastquay +{ + +static vector<string> extensions() { + vector<string> ee; + ee.push_back("wav"); + ee.push_back("aiff"); + return ee; +} + +static +AudioWriteStreamBuilder<WavFileWriteStream> +wavbuilder( + string("http://breakfastquay.com/rdf/turbot/audiostream/WavFileWriteStream"), + extensions() + ); + +WavFileWriteStream::WavFileWriteStream(Target target) : + AudioWriteStream(target), + m_file(0) +{ + memset(&m_fileInfo, 0, sizeof(SF_INFO)); + m_fileInfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT; + m_fileInfo.channels = getChannelCount(); + m_fileInfo.samplerate = getSampleRate(); + + m_file = sf_open(getPath().c_str(), SFM_WRITE, &m_fileInfo); + + if (!m_file) { + cerr << "WavFileWriteStream::initialize: Failed to open output file for writing (" + << sf_strerror(m_file) << ")" << endl; + + m_error = string("Failed to open audio file '") + + getPath() + "' for writing"; + throw FailedToWriteFile(getPath()); + } +} + +WavFileWriteStream::~WavFileWriteStream() +{ + if (m_file) sf_close(m_file); +} + +void +WavFileWriteStream::putInterleavedFrames(size_t count, float *frames) +{ + if (count == 0) return; + + sf_count_t written = sf_writef_float(m_file, frames, count); + + if (written != count) { + throw FileOperationFailed(getPath(), "write sf data"); + } +} + +} + +#endif diff --git a/bqaudiostream/src/WavFileWriteStream.h b/bqaudiostream/src/WavFileWriteStream.h new file mode 100644 index 0000000..6453071 --- /dev/null +++ b/bqaudiostream/src/WavFileWriteStream.h @@ -0,0 +1,68 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* + bqaudiostream + + A small library wrapping various audio file read/write + implementations in C++. + + Copyright 2007-2015 Particular Programs Ltd. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of Chris Cannam and + Particular Programs Ltd shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this + Software without prior written authorization. +*/ + +#ifndef BQ_WAV_FILE_WRITE_STREAM_H_ +#define BQ_WAV_FILE_WRITE_STREAM_H_ + +#include "AudioWriteStream.h" + +#if defined(HAVE_LIBSNDFILE) || defined(HAVE_SNDFILE) + +#include <sndfile.h> + +namespace breakfastquay +{ + +class WavFileWriteStream : public AudioWriteStream +{ +public: + WavFileWriteStream(Target target); + virtual ~WavFileWriteStream(); + + virtual std::string getError() const { return m_error; } + + virtual void putInterleavedFrames(size_t count, float *frames); + +protected: + SF_INFO m_fileInfo; + SNDFILE *m_file; + + std::string m_error; +}; + +} + +#endif + +#endif diff --git a/bqaudiostream/test/AudioStreamTestData.h b/bqaudiostream/test/AudioStreamTestData.h new file mode 100644 index 0000000..6baf440 --- /dev/null +++ b/bqaudiostream/test/AudioStreamTestData.h @@ -0,0 +1,124 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* Copyright Chris Cannam - All Rights Reserved */ + +#ifndef AUDIOSTREAM_TEST_DATA_H +#define AUDIOSTREAM_TEST_DATA_H + +#include "bqaudiostream/AudioWriteStreamFactory.h" +#include "bqaudiostream/AudioWriteStream.h" + +#include <cmath> + +#include <iostream> + +namespace breakfastquay { + +/** + * Class that generates a single fixed test pattern to a given sample + * rate and number of channels. + * + * The test pattern is two seconds long and consists of: + * + * -- in channel 0, a 600Hz sinusoid with peak amplitude 1.0 + * + * -- in channel 1, four triangular forms with peaks at +1.0, -1.0, + * +1.0, -1.0 respectively, of 10ms width, starting at 0.0, 0.5, + * 1.0 and 1.5 seconds; silence elsewhere + * + * -- in subsequent channels, a flat DC offset at +(channelNo / 20.0) + */ +class AudioStreamTestData +{ +public: + AudioStreamTestData(float rate, int channels) : + m_channelCount(channels), + m_duration(2.0), + m_sampleRate(rate), + m_sinFreq(600.0), + m_pulseFreq(2) + { + m_frameCount = lrint(m_duration * m_sampleRate); + m_data = new float[m_frameCount * m_channelCount]; + m_pulseWidth = 0.01 * m_sampleRate; + generate(); + } + + ~AudioStreamTestData() { + delete[] m_data; + } + + void generate() { + + float hpw = m_pulseWidth / 2.0; + + for (int i = 0; i < m_frameCount; ++i) { + for (int c = 0; c < m_channelCount; ++c) { + + float s = 0.f; + + if (c == 0) { + + float phase = (i * m_sinFreq * 2.f * M_PI) / m_sampleRate; + s = sinf(phase); + + } else if (c == 1) { + + int pulseNo = int((i * m_pulseFreq) / m_sampleRate); + int index = (i * m_pulseFreq) - (m_sampleRate * pulseNo); + if (index < m_pulseWidth) { + s = 1.0 - fabsf(hpw - index) / hpw; + if (pulseNo % 2) s = -s; + } + + } else { + + s = c / 20.0; + } + + m_data[i * m_channelCount + c] = s; + } + } + } + + float *getInterleavedData() const { + return m_data; + } + + int getFrameCount() const { + return m_frameCount; + } + + int getChannelCount() const { + return m_channelCount; + } + + float getSampleRate () const { + return m_sampleRate; + } + + float getDuration() const { // seconds + return m_duration; + } + + void writeToFile(std::string filename) { + AudioWriteStream *ws = AudioWriteStreamFactory::createWriteStream + (filename, m_channelCount, lrint(m_sampleRate)); + ws->putInterleavedFrames(m_frameCount, m_data); + delete ws; + } + +private: + float *m_data; + int m_frameCount; + int m_channelCount; + float m_duration; + float m_sampleRate; + float m_sinFreq; + float m_pulseFreq; + float m_pulseWidth; +}; + +} + +#endif + diff --git a/bqaudiostream/test/TestAudioStreamRead.h b/bqaudiostream/test/TestAudioStreamRead.h new file mode 100644 index 0000000..6125026 --- /dev/null +++ b/bqaudiostream/test/TestAudioStreamRead.h @@ -0,0 +1,209 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* Copyright Chris Cannam - All Rights Reserved */ + +#ifndef TEST_AUDIOSTREAM_READ_H +#define TEST_AUDIOSTREAM_READ_H + +#include "bqaudiostream/AudioReadStreamFactory.h" +#include "bqaudiostream/AudioReadStream.h" +#include "bqaudiostream/Exceptions.h" + +#include "AudioStreamTestData.h" + +#include <cmath> + +#include <QObject> +#include <QtTest> +#include <QDir> + +#include <iostream> + +using namespace std; + +namespace breakfastquay { + +static QString audioDir = "testfiles"; + +class TestAudioStreamRead : public QObject +{ + Q_OBJECT + + const char *strOf(QString s) { + return strdup(s.toLocal8Bit().data()); + } + +private slots: + void init() + { + if (!QDir(audioDir).exists()) { + cerr << "ERROR: Audio test file directory \"" << audioDir.toLocal8Bit().data() << "\" does not exist" << endl; + QVERIFY2(QDir(audioDir).exists(), "Audio test file directory not found"); + } + } + + void read_data() + { + QTest::addColumn<QString>("audiofile"); + QStringList files = QDir(audioDir).entryList(QDir::Files); + foreach (QString filename, files) { + // Our test audio files are all named + // RATE-CHANNELS-BITDEPTH.ext (for PCM data) or + // RATE-CHANNELS.ext (for lossy data) + QStringList fileAndExt = filename.split("."); + QStringList bits = fileAndExt[0].split("-"); + if (bits.size() < 2 || bits.size() > 3) continue; + QTest::newRow(strOf(filename)) << filename; + } + } + + void read() + { + QFETCH(QString, audiofile); + + cerr << "\n\n*** audiofile = " << audiofile.toLocal8Bit().data() << "\n\n" << endl; + + try { + + int readRate = 48000; + + string filename = (audioDir + "/" + audiofile).toLocal8Bit().data(); + AudioReadStream *stream = + AudioReadStreamFactory::createReadStream(filename); + + stream->setRetrievalSampleRate(readRate); + int channels = stream->getChannelCount(); + AudioStreamTestData tdata(readRate, channels); + + // Our test audio files are all named + // RATE-CHANNELS-BITDEPTH.ext (for PCM data) or + // RATE-CHANNELS.ext (for lossy data) + QStringList fileAndExt = audiofile.split("."); + QStringList bits = fileAndExt[0].split("-"); + QString extension = fileAndExt[1]; + int nominalRate = bits[0].toInt(); + int nominalChannels = bits[1].toInt(); + int nominalDepth = 16; + if (bits.length() > 2) nominalDepth = bits[2].toInt(); + + QCOMPARE(channels, nominalChannels); + + QCOMPARE((int)stream->getSampleRate(), nominalRate); + QCOMPARE((int)stream->getRetrievalSampleRate(), readRate); + + float *reference = tdata.getInterleavedData(); + int refFrames = tdata.getFrameCount(); + + // The reader should give us exactly the expected number of + // frames, except for mp3/aac files. We ask for quite a lot + // more, though, so we can (a) check that we only get the + // expected number back (if this is not mp3/aac) or (b) take + // into account silence at beginning and end (if it is). + int testFrames = refFrames + 5000; + int testsize = testFrames * channels; + + float *test = new float[testsize]; + + int read = stream->getInterleavedFrames(testFrames, test); + + if (extension == "mp3" || extension == "aac" || extension == "m4a") { + // mp3s and aacs can have silence at start and end + QVERIFY(read >= refFrames); + } else { + QCOMPARE(read, refFrames); + } + + // Our limits are pretty relaxed -- we're not testing decoder + // or resampler quality here, just whether the results are + // plainly wrong (e.g. at wrong samplerate or with an offset) + + float limit = 0.01; + float edgeLimit = limit * 10; // in first or final edgeSize frames + int edgeSize = 100; + + if (nominalDepth < 16) { + limit = 0.02; + } + if (extension == "ogg" || extension == "mp3" || + extension == "aac" || extension == "m4a") { + limit = 0.2; + edgeLimit = limit * 3; + } + + // And we ignore completely the last few frames when upsampling + int discard = 1 + readRate / nominalRate; + + int offset = 0; + + if (extension == "aac" || extension == "m4a") { + // our m4a file appears to have a fixed offset of 1024 (at + // file sample rate) + offset = (1024 / float(nominalRate)) * readRate; + } + + if (extension == "mp3") { + // while mp3s appear to vary + for (int i = 0; i < read; ++i) { + bool any = false; + float thresh = 0.01; + for (int c = 0; c < channels; ++c) { + if (fabsf(test[i * channels + c]) > thresh) { + any = true; + break; + } + } + if (any) { + offset = i; + break; + } + } + } + + for (int c = 0; c < channels; ++c) { + float maxdiff = 0.f; + int maxAt = 0; + float totdiff = 0.f; + for (int i = 0; i < read - offset - discard && i < refFrames; ++i) { + float diff = fabsf(test[(i + offset) * channels + c] - + reference[i * channels + c]); + totdiff += diff; + // in edge areas, record this only if it exceeds edgeLimit + if (i < edgeSize || i + edgeSize >= read - offset) { + if (diff > edgeLimit) { + maxdiff = diff; + maxAt = i; + } + } else { + if (diff > maxdiff) { + maxdiff = diff; + maxAt = i; + } + } + } + float meandiff = totdiff / read; + if (meandiff >= limit) { + cerr << "ERROR: for audiofile " << audiofile.toLocal8Bit().data() << ": mean diff = " << meandiff << " for channel " << c << endl; + QVERIFY(meandiff < limit); + } + if (maxdiff >= limit) { + cerr << "ERROR: for audiofile " << audiofile.toLocal8Bit().data() << ": max diff = " << maxdiff << " at frame " << maxAt << " of " << read << " on channel " << c << " (mean diff = " << meandiff << ")" << endl; + QVERIFY(maxdiff < limit); + } + } + + delete[] test; + + } catch (UnknownFileType &t) { +#if (QT_VERSION >= 0x050000) + QSKIP(strOf(QString("File format for \"%1\" not supported, skipping").arg(audiofile))); +#else + QSKIP(strOf(QString("File format for \"%1\" not supported, skipping").arg(audiofile)), SkipSingle); +#endif + } + } +}; + +} + +#endif + + diff --git a/bqaudiostream/test/TestSimpleWavRead.h b/bqaudiostream/test/TestSimpleWavRead.h new file mode 100644 index 0000000..be19b97 --- /dev/null +++ b/bqaudiostream/test/TestSimpleWavRead.h @@ -0,0 +1,91 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* Copyright Chris Cannam - All Rights Reserved */ + +#ifndef TEST_SIMPLE_WAV_READ_H +#define TEST_SIMPLE_WAV_READ_H + +#include <QObject> +#include <QtTest> + +#include "bqaudiostream/AudioReadStreamFactory.h" +#include "bqaudiostream/AudioReadStream.h" + +namespace breakfastquay { + +class TestSimpleWavRead : public QObject +{ + Q_OBJECT + + // This is a 44.1KHz 16-bit mono WAV file with 20 samples in it, + // with a 1 at the start, -1 at the end and 0 elsewhere + static const char *testsound() { + static const char *f = "testfiles/20samples.wav"; + return f; + } + +private slots: + + void supported() { + // We should *always* be able to read WAV files + QVERIFY(AudioReadStreamFactory::isExtensionSupportedFor(testsound())); + } + + void open() { + AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound()); + QVERIFY(s); + QCOMPARE(s->getError(), std::string()); + QCOMPARE(s->getChannelCount(), size_t(1)); + QCOMPARE(s->getSampleRate(), size_t(44100)); + delete s; + } + + void length() { + AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound()); + QVERIFY(s); + float frames[22]; + size_t n = s->getInterleavedFrames(22, frames); + QCOMPARE(n, size_t(20)); + delete s; + } + + void read() { + AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound()); + QVERIFY(s); + float frames[4]; + size_t n = s->getInterleavedFrames(4, frames); + QCOMPARE(n, size_t(4)); + QCOMPARE(frames[0], 32767.f/32768.f); // 16 bit file, so never quite 1 + QCOMPARE(frames[1], 0.f); + QCOMPARE(frames[2], 0.f); + QCOMPARE(frames[3], 0.f); + delete s; + } + + void readEnd() { + AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound()); + QVERIFY(s); + float frames[20]; + size_t n = s->getInterleavedFrames(20, frames); + QCOMPARE(n, size_t(20)); + QCOMPARE(frames[17], 0.f); + QCOMPARE(frames[18], 0.f); + QCOMPARE(frames[19], -1.f); + delete s; + } + + void resampledLength() { + AudioReadStream *s = AudioReadStreamFactory::createReadStream(testsound()); + QVERIFY(s); + s->setRetrievalSampleRate(22050); + float frames[22]; + size_t n = s->getInterleavedFrames(22, frames); + QCOMPARE(n, size_t(10)); + delete s; + } +}; + +} + +#endif + + diff --git a/bqaudiostream/test/TestWavReadWrite.h b/bqaudiostream/test/TestWavReadWrite.h new file mode 100644 index 0000000..f44fedc --- /dev/null +++ b/bqaudiostream/test/TestWavReadWrite.h @@ -0,0 +1,179 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* Copyright Chris Cannam - All Rights Reserved */ + +#ifndef TEST_WAV_READ_WRITE_H +#define TEST_WAV_READ_WRITE_H + +#include <QObject> +#include <QtTest> + +#include "bqaudiostream/AudioReadStreamFactory.h" +#include "bqaudiostream/AudioReadStream.h" +#include "bqaudiostream/AudioWriteStreamFactory.h" +#include "bqaudiostream/AudioWriteStream.h" + +#include "bqvec/Allocators.h" + +namespace breakfastquay { + +static const float DB_FLOOR = -1000.0; + +static float to_dB(float ratio) +{ + if (ratio == 0.0) return DB_FLOOR; + float dB = 10 * log10f(ratio); + return dB; +} + +static float from_dB(float dB) +{ + if (dB == DB_FLOOR) return 0.0; + float m = powf(10.0, dB / 10.0); + return m; +} + +class TestWavReadWrite : public QObject +{ + Q_OBJECT + + // This is a 44.1KHz, 16-bit, 2-channel PCM WAV containing our + // 2-second test signal + static const char *testfile() { + static const char *f = "testfiles/44100-2-16.wav"; + return f; + } + static const char *outfile() { + static const char *f = "test-audiostream-out.wav"; + return f; + } + static const char *outfile_origrate() { + static const char *f = "test-audiostream-out-origrate.wav"; + return f; + } + +private slots: + void readWriteResample() { + + // First read file into memory at normal sample rate + + AudioReadStream *rs = AudioReadStreamFactory::createReadStream(testfile()); + QVERIFY(rs); + + int cc = rs->getChannelCount(); + QCOMPARE(cc, 2); + + int rate = rs->getSampleRate(); + QCOMPARE(rate, 44100); + + int bs = 2048; + int count = 0; + int bufsiz = bs; + float *buffer = allocate<float>(bs); + + while (1) { + if (count + bs > bufsiz) { + buffer = reallocate<float>(buffer, bufsiz, bufsiz * 2); + bufsiz *= 2; + } + int got = cc * rs->getInterleavedFrames(bs / cc, buffer + count); + count += got; + if (got < bs) break; + } + + delete rs; + + // Re-open with resampling + + rs = AudioReadStreamFactory::createReadStream(testfile()); + + QVERIFY(rs); + + rs->setRetrievalSampleRate(rate * 2); + + // Write resampled test file + + AudioWriteStream *ws = AudioWriteStreamFactory::createWriteStream + (outfile(), cc, rate * 2); + + QVERIFY(ws); + + float *block = allocate<float>(bs); + + while (1) { + int got = rs->getInterleavedFrames(bs / cc, block); + ws->putInterleavedFrames(got, block); + if (got < bs / cc) break; + } + + delete ws; + delete rs; + ws = 0; + + // Read back resampled file at original rate and compare + + rs = AudioReadStreamFactory::createReadStream(outfile()); + + QVERIFY(rs); + QCOMPARE(rs->getSampleRate(), size_t(rate * 2)); + + rs->setRetrievalSampleRate(rate); + + ws = AudioWriteStreamFactory::createWriteStream + (outfile_origrate(), cc, rate); + + QVERIFY(ws); + + float error = from_dB(-10); + float warning = from_dB(-25); + float maxdiff = 0.f; + float mda = 0.f, mdb = 0.f; + int maxdiffindex = -1; + + count = 0; + + while (1) { + int got = rs->getInterleavedFrames(bs / cc, block); + for (int i = 0; i < got * cc; ++i) { + float a = block[i]; + float b = buffer[count + i]; + float diff = fabsf(a - b); + if (diff > maxdiff && + (count + i) > 10) { // first few samples are generally shaky + maxdiff = diff; + maxdiffindex = count + i; + mda = a; + mdb = b; + } + } + count += got * cc; + ws->putInterleavedFrames(got, block); + if (got < bs / cc) break; + } + + delete ws; + delete rs; + deallocate(block); + deallocate(buffer); + + QString message = QString("Max diff is %1 (%2 dB) at index %3 (a = %4, b = %5) [error threshold %6 (%7 dB), warning threshold %8 (%9 dB)]") + .arg(maxdiff) + .arg(to_dB(maxdiff)) + .arg(maxdiffindex) + .arg(mda) + .arg(mdb) + .arg(error) + .arg(to_dB(error)) + .arg(warning) + .arg(to_dB(warning)); + + QVERIFY2(maxdiff < error, message.toLocal8Bit().data()); + + if (maxdiff > warning) { + QWARN(message.toLocal8Bit().data()); + } + } +}; + +} + +#endif diff --git a/bqaudiostream/test/generate.cpp b/bqaudiostream/test/generate.cpp new file mode 100644 index 0000000..fbbbe4e --- /dev/null +++ b/bqaudiostream/test/generate.cpp @@ -0,0 +1,54 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* Copyright Chris Cannam - All Rights Reserved */ + +#include "AudioStreamTestData.h" + +#include <iostream> + +#include <cstdlib> + +using namespace Turbot; +using namespace std; + +int main(int argc, char **argv) +{ + cerr << endl; + + if (argc != 4) { + cerr << "Usage: " << argv[0] << " <rate> <channels> <outfile.wav>" << endl; + cerr << "Supported outfile extensions: "; + QStringList exts = AudioWriteStreamFactory::getSupportedFileExtensions(); + foreach (QString e, exts) cerr << e << " "; + cerr << endl; + return 2; + } + + float rate = atof(argv[1]); + int channels = atoi(argv[2]); + QString filename = argv[3]; + + cerr << "Sample rate: " << rate << endl; + cerr << "Channel count: " << channels << endl; + cerr << "Output filename: " << filename << endl; + + if (rate < 1 || rate > 1e6) { + cerr << "ERROR: Crazy rate " << rate << " (try somewhere between 1 and a million)" << endl; + return 2; + } + + if (channels < 1 || channels > 20) { + cerr << "ERROR: Crazy channel count " << channels << " (try somewhere between 1 and 20)" << endl; + return 2; + } + + AudioStreamTestData td(rate, channels); + try { + td.writeToFile(filename); + } catch (std::exception &e) { + std::cerr << "Failed to write test data to output file \"" << filename << "\": " << e.what() << std::endl; + return 1; + } + + return 0; +} + diff --git a/bqaudiostream/test/generate.pro b/bqaudiostream/test/generate.pro new file mode 100644 index 0000000..6c35024 --- /dev/null +++ b/bqaudiostream/test/generate.pro @@ -0,0 +1,23 @@ + +TEMPLATE = app +TARGET = audiostream-test-generate + +DESTDIR = ../../../out +QMAKE_LIBDIR += ../../../out ../../../../dataquay + +INCLUDEPATH += . ../.. ../../../.. +DEPENDPATH += . ../.. ../../../.. + +!win32-* { + PRE_TARGETDEPS += ../../../out/libturbot.a +} + +include(../../../platform.pri) +TEMPLATE += platform + +OBJECTS_DIR = o +MOC_DIR = o + +HEADERS += AudioStreamTestData.h +SOURCES += generate.cpp + diff --git a/bqaudiostream/test/main.cpp b/bqaudiostream/test/main.cpp new file mode 100644 index 0000000..17e9c72 --- /dev/null +++ b/bqaudiostream/test/main.cpp @@ -0,0 +1,45 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ +/* Copyright Chris Cannam - All Rights Reserved */ + +#include "TestSimpleWavRead.h" +#include "TestAudioStreamRead.h" +#include "TestWavReadWrite.h" +#include <QtTest> + +#include <iostream> + +int main(int argc, char *argv[]) +{ + int good = 0, bad = 0; + + QCoreApplication app(argc, argv); + app.setOrganizationName("Particular Programs"); + app.setApplicationName("test-audiostream"); + + { + breakfastquay::TestSimpleWavRead t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + { + breakfastquay::TestAudioStreamRead t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + { + breakfastquay::TestWavReadWrite t; + if (QTest::qExec(&t, argc, argv) == 0) ++good; + else ++bad; + } + + if (bad > 0) { + std::cerr << "\n********* " << bad << " test suite(s) failed!\n" << std::endl; + return 1; + } else { + std::cerr << "All tests passed" << std::endl; + return 0; + } +} + diff --git a/bqaudiostream/test/test.pro b/bqaudiostream/test/test.pro new file mode 100644 index 0000000..9f94745 --- /dev/null +++ b/bqaudiostream/test/test.pro @@ -0,0 +1,31 @@ + +TEMPLATE = app +CONFIG += debug +TARGET = test-audiostream +win*: TARGET = "TestAudiostream" + +QT += testlib +QT -= gui + +DESTDIR = . +QMAKE_LIBDIR += . .. + +LIBS += -L.. -lbqaudiostream -L../../bqresample -lbqresample -L../../bqvec -lbqvec -lsndfile -loggz -lfishsound + +INCLUDEPATH += . .. ../../bqvec ../../bqresample ../../bqthingfactory +DEPENDPATH += . .. ../../bqvec ../../bqresample ../../bqthingfactory + +HEADERS += AudioStreamTestData.h TestAudioStreamRead.h TestSimpleWavRead.h TestWavReadWrite.h +SOURCES += main.cpp + +!win32 { + !macx* { + QMAKE_POST_LINK=$${DESTDIR}/$${TARGET} + } + macx* { + QMAKE_POST_LINK=$${DESTDIR}/$${TARGET}.app/Contents/MacOS/$${TARGET} + } +} + +win32-g++:QMAKE_POST_LINK=$${DESTDIR}$${TARGET}.exe + diff --git a/bqaudiostream/test/testfiles/12000-6-16.aiff b/bqaudiostream/test/testfiles/12000-6-16.aiff Binary files differnew file mode 100644 index 0000000..2781fc6 --- /dev/null +++ b/bqaudiostream/test/testfiles/12000-6-16.aiff diff --git a/bqaudiostream/test/testfiles/20samples.wav b/bqaudiostream/test/testfiles/20samples.wav Binary files differnew file mode 100644 index 0000000..8e343fe --- /dev/null +++ b/bqaudiostream/test/testfiles/20samples.wav diff --git a/bqaudiostream/test/testfiles/32000-1-16.wav b/bqaudiostream/test/testfiles/32000-1-16.wav Binary files differnew file mode 100644 index 0000000..799150a --- /dev/null +++ b/bqaudiostream/test/testfiles/32000-1-16.wav diff --git a/bqaudiostream/test/testfiles/32000-1.aac b/bqaudiostream/test/testfiles/32000-1.aac Binary files differnew file mode 100644 index 0000000..50b8af1 --- /dev/null +++ b/bqaudiostream/test/testfiles/32000-1.aac diff --git a/bqaudiostream/test/testfiles/32000-1.mp3 b/bqaudiostream/test/testfiles/32000-1.mp3 Binary files differnew file mode 100644 index 0000000..df5e0f0 --- /dev/null +++ b/bqaudiostream/test/testfiles/32000-1.mp3 diff --git a/bqaudiostream/test/testfiles/32000-1.ogg b/bqaudiostream/test/testfiles/32000-1.ogg Binary files differnew file mode 100644 index 0000000..14add79 --- /dev/null +++ b/bqaudiostream/test/testfiles/32000-1.ogg diff --git a/bqaudiostream/test/testfiles/44100-1-32.wav b/bqaudiostream/test/testfiles/44100-1-32.wav Binary files differnew file mode 100644 index 0000000..0058bfa --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-1-32.wav diff --git a/bqaudiostream/test/testfiles/44100-2-16.wav b/bqaudiostream/test/testfiles/44100-2-16.wav Binary files differnew file mode 100644 index 0000000..dd57b1b --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-2-16.wav diff --git a/bqaudiostream/test/testfiles/44100-2-8.wav b/bqaudiostream/test/testfiles/44100-2-8.wav Binary files differnew file mode 100644 index 0000000..9d69e32 --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-2-8.wav diff --git a/bqaudiostream/test/testfiles/44100-2.aac b/bqaudiostream/test/testfiles/44100-2.aac Binary files differnew file mode 100644 index 0000000..b5688c0 --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-2.aac diff --git a/bqaudiostream/test/testfiles/44100-2.flac b/bqaudiostream/test/testfiles/44100-2.flac Binary files differnew file mode 100644 index 0000000..7769fdc --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-2.flac diff --git a/bqaudiostream/test/testfiles/44100-2.mp3 b/bqaudiostream/test/testfiles/44100-2.mp3 Binary files differnew file mode 100644 index 0000000..63b5801 --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-2.mp3 diff --git a/bqaudiostream/test/testfiles/44100-2.ogg b/bqaudiostream/test/testfiles/44100-2.ogg Binary files differnew file mode 100644 index 0000000..1ccc284 --- /dev/null +++ b/bqaudiostream/test/testfiles/44100-2.ogg diff --git a/bqaudiostream/test/testfiles/48000-1-16.wav b/bqaudiostream/test/testfiles/48000-1-16.wav Binary files differnew file mode 100644 index 0000000..a50eaaa --- /dev/null +++ b/bqaudiostream/test/testfiles/48000-1-16.wav diff --git a/bqaudiostream/test/testfiles/48000-1-24.aiff b/bqaudiostream/test/testfiles/48000-1-24.aiff Binary files differnew file mode 100644 index 0000000..f5d8eda --- /dev/null +++ b/bqaudiostream/test/testfiles/48000-1-24.aiff diff --git a/bqaudiostream/test/testfiles/8000-1-8.wav b/bqaudiostream/test/testfiles/8000-1-8.wav Binary files differnew file mode 100644 index 0000000..c7e9544 --- /dev/null +++ b/bqaudiostream/test/testfiles/8000-1-8.wav diff --git a/bqaudiostream/test/testfiles/8000-2-16.wav b/bqaudiostream/test/testfiles/8000-2-16.wav Binary files differnew file mode 100644 index 0000000..df88629 --- /dev/null +++ b/bqaudiostream/test/testfiles/8000-2-16.wav diff --git a/bqaudiostream/test/testfiles/8000-6-16.wav b/bqaudiostream/test/testfiles/8000-6-16.wav Binary files differnew file mode 100644 index 0000000..a466c1f --- /dev/null +++ b/bqaudiostream/test/testfiles/8000-6-16.wav |