summaryrefslogtreecommitdiff
path: root/bqaudiostream
diff options
context:
space:
mode:
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>2019-07-15 20:43:29 +0200
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>2019-07-15 20:43:29 +0200
commit8c5b9f24a637ebe4f79883ed32c7ba27b8ab9e0c (patch)
tree46d4084e0ae1aa35fe6a68c98c739bfdb33dfd5c /bqaudiostream
parentc1c08a7a391491913e4a10c3c55673bd81129cf7 (diff)
New upstream version 3.3
Diffstat (limited to 'bqaudiostream')
-rw-r--r--bqaudiostream/.hgignore7
-rw-r--r--bqaudiostream/.hgtags12
-rw-r--r--bqaudiostream/COPYING26
-rw-r--r--bqaudiostream/Makefile50
-rw-r--r--bqaudiostream/README.txt14
-rw-r--r--bqaudiostream/bqaudiostream/AudioReadStream.h110
-rw-r--r--bqaudiostream/bqaudiostream/AudioReadStreamFactory.h93
-rw-r--r--bqaudiostream/bqaudiostream/AudioWriteStream.h98
-rw-r--r--bqaudiostream/bqaudiostream/AudioWriteStreamFactory.h97
-rw-r--r--bqaudiostream/bqaudiostream/Exceptions.h143
-rw-r--r--bqaudiostream/src/AudioReadStream.cpp197
-rw-r--r--bqaudiostream/src/AudioReadStreamFactory.cpp131
-rw-r--r--bqaudiostream/src/AudioStreamExceptions.cpp88
-rw-r--r--bqaudiostream/src/AudioWriteStreamFactory.cpp142
-rw-r--r--bqaudiostream/src/CoreAudioReadStream.cpp273
-rw-r--r--bqaudiostream/src/CoreAudioReadStream.h72
-rw-r--r--bqaudiostream/src/CoreAudioWriteStream.cpp255
-rw-r--r--bqaudiostream/src/CoreAudioWriteStream.h66
-rw-r--r--bqaudiostream/src/MediaFoundationReadStream.cpp479
-rw-r--r--bqaudiostream/src/MediaFoundationReadStream.h70
-rw-r--r--bqaudiostream/src/OggVorbisReadStream.cpp250
-rw-r--r--bqaudiostream/src/OggVorbisReadStream.h72
-rw-r--r--bqaudiostream/src/OpusReadStream.cpp222
-rw-r--r--bqaudiostream/src/OpusReadStream.h72
-rw-r--r--bqaudiostream/src/SimpleWavFileWriteStream.cpp212
-rw-r--r--bqaudiostream/src/SimpleWavFileWriteStream.h74
-rw-r--r--bqaudiostream/src/WavFileReadStream.cpp166
-rw-r--r--bqaudiostream/src/WavFileReadStream.h81
-rw-r--r--bqaudiostream/src/WavFileWriteStream.cpp101
-rw-r--r--bqaudiostream/src/WavFileWriteStream.h68
-rw-r--r--bqaudiostream/test/AudioStreamTestData.h124
-rw-r--r--bqaudiostream/test/TestAudioStreamRead.h209
-rw-r--r--bqaudiostream/test/TestSimpleWavRead.h91
-rw-r--r--bqaudiostream/test/TestWavReadWrite.h179
-rw-r--r--bqaudiostream/test/generate.cpp54
-rw-r--r--bqaudiostream/test/generate.pro23
-rw-r--r--bqaudiostream/test/main.cpp45
-rw-r--r--bqaudiostream/test/test.pro31
-rw-r--r--bqaudiostream/test/testfiles/12000-6-16.aiffbin0 -> 288054 bytes
-rw-r--r--bqaudiostream/test/testfiles/20samples.wavbin0 -> 84 bytes
-rw-r--r--bqaudiostream/test/testfiles/32000-1-16.wavbin0 -> 128044 bytes
-rw-r--r--bqaudiostream/test/testfiles/32000-1.aacbin0 -> 10099 bytes
-rw-r--r--bqaudiostream/test/testfiles/32000-1.mp3bin0 -> 33478 bytes
-rw-r--r--bqaudiostream/test/testfiles/32000-1.oggbin0 -> 7624 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-1-32.wavbin0 -> 352880 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-2-16.wavbin0 -> 352844 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-2-8.wavbin0 -> 176444 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-2.aacbin0 -> 12286 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-2.flacbin0 -> 89258 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-2.mp3bin0 -> 33214 bytes
-rw-r--r--bqaudiostream/test/testfiles/44100-2.oggbin0 -> 13215 bytes
-rw-r--r--bqaudiostream/test/testfiles/48000-1-16.wavbin0 -> 192044 bytes
-rw-r--r--bqaudiostream/test/testfiles/48000-1-24.aiffbin0 -> 288054 bytes
-rw-r--r--bqaudiostream/test/testfiles/8000-1-8.wavbin0 -> 16044 bytes
-rw-r--r--bqaudiostream/test/testfiles/8000-2-16.wavbin0 -> 64044 bytes
-rw-r--r--bqaudiostream/test/testfiles/8000-6-16.wavbin0 -> 192044 bytes
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
new file mode 100644
index 0000000..2781fc6
--- /dev/null
+++ b/bqaudiostream/test/testfiles/12000-6-16.aiff
Binary files differ
diff --git a/bqaudiostream/test/testfiles/20samples.wav b/bqaudiostream/test/testfiles/20samples.wav
new file mode 100644
index 0000000..8e343fe
--- /dev/null
+++ b/bqaudiostream/test/testfiles/20samples.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/32000-1-16.wav b/bqaudiostream/test/testfiles/32000-1-16.wav
new file mode 100644
index 0000000..799150a
--- /dev/null
+++ b/bqaudiostream/test/testfiles/32000-1-16.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/32000-1.aac b/bqaudiostream/test/testfiles/32000-1.aac
new file mode 100644
index 0000000..50b8af1
--- /dev/null
+++ b/bqaudiostream/test/testfiles/32000-1.aac
Binary files differ
diff --git a/bqaudiostream/test/testfiles/32000-1.mp3 b/bqaudiostream/test/testfiles/32000-1.mp3
new file mode 100644
index 0000000..df5e0f0
--- /dev/null
+++ b/bqaudiostream/test/testfiles/32000-1.mp3
Binary files differ
diff --git a/bqaudiostream/test/testfiles/32000-1.ogg b/bqaudiostream/test/testfiles/32000-1.ogg
new file mode 100644
index 0000000..14add79
--- /dev/null
+++ b/bqaudiostream/test/testfiles/32000-1.ogg
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-1-32.wav b/bqaudiostream/test/testfiles/44100-1-32.wav
new file mode 100644
index 0000000..0058bfa
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-1-32.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-2-16.wav b/bqaudiostream/test/testfiles/44100-2-16.wav
new file mode 100644
index 0000000..dd57b1b
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-2-16.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-2-8.wav b/bqaudiostream/test/testfiles/44100-2-8.wav
new file mode 100644
index 0000000..9d69e32
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-2-8.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-2.aac b/bqaudiostream/test/testfiles/44100-2.aac
new file mode 100644
index 0000000..b5688c0
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-2.aac
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-2.flac b/bqaudiostream/test/testfiles/44100-2.flac
new file mode 100644
index 0000000..7769fdc
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-2.flac
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-2.mp3 b/bqaudiostream/test/testfiles/44100-2.mp3
new file mode 100644
index 0000000..63b5801
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-2.mp3
Binary files differ
diff --git a/bqaudiostream/test/testfiles/44100-2.ogg b/bqaudiostream/test/testfiles/44100-2.ogg
new file mode 100644
index 0000000..1ccc284
--- /dev/null
+++ b/bqaudiostream/test/testfiles/44100-2.ogg
Binary files differ
diff --git a/bqaudiostream/test/testfiles/48000-1-16.wav b/bqaudiostream/test/testfiles/48000-1-16.wav
new file mode 100644
index 0000000..a50eaaa
--- /dev/null
+++ b/bqaudiostream/test/testfiles/48000-1-16.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/48000-1-24.aiff b/bqaudiostream/test/testfiles/48000-1-24.aiff
new file mode 100644
index 0000000..f5d8eda
--- /dev/null
+++ b/bqaudiostream/test/testfiles/48000-1-24.aiff
Binary files differ
diff --git a/bqaudiostream/test/testfiles/8000-1-8.wav b/bqaudiostream/test/testfiles/8000-1-8.wav
new file mode 100644
index 0000000..c7e9544
--- /dev/null
+++ b/bqaudiostream/test/testfiles/8000-1-8.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/8000-2-16.wav b/bqaudiostream/test/testfiles/8000-2-16.wav
new file mode 100644
index 0000000..df88629
--- /dev/null
+++ b/bqaudiostream/test/testfiles/8000-2-16.wav
Binary files differ
diff --git a/bqaudiostream/test/testfiles/8000-6-16.wav b/bqaudiostream/test/testfiles/8000-6-16.wav
new file mode 100644
index 0000000..a466c1f
--- /dev/null
+++ b/bqaudiostream/test/testfiles/8000-6-16.wav
Binary files differ