• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Sonic library
2    Copyright 2010
3    Bill Cox
4    This file is part of the Sonic Library.
5 
6    This file is licensed under the Apache 2.0 license.
7 */
8 
9 /*
10 This file supports read/write wave files.
11 */
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include "wave.h"
16 
17 #define WAVE_BUF_LEN 4096
18 
19 struct waveFileStruct {
20     int numChannels;
21     int sampleRate;
22     FILE *soundFile;
23     int bytesWritten; /* The number of bytes written so far, including header */
24     int failed;
25     int isInput;
26 };
27 
28 /* Write a string to a file. */
writeBytes(waveFile file,void * bytes,int length)29 static void writeBytes(
30     waveFile file,
31     void *bytes,
32     int length)
33 {
34     size_t bytesWritten;
35 
36     if(file->failed) {
37         return;
38     }
39     bytesWritten = fwrite(bytes, sizeof(char), length, file->soundFile);
40     if(bytesWritten != length) {
41         fprintf(stderr, "Unable to write to output file");
42         file->failed = 1;
43     }
44     file->bytesWritten += bytesWritten;
45 }
46 
47 /* Write a string to a file. */
writeString(waveFile file,char * string)48 static void writeString(
49     waveFile file,
50     char *string)
51 {
52     writeBytes(file, string, strlen(string));
53 }
54 
55 /* Write an integer to a file in little endian order. */
writeInt(waveFile file,int value)56 static void writeInt(
57     waveFile file,
58     int value)
59 {
60     char bytes[4];
61     int i;
62 
63     for(i = 0; i < 4; i++) {
64         bytes[i] = value;
65         value >>= 8;
66     }
67     writeBytes(file, bytes, 4);
68 }
69 
70 /* Write a short integer to a file in little endian order. */
writeShort(waveFile file,short value)71 static void writeShort(
72     waveFile file,
73     short value)
74 {
75     char bytes[2];
76     int i;
77 
78     for(i = 0; i < 2; i++) {
79         bytes[i] = value;
80         value >>= 8;
81     }
82     writeBytes(file, bytes, 2);
83 }
84 
85 /* Read bytes from the input file. Return the number of bytes actually read. */
readBytes(waveFile file,void * bytes,int length)86 static int readBytes(
87     waveFile file,
88     void *bytes,
89     int length)
90 {
91     if(file->failed) {
92         return 0;
93     }
94     return fread(bytes, sizeof(char), length, file->soundFile);
95 }
96 
97 /* Read an exact number of bytes from the input file. */
readExactBytes(waveFile file,void * bytes,int length)98 static void readExactBytes(
99     waveFile file,
100     void *bytes,
101     int length)
102 {
103     int numRead;
104 
105     if(file->failed) {
106         return;
107     }
108     numRead = fread(bytes, sizeof(char), length, file->soundFile);
109     if(numRead != length) {
110         fprintf(stderr, "Failed to read requested bytes from input file\n");
111         file->failed = 1;
112     }
113 }
114 
115 /* Read an integer from the input file */
readInt(waveFile file)116 static int readInt(
117     waveFile file)
118 {
119     unsigned char bytes[4];
120     int value = 0, i;
121 
122     readExactBytes(file, bytes, 4);
123     for(i = 3; i >= 0; i--) {
124         value <<= 8;
125         value |= bytes[i];
126     }
127     return value;
128 }
129 
130 /* Read a short from the input file */
readShort(waveFile file)131 static int readShort(
132     waveFile file)
133 {
134     unsigned char bytes[2];
135     int value = 0, i;
136 
137     readExactBytes(file, bytes, 2);
138     for(i = 1; i >= 0; i--) {
139         value <<= 8;
140         value |= bytes[i];
141     }
142     return value;
143 }
144 
145 /* Read a string from the input and compare it to an expected string. */
expectString(waveFile file,char * expectedString)146 static void expectString(
147     waveFile file,
148     char *expectedString)
149 {
150     char buf[11]; /* Be sure that we never call with a longer string */
151     int length = strlen(expectedString);
152 
153     if(length > 10) {
154         fprintf(stderr, "Internal error: expected string too long\n");
155         file->failed = 1;
156     } else {
157         readExactBytes(file, buf, length);
158         buf[length] = '\0';
159         if(strcmp(expectedString, buf)) {
160             fprintf(stderr, "Unsupported wave file format\n");
161             file->failed = 1;
162         }
163     }
164 }
165 
166 /* Write the header of the wave file. */
writeHeader(waveFile file,int sampleRate)167 static void writeHeader(
168     waveFile file,
169     int sampleRate)
170 {
171     /* write the wav file per the wav file format */
172     writeString(file, "RIFF"); /* 00 - RIFF */
173     /* We have to fseek and overwrite this later when we close the file because */
174     /* we don't know how big it is until then. */
175     writeInt(file, 36 /* + dataLength */); /* 04 - how big is the rest of this file? */
176     writeString(file, "WAVE"); /* 08 - WAVE */
177     writeString(file, "fmt "); /* 12 - fmt */
178     writeInt(file, 16); /* 16 - size of this chunk */
179     writeShort(file, 1); /* 20 - what is the audio format? 1 for PCM = Pulse Code Modulation */
180     writeShort(file, 1); /* 22 - mono or stereo? 1 or 2?  (or 5 or ???) */
181     writeInt(file, sampleRate); /* 24 - samples per second (numbers per second) */
182     writeInt(file, sampleRate * 2); /* 28 - bytes per second */
183     writeShort(file, 2); /* 32 - # of bytes in one sample, for all channels */
184     writeShort(file, 16); /* 34 - how many bits in a sample(number)?  usually 16 or 24 */
185     writeString(file, "data"); /* 36 - data */
186     writeInt(file, 0); /* 40 - how big is this data chunk */
187 }
188 
189 /* Read the header of the wave file. */
readHeader(waveFile file)190 static int readHeader(
191     waveFile file)
192 {
193     int data;
194 
195     expectString(file, "RIFF");
196     data = readInt(file); /* 04 - how big is the rest of this file? */
197     expectString(file, "WAVE"); /* 08 - WAVE */
198     expectString(file, "fmt "); /* 12 - fmt */
199     int chunkSize = readInt(file); /* 16 or 18 - size of this chunk */
200     if(chunkSize != 16 && chunkSize != 18) {
201         fprintf(stderr, "Only basic wave files are supported\n");
202         return 0;
203     }
204     data = readShort(file); /* 20 - what is the audio format? 1 for PCM = Pulse Code Modulation */
205     if(data != 1) {
206         fprintf(stderr, "Only PCM wave files are supported\n");
207         return 0;
208     }
209     file->numChannels = readShort(file); /* 22 - mono or stereo? 1 or 2?  (or 5 or ???) */
210     file->sampleRate = readInt(file); /* 24 - samples per second (numbers per second) */
211     readInt(file); /* 28 - bytes per second */
212     readShort(file); /* 32 - # of bytes in one sample, for all channels */
213     data = readShort(file); /* 34 - how many bits in a sample(number)?  usually 16 or 24 */
214     if(data != 16) {
215         fprintf(stderr, "Only 16 bit PCM wave files are supported\n");
216         return 0;
217     }
218     if (chunkSize == 18) { /* ffmpeg writes 18, and so has 2 extra bytes here */
219         data = readShort(file);
220     }
221     expectString(file, "data"); /* 36 - data */
222     readInt(file); /* 40 - how big is this data chunk */
223     return 1;
224 }
225 
226 /* Close the input or output file and free the waveFile. */
closeFile(waveFile file)227 static void closeFile(
228     waveFile file)
229 {
230     FILE *soundFile = file->soundFile;
231 
232     if(soundFile != NULL) {
233         fclose(soundFile);
234         file->soundFile = NULL;
235     }
236     free(file);
237 }
238 
239 /* Open a 16-bit little-endian wav file for reading.  It may be mono or stereo. */
openInputWaveFile(char * fileName,int * sampleRate,int * numChannels)240 waveFile openInputWaveFile(
241     char *fileName,
242     int *sampleRate,
243     int *numChannels)
244 {
245     waveFile file;
246     FILE *soundFile = fopen(fileName, "rb");
247 
248     if(soundFile == NULL) {
249 	fprintf(stderr, "Unable to open wave file %s for reading\n", fileName);
250 	return NULL;
251     }
252     file = (waveFile)calloc(1, sizeof(struct waveFileStruct));
253     file->soundFile = soundFile;
254     file->isInput = 1;
255     if(!readHeader(file)) {
256         closeFile(file);
257         return NULL;
258     }
259     *sampleRate = file->sampleRate;
260     *numChannels = file->numChannels;
261     return file;
262 }
263 
264 /* Open a 16-bit little-endian wav file for writing.  It may be mono or stereo. */
openOutputWaveFile(char * fileName,int sampleRate,int numChannels)265 waveFile openOutputWaveFile(
266     char *fileName,
267     int sampleRate,
268     int numChannels)
269 {
270     waveFile file;
271     FILE *soundFile = fopen(fileName, "wb");
272 
273     if(soundFile == NULL) {
274 	fprintf(stderr, "Unable to open wave file %s for writing\n", fileName);
275 	return NULL;
276     }
277     file = (waveFile)calloc(1, sizeof(struct waveFileStruct));
278     file->soundFile = soundFile;
279     file->sampleRate = sampleRate;
280     file->numChannels = numChannels;
281     writeHeader(file, sampleRate);
282     if(file->failed) {
283         closeFile(file);
284         return NULL;
285     }
286     return file;
287 }
288 
289 /* Close the sound file. */
closeWaveFile(waveFile file)290 int closeWaveFile(
291     waveFile file)
292 {
293     FILE *soundFile = file->soundFile;
294     int passed = 1;
295 
296     if(!file->isInput) {
297         if(fseek(soundFile, 4, SEEK_SET) != 0) {
298             fprintf(stderr, "Failed to seek on input file.\n");
299             passed = 0;
300         } else {
301             /* Now update the file to have the correct size. */
302             writeInt(file, file->bytesWritten - 8);
303             if(file->failed) {
304                 fprintf(stderr, "Failed to write wave file size.\n");
305                 passed = 0;
306             }
307             if(fseek(soundFile, 40, SEEK_SET) != 0) {
308                 fprintf(stderr, "Failed to seek on input file.\n");
309                 passed = 0;
310             } else {
311                 /* Now update the file to have the correct size. */
312                 writeInt(file, file->bytesWritten - 48);
313                 if(file->failed) {
314                     fprintf(stderr, "Failed to write wave file size.\n");
315                     passed = 0;
316                 }
317             }
318         }
319     }
320     closeFile(file);
321     return passed;
322 }
323 
324 /* Read from the wave file.  Return the number of samples read. */
readFromWaveFile(waveFile file,short * buffer,int maxSamples)325 int readFromWaveFile(
326     waveFile file,
327     short *buffer,
328     int maxSamples)
329 {
330     int i, bytesRead, samplesRead;
331     int bytePos = 0;
332     unsigned char bytes[WAVE_BUF_LEN];
333     short sample;
334 
335     if(maxSamples*file->numChannels*2 > WAVE_BUF_LEN) {
336         maxSamples = WAVE_BUF_LEN/(file->numChannels*2);
337     }
338     bytesRead = readBytes(file, bytes, maxSamples*file->numChannels*2);
339     samplesRead = bytesRead/(file->numChannels*2);
340     for(i = 0; i < samplesRead*file->numChannels; i++) {
341         sample = bytes[bytePos++];
342         sample |= (unsigned int)bytes[bytePos++] << 8;
343         *buffer++ = sample;
344     }
345     return samplesRead;
346 }
347 
348 /* Write to the wave file. */
writeToWaveFile(waveFile file,short * buffer,int numSamples)349 int writeToWaveFile(
350     waveFile file,
351     short *buffer,
352     int numSamples)
353 {
354     int i;
355     int bytePos = 0;
356     unsigned char bytes[WAVE_BUF_LEN];
357     short sample;
358     int total = numSamples*file->numChannels;
359 
360     for(i = 0; i < total; i++) {
361         if(bytePos == WAVE_BUF_LEN) {
362             writeBytes(file, bytes, bytePos);
363             bytePos = 0;
364         }
365         sample = buffer[i];
366         bytes[bytePos++] = sample;
367         bytes[bytePos++] = sample >> 8;
368     }
369     if(bytePos != 0) {
370         writeBytes(file, bytes, bytePos);
371     }
372     return file->failed;
373 }
374