]> git.mxchange.org Git - simgear.git/blob - simgear/sound/readwav.cxx
Fix tests linkage when building static libs.
[simgear.git] / simgear / sound / readwav.cxx
1 // Copyright (C) 2012  James Turner - zakalawe@mac.com
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Library General Public
5 // License as published by the Free Software Foundation; either
6 // version 2 of the License, or (at your option) any later version.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Library General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program; if not, write to the Free Software
15 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
16 //
17
18 // adapted from the freealut sources, especially alutBufferData.c, alutLoader.c
19 // and alutCodec.c (freealut is also LGPL licensed)
20
21 #include "readwav.hxx"
22
23 #include <cassert>
24 #include <cstdlib>
25
26 #include <zlib.h> // for gzXXX functions
27
28 #include <simgear/misc/sg_path.hxx>
29 #include <simgear/debug/logstream.hxx>
30 #include <simgear/misc/stdint.hxx>
31 #include <simgear/structure/exception.hxx>
32
33 namespace 
34 {
35   class Buffer {
36   public:
37     ALvoid* data;
38     ALenum format;
39     ALsizei length;
40     ALfloat frequency;
41     SGPath path;
42       
43     Buffer() : data(NULL), format(AL_NONE), length(0), frequency(0.0f) {}
44     
45     ~Buffer()
46     {
47       if (data) {
48         free(data);
49       }
50     }
51   };
52   
53   ALenum formatConstruct(ALint numChannels, ALint bitsPerSample)
54   {
55     switch (numChannels)
56       {
57       case 1:
58         switch (bitsPerSample) {
59           case 8: return  AL_FORMAT_MONO8;
60           case 16: return AL_FORMAT_MONO16;
61         }
62         break;
63       case 2:
64         switch (bitsPerSample) {
65           case 8: return AL_FORMAT_STEREO8;
66           case 16: return AL_FORMAT_STEREO16;
67           }
68         break;
69       }
70     return AL_NONE;
71   }
72   
73 // function prototype for decoding audio data
74   typedef void Codec(Buffer* buf);
75   
76   void codecLinear(Buffer* /*buf*/)
77   {
78   }
79   
80   void codecPCM16 (Buffer* buf)
81   {
82     // always byte-swaps here; is this a good idea?
83     uint16_t *d = (uint16_t *) buf->data;
84     size_t i, l = buf->length / 2;
85     for (i = 0; i < l; i++) {
86       *d = sg_bswap_16(*d);
87     }
88   }
89   
90  /*
91   * From: http://www.multimedia.cx/simpleaudio.html#tth_sEc6.1
92   */
93 int16_t mulaw2linear (uint8_t mulawbyte)
94  {
95    static const int16_t exp_lut[8] = {
96      0, 132, 396, 924, 1980, 4092, 8316, 16764
97    };
98    int16_t sign, exponent, mantissa, sample;
99    mulawbyte = ~mulawbyte;
100    sign = (mulawbyte & 0x80);
101    exponent = (mulawbyte >> 4) & 0x07;
102    mantissa = mulawbyte & 0x0F;
103    sample = exp_lut[exponent] + (mantissa << (exponent + 3));
104    return sign ? -sample : sample;
105  }
106
107  void codecULaw (Buffer* b)
108  {
109    uint8_t *d = (uint8_t *) b->data;
110    size_t newLength = b->length * 2;
111    int16_t *buf = (int16_t *) malloc(newLength);
112    size_t i;
113    if (buf == NULL)
114      throw sg_exception("malloc failed decoing ULaw WAV file");
115    
116    for (i = 0; i < b->length; i++) {
117        buf[i] = mulaw2linear(d[i]);
118     }
119     
120    free(b->data);
121    b->data = buf;
122    b->length = newLength;
123  }
124  
125   bool gzSkip(gzFile fd, int skipCount)
126   {
127       int r = gzseek(fd, skipCount, SEEK_CUR);
128       return (r >= 0);
129   }
130   
131   const int32_t WAV_RIFF_4CC = 0x52494646; // 'RIFF'
132   const int32_t WAV_WAVE_4CC = 0x57415645; // 'WAVE'
133   const int32_t WAV_DATA_4CC = 0x64617461; // 'data'
134   const int32_t WAV_FORMAT_4CC = 0x666d7420; // 'fmt '
135   
136   template<class T>
137   bool wavReadBE(gzFile fd, T& value)
138   {
139     if (gzread(fd, &value, sizeof(T)) != sizeof(T))
140       return false;
141     
142     if (sgIsLittleEndian()) 
143       sgEndianSwap(&value);
144     
145     return true;
146   }
147
148   template<class T>
149   bool wavReadLE(gzFile fd, T& value)
150   {
151     if (gzread(fd, &value, sizeof(T)) != sizeof(T))
152       return false;
153     
154     if (sgIsBigEndian()) 
155       sgEndianSwap(&value);
156     
157     return true;
158   }
159   
160   void loadWavFile(gzFile fd, Buffer* b)
161   {
162     assert(b->data == NULL);
163     
164     bool found_header = false;
165     uint32_t chunkLength;
166     int32_t magic;
167     uint16_t audioFormat;
168     uint16_t numChannels;
169     uint32_t samplesPerSecond;
170     uint32_t byteRate;
171     uint16_t blockAlign;
172     uint16_t bitsPerSample;
173     Codec *codec = codecLinear;
174
175     if (!wavReadBE(fd, magic))
176       throw sg_io_exception("corrupt or truncated WAV data", b->path);
177     
178     if (magic != WAV_RIFF_4CC) {
179       throw sg_io_exception("not a .wav file", b->path);
180     }
181
182     if (!wavReadLE(fd, chunkLength) || !wavReadBE(fd, magic))
183       throw sg_io_exception("corrupt or truncated WAV data", b->path);
184
185     if (magic != WAV_WAVE_4CC)      /* "WAVE" */
186     {
187         throw sg_io_exception("unrecognized WAV magic", b->path);
188     }
189
190     while (1) {
191         if (!wavReadBE(fd, magic) || !wavReadLE(fd, chunkLength))
192             throw sg_io_exception("corrupt or truncated WAV data", b->path);
193
194         if (magic == WAV_FORMAT_4CC)  /* "fmt " */
195         {
196             found_header = true;
197             if (chunkLength < 16) {
198               throw sg_io_exception("corrupt or truncated WAV data", b->path);
199             }
200
201             if (!wavReadLE (fd, audioFormat) ||
202                 !wavReadLE (fd, numChannels) ||
203                 !wavReadLE (fd, samplesPerSecond) ||
204                 !wavReadLE (fd, byteRate) ||
205                 !wavReadLE (fd, blockAlign) ||
206                 !wavReadLE (fd, bitsPerSample))
207               {
208                 throw sg_io_exception("corrupt or truncated WAV data", b->path);
209               }
210
211             if (!gzSkip(fd, chunkLength - 16))
212                 throw sg_io_exception("corrupt or truncated WAV data", b->path);
213
214             switch (audioFormat)
215               {
216               case 1:            /* PCM */
217                 codec = (bitsPerSample == 8 || sgIsLittleEndian()) ? codecLinear : codecPCM16;
218                 break;
219               case 7:            /* uLaw */
220                 bitsPerSample *= 2;       /* ToDo: ??? */
221                 codec = codecULaw;
222                 break;
223               default:
224                 throw sg_io_exception("unsupported WAV encoding", b->path);
225               }
226               
227               b->frequency = samplesPerSecond;
228               b->format = formatConstruct(numChannels, bitsPerSample);
229         } else if (magic == WAV_DATA_4CC) {
230             if (!found_header) {
231                 /* ToDo: A bit wrong to check here, fmt chunk could come later... */
232                 throw sg_io_exception("corrupt or truncated WAV data", b->path);
233             }
234             
235             b->data = malloc(chunkLength);
236             b->length = chunkLength;
237             size_t read = gzread(fd, b->data, chunkLength);
238             if (read != chunkLength) {
239                 throw sg_io_exception("insufficent data reading WAV file", b->path);
240             }
241             
242             break;
243         } else {
244             if (!gzSkip(fd, chunkLength))
245               throw sg_io_exception("corrupt or truncated WAV data", b->path);
246         }
247
248         if ((chunkLength & 1) && !gzeof(fd) && !gzSkip(fd, 1))
249           throw sg_io_exception("corrupt or truncated WAV data", b->path);
250       } // of file chunk parser loop
251       
252       codec(b); // might throw if something really bad occurs
253   } // of loadWav function
254   
255 } // of anonymous namespace
256
257 namespace simgear
258 {
259
260 ALvoid* loadWAVFromFile(const SGPath& path, ALenum& format, ALsizei& size, ALfloat& freqf)
261 {
262   if (!path.exists()) {
263     throw sg_io_exception("loadWAVFromFile: file not found", path);
264   }
265   
266   Buffer b;
267     b.path = path;
268     
269   gzFile fd;
270   fd = gzopen(path.c_str(), "rb");
271   if (!fd) {
272     throw sg_io_exception("loadWAVFromFile: unable to open file", path);
273   }
274   
275     loadWavFile(fd, &b);
276   ALvoid* data = b.data;
277     b.data = NULL; // don't free when Buffer does out of scope
278     format = b.format;
279     size = b.length;
280     freqf = b.frequency;
281   
282   gzclose(fd);
283   return data;
284 }
285
286 ALuint createBufferFromFile(const SGPath& path)
287 {
288   ALenum format;
289   ALsizei size;
290   ALfloat sampleFrequency;
291   ALvoid* data = loadWAVFromFile(path, format, size, sampleFrequency);
292   assert(data);
293   
294   ALuint buffer;
295   alGenBuffers(1, &buffer);
296   if (alGetError() != AL_NO_ERROR) {
297     free(data);
298     throw sg_io_exception("OpenAL buffer allocation failed", sg_location(path.str()));
299   }
300     
301   alBufferData (buffer, format, data, size, (ALsizei) sampleFrequency);
302   if (alGetError() != AL_NO_ERROR) {
303     alDeleteBuffers(1, &buffer);
304     free(data);
305     throw sg_io_exception("OpenAL setting buffer data failed", sg_location(path.str()));
306   }
307     
308   return buffer;
309 }
310
311 } // of namespace simgear