From dad96afdcb526d613139faa6a0d328d9b73ba67a Mon Sep 17 00:00:00 2001 From: Dimitri Diakopoulos Date: Sun, 17 May 2015 15:43:51 -0700 Subject: [PATCH] very initial pass at wav file encoding --- examples/src/Main.cpp | 16 +++-- include/libnyquist/RiffUtils.h | 27 ++++++++ include/libnyquist/WavDecoder.h | 2 +- include/libnyquist/WavEncoder.h | 54 +++++++++++++++ libnyquist.xcodeproj/project.pbxproj | 6 ++ src/RiffUtils.cpp | 20 +++++- src/WavDecoder.cpp | 3 +- src/WavEncoder.cpp | 99 ++++++++++++++++++++++++++++ 8 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 include/libnyquist/WavEncoder.h create mode 100644 src/WavEncoder.cpp diff --git a/examples/src/Main.cpp b/examples/src/Main.cpp index 60e1294..40c8aff 100644 --- a/examples/src/Main.cpp +++ b/examples/src/Main.cpp @@ -4,6 +4,7 @@ #include "libnyquist/AudioDevice.h" #include "libnyquist/AudioDecoder.h" +#include "libnyquist/WavEncoder.h" #include @@ -20,10 +21,14 @@ int main() AudioData * fileData = new AudioData(); NyquistIO loader; + + WavEncoder encoder; try { - + // Circular libnyquist testing! + //auto result = loader.Load(fileData, "encoded.wav"); + //auto result = loader.Load(fileData, "test_data/1ch/44100/8/test.wav"); //auto result = loader.Load(fileData, "test_data/1ch/44100/16/test.wav"); //auto result = loader.Load(fileData, "test_data/1ch/44100/24/test.wav"); @@ -33,7 +38,7 @@ int main() //auto result = loader.Load(fileData, "test_data/2ch/44100/8/test.wav"); //auto result = loader.Load(fileData, "test_data/2ch/44100/16/test.wav"); //auto result = loader.Load(fileData, "test_data/2ch/44100/24/test.wav"); - auto result = loader.Load(fileData, "test_data/2ch/44100/32/test.wav"); + //auto result = loader.Load(fileData, "test_data/2ch/44100/32/test.wav"); //auto result = loader.Load(fileData, "test_data/2ch/44100/64/test.wav"); //auto result = loader.Load(fileData, "test_data/ad_hoc/6_channel_44k_16b.wav"); @@ -42,7 +47,7 @@ int main() //auto result = loader.Load(fileData, "test_data/ad_hoc/TestLaugh_44k.ogg"); //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeat.ogg"); //auto result = loader.Load(fileData, "test_data/ad_hoc/TestBeatMono.ogg"); - //auto result = loader.Load(fileData, "test_data/ad_hoc/BlockWoosh_Stereo.ogg"); + auto result = loader.Load(fileData, "test_data/ad_hoc/BlockWoosh_Stereo.ogg"); //auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr8_Stereo_Dithered.flac"); //auto result = loader.Load(fileData, "test_data/ad_hoc/KittyPurr16_Stereo.flac"); @@ -91,6 +96,9 @@ int main() std::cout << "Playing for: " << fileData->lengthSeconds << " seconds..." << std::endl; myDevice.Play(fileData->samples); } - + + // Test wav file encoder + encoder.WriteFile({2, 44100, 32, PCM_FLT}, fileData->samples, "encoded.wav"); + return 0; } \ No newline at end of file diff --git a/include/libnyquist/RiffUtils.h b/include/libnyquist/RiffUtils.h index c39f1b4..0bdae07 100644 --- a/include/libnyquist/RiffUtils.h +++ b/include/libnyquist/RiffUtils.h @@ -27,6 +27,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define RIFF_UTILS_H #include "Common.h" +#include "WavDecoder.h" namespace nqr { @@ -35,6 +36,14 @@ namespace nqr // Chunk utilities // ///////////////////// +struct EncoderParams +{ + int channels; + int samplerate; + int bit_depth; + PCMFormat fmt; +}; + struct ChunkHeaderInfo { uint32_t offset; // Byte offset into chunk @@ -49,8 +58,26 @@ inline uint32_t GenerateChunkCode(uint8_t a, uint8_t b, uint8_t c, uint8_t d) return ((uint32_t) ((((uint32_t) (a)) << 24) | ((b) << 16) | ((c) << 8) | (d))); #endif } + +inline char * GenerateChunkCodeChar(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +{ +auto chunk = GenerateChunkCode(a, b, c, d); + + char * outArr = new char[4]; + + uint32_t t = 0x000000FF; + + for(size_t i = 0; i < 4; i++) + { + outArr[i] = chunk & t; + chunk >>= 8; + } + return outArr; +} ChunkHeaderInfo ScanForChunk(const std::vector & fileData, uint32_t chunkMarker); + +WaveChunkHeader MakeWaveHeader(const EncoderParams param); } // end namespace nqr diff --git a/include/libnyquist/WavDecoder.h b/include/libnyquist/WavDecoder.h index 49c4e2b..83bb4fe 100644 --- a/include/libnyquist/WavDecoder.h +++ b/include/libnyquist/WavDecoder.h @@ -56,7 +56,7 @@ struct WaveChunkHeader uint16_t channel_count; // Num interleaved channels uint32_t sample_rate; // SR uint32_t data_rate; // Data rate - uint16_t frame_size; // 1 frame = channels * bits per sample + uint16_t frame_size; // 1 frame = channels * bits per sample (also known as block align) uint16_t bit_depth; // Bits per sample }; diff --git a/include/libnyquist/WavEncoder.h b/include/libnyquist/WavEncoder.h new file mode 100644 index 0000000..fd8b0c9 --- /dev/null +++ b/include/libnyquist/WavEncoder.h @@ -0,0 +1,54 @@ +/* +Copyright (c) 2015, Dimitri Diakopoulos All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef WAVE_ENCODER_H +#define WAVE_ENCODER_H + +#include "Common.h" +#include "WavDecoder.h" // For reference structs +#include "RiffUtils.h" + +namespace nqr +{ + +// A simplistic encoder that takes a blob of data, conforms it to the user's +// EncoderParams preference, and writes to disk. Be warned, does not support resampling! +// @todo support dithering, samplerate conversion, etc. +class WavEncoder +{ + +public: + + WavEncoder(); + ~WavEncoder(); + + // Assume data adheres to EncoderParams, except for bit depth and fmt + void WriteFile(const EncoderParams p, const std::vector & data, const std::string & path); + +}; + +} // end namespace nqr + +#endif diff --git a/libnyquist.xcodeproj/project.pbxproj b/libnyquist.xcodeproj/project.pbxproj index 8e18966..8d900bc 100644 --- a/libnyquist.xcodeproj/project.pbxproj +++ b/libnyquist.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 0804D1461AE6BA5100F4B1FD /* celt_encoder.c in Sources */ = {isa = PBXBuildFile; fileRef = 0804D1441AE6BA5100F4B1FD /* celt_encoder.c */; }; 080A58581B0866F600302850 /* RiffUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 080A58571B0866F600302850 /* RiffUtils.cpp */; }; 081FFB191ADF803800673073 /* FlacDependencies.c in Sources */ = {isa = PBXBuildFile; fileRef = 081FFB181ADF803800673073 /* FlacDependencies.c */; }; + 083DB3F31B091C1100FB0661 /* WavEncoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 083DB3F21B091C1100FB0661 /* WavEncoder.cpp */; }; 086DADAD1AE029860031F793 /* VorbisDependencies.c in Sources */ = {isa = PBXBuildFile; fileRef = 086DADAB1ADF9DF30031F793 /* VorbisDependencies.c */; }; 08894F291AEAA5D7007AAF90 /* bits.c in Sources */ = {isa = PBXBuildFile; fileRef = 08894F1E1AEAA5D7007AAF90 /* bits.c */; }; 08894F2A1AEAA5D7007AAF90 /* extra1.c in Sources */ = {isa = PBXBuildFile; fileRef = 08894F1F1AEAA5D7007AAF90 /* extra1.c */; }; @@ -62,6 +63,8 @@ 080A586C1B086D9100302850 /* libnyquistPriv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = libnyquistPriv.h; sourceTree = ""; }; 080A586E1B086D9100302850 /* libnyquist.cp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = libnyquist.cp; sourceTree = ""; }; 081FFB181ADF803800673073 /* FlacDependencies.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = FlacDependencies.c; path = src/FlacDependencies.c; sourceTree = SOURCE_ROOT; }; + 083DB3F21B091C1100FB0661 /* WavEncoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WavEncoder.cpp; path = src/WavEncoder.cpp; sourceTree = SOURCE_ROOT; }; + 083DB3F41B091C1E00FB0661 /* WavEncoder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WavEncoder.h; path = include/libnyquist/WavEncoder.h; sourceTree = SOURCE_ROOT; }; 086DADAB1ADF9DF30031F793 /* VorbisDependencies.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = VorbisDependencies.c; path = src/VorbisDependencies.c; sourceTree = SOURCE_ROOT; }; 08894F1E1AEAA5D7007AAF90 /* bits.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = bits.c; path = third_party/wavpack/src/bits.c; sourceTree = SOURCE_ROOT; }; 08894F1F1AEAA5D7007AAF90 /* extra1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = extra1.c; path = third_party/wavpack/src/extra1.c; sourceTree = SOURCE_ROOT; }; @@ -247,6 +250,7 @@ 08B91D881AC73B8000335131 /* FlacDecoder.h */, 08B91D891AC73B8000335131 /* VorbisDecoder.h */, 08B91D8A1AC73B8000335131 /* OpusDecoder.h */, + 083DB3F41B091C1E00FB0661 /* WavEncoder.h */, 08B91D8E1AC73B8000335131 /* WavDecoder.h */, 08B91D8F1AC73B8000335131 /* WavPackDecoder.h */, ); @@ -265,6 +269,7 @@ 08B91D941AC73B8A00335131 /* FlacDecoder.cpp */, 08B91D961AC73B8A00335131 /* VorbisDecoder.cpp */, 08B91D971AC73B8A00335131 /* OpusDecoder.cpp */, + 083DB3F21B091C1100FB0661 /* WavEncoder.cpp */, 08B91D981AC73B8A00335131 /* WavDecoder.cpp */, 08B91D991AC73B8A00335131 /* WavPackDecoder.cpp */, ); @@ -328,6 +333,7 @@ buildActionMask = 2147483647; files = ( 08894F2C1AEAA5D7007AAF90 /* float.c in Sources */, + 083DB3F31B091C1100FB0661 /* WavEncoder.cpp in Sources */, 08894F2E1AEAA5D7007AAF90 /* pack.c in Sources */, 08894F2A1AEAA5D7007AAF90 /* extra1.c in Sources */, 08894F321AEAA5D7007AAF90 /* words.c in Sources */, diff --git a/src/RiffUtils.cpp b/src/RiffUtils.cpp index ba07541..36039db 100644 --- a/src/RiffUtils.cpp +++ b/src/RiffUtils.cpp @@ -45,4 +45,22 @@ ChunkHeaderInfo nqr::ScanForChunk(const std::vector & fileData, uint32_ else continue; } return {0, 0}; -}; \ No newline at end of file +}; + + + +WaveChunkHeader nqr::MakeWaveHeader(const EncoderParams param) +{ + WaveChunkHeader header; + + header.fmt_id = GenerateChunkCode('f', 'm', 't', ' '); + header.chunk_size = 16; + header.format = (param.fmt <= PCMFormat::PCM_32) ? WaveFormatCode::FORMAT_PCM : WaveFormatCode::FORMAT_IEEE; + header.channel_count = param.channels; + header.sample_rate = param.samplerate; + header.data_rate = param.samplerate * param.channels * (param.bit_depth / 8); + header.frame_size = param.channels * (param.bit_depth / 8); + header.bit_depth = param.bit_depth; + + return header; +} \ No newline at end of file diff --git a/src/WavDecoder.cpp b/src/WavDecoder.cpp index 3c5ad69..133be4b 100644 --- a/src/WavDecoder.cpp +++ b/src/WavDecoder.cpp @@ -70,7 +70,8 @@ int WavDecoder::LoadFromBuffer(AudioData * data, const std::vector & me if (riffHeader.id_wave != GenerateChunkCode('W', 'A', 'V', 'E')) throw std::runtime_error("bad WAVE header"); - if ((memory.size() - riffHeader.file_size) != sizeof(uint32_t) * 2) + auto expectedSize = (memory.size() - riffHeader.file_size); + if (expectedSize != sizeof(uint32_t) * 2) { throw std::runtime_error("declared size of file less than file size"); //@todo warning instead of runtime_error } diff --git a/src/WavEncoder.cpp b/src/WavEncoder.cpp new file mode 100644 index 0000000..c0be1aa --- /dev/null +++ b/src/WavEncoder.cpp @@ -0,0 +1,99 @@ +/* +Copyright (c) 2015, Dimitri Diakopoulos All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "WavEncoder.h" +#include + +using namespace nqr; + +// Big-endian convert +inline void toBytes(int value, char * arr) +{ + arr[0] = (value) & 0xFF; + arr[1] = (value >> 8) & 0xFF; + arr[2] = (value >> 16) & 0xFF; + arr[3] = (value >> 24) & 0xFF; +} + +WavEncoder::WavEncoder() +{ + +} + +WavEncoder::~WavEncoder() +{ + +} + +//@todo check for max file length, sanity checks, etc. +void WavEncoder::WriteFile(const EncoderParams p, const std::vector & data, const std::string & path) +{ + + std::ofstream fout(path.c_str(), std::ios::out | std::ios::binary); + + if (!fout.is_open()) + { + throw std::runtime_error("File cannot be opened"); + } + + char * chunkSizeBuff = new char[4]; + + // Initial size + toBytes(36, chunkSizeBuff); + + // RIFF File Header + fout.write(GenerateChunkCodeChar('R', 'I', 'F', 'F'), 4); + fout.write(chunkSizeBuff, 4); + + fout.write(GenerateChunkCodeChar('W', 'A', 'V', 'E'), 4); + + // Fmt Header + auto header = MakeWaveHeader(p); + fout.write(reinterpret_cast(&header), sizeof(WaveChunkHeader)); + + // Data Header + fout.write(GenerateChunkCodeChar('d', 'a', 't', 'a'), 4); + + // + data chunk size + auto numSamplesBytes = data.size() * sizeof(float); + toBytes(numSamplesBytes, chunkSizeBuff); + fout.write(chunkSizeBuff, 4); + + // Debugging -- assume IEEE_Float + fout.write(reinterpret_cast(data.data()), numSamplesBytes); + + // Find size + long totalSize = fout.tellp(); + + // Modify RIFF header + fout.seekp(4); + + // Total size of the file, less 8 for the RIFF header + toBytes(totalSize - 8 , chunkSizeBuff); + + fout.write(chunkSizeBuff, 4); + + delete[] chunkSizeBuff; +}