chiark / gitweb /
more automation of test logic
[disorder] / lib / wav.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20 /** @file lib/wav.c
21  * @brief WAV file support
22  *
23  * This is used by the WAV file suppoort in the tracklength plugin and
24  * by disorder-decode (see @ref server/decode.c).
25  */
26
27 /* Sources:
28  *
29  * http://www.technology.niagarac.on.ca/courses/comp530/WavFileFormat.html
30  * http://www.borg.com/~jglatt/tech/wave.htm
31  * http://www.borg.com/~jglatt/tech/aboutiff.htm
32  *
33  * These files consists of a header followed by chunks.
34  * Multibyte values are little-endian.
35  *
36  * 12 byte file header:
37  *  offset  size  meaning
38  *  00      4     'RIFF'
39  *  04      4     length of rest of file
40  *  08      4     'WAVE'
41  *
42  * The length includes 'WAVE' but excludes the 1st 8 bytes.
43  *
44  * Chunk header:
45  *  00      4     chunk ID
46  *  04      4     length of rest of chunk
47  *
48  * The stated length may be odd, if so then there is an implicit padding byte
49  * appended to the chunk to make it up to an even length (someone wasn't
50  * think about 32/64-bit worlds).
51  *
52  * Also some files seem to have extra stuff at the end of chunks that nobody
53  * I know of documents.  Go figure, but check the length field rather than
54  * deducing the length from the ID.
55  *
56  * Format chunk:
57  *  00      4     'fmt'
58  *  04      4     length of rest of chunk
59  *  08      2     compression (1 = none)
60  *  0a      2     number of channels
61  *  0c      4     samples/second
62  *  10      4     average bytes/second, = (samples/sec) * (bytes/sample)
63  *  14      2     bytes/sample
64  *  16      2     bits/sample point
65  *
66  * 'sample' means 'sample frame' above, i.e. a sample point for each channel.
67  *
68  * Data chunk:
69  *  00      4     'data'
70  *  04      4     length of rest of chunk
71  *  08      ...   data
72  *
73  * There is only allowed to be one data chunk.  Some people violate this; we
74  * shall encourage people to fix their broken WAV files by not supporting
75  * this violation and because it's easier.
76  *
77  * As to the encoding of the data:
78  *
79  * Firstly, samples up to 8 bits in size are unsigned, larger samples are
80  * signed.  Madness.
81  *
82  * Secondly sample points are stored rounded up to a multiple of 8 bits in
83  * size.  Marginally saner.
84  *
85  * Written as a single word (of 8, 16, 24, whatever bits) the padding to
86  * implement this happens at the right hand (least significant) end.
87  * e.g. assuming a 9 bit sample:
88  *
89  * |                 padded sample word              |
90  * | 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0 |
91  * |  8  7  6  5  4  3  2  1  0  -  -  -  -  -  -  - |
92  * 
93  * But this is a little-endian file format so the least significant byte is
94  * the first, which means that the padding is "between" the bits if you
95  * imagine them in their usual order:
96  *
97  *  |     first byte         |     second byte        |
98  *  | 7  6  5  4  3  2  1  0 | 7  6  5  4  3  2  1  0 |
99  *  | 0  -  -  -  -  -  -  - | 8  7  6  5  4  3  2  1 |
100  *
101  * Sample points are grouped into sample frames, consisting of as many
102  * samples points as their are channels.  It seems that there are standard
103  * orderings of different channels.
104  */
105
106 #include <config.h>
107 #include "types.h"
108
109 #include <unistd.h>
110 #include <fcntl.h>
111 #include <errno.h>
112 #include <string.h>
113
114 #include "log.h"
115 #include "wav.h"
116
117 static inline uint16_t get16(const char *ptr) {
118   return (uint8_t)ptr[0] + 256 * (uint8_t)ptr[1];
119 }
120
121 static inline uint32_t get32(const char *ptr) {
122   return (uint8_t)ptr[0] + 256 * (uint8_t)ptr[1]
123          + 65536 * (uint8_t)ptr[2] + 16777216 * (uint8_t)ptr[3];
124 }
125
126 /** @brief Open a WAV file
127  * @return 0 on success, an errno value on error
128  */
129 int wav_init(struct wavfile *f, const char *path) {
130   int err, n;
131   char header[64];
132   off_t where;
133   
134   memset(f, 0, sizeof *f);
135   f->fd = -1;
136   f->data = -1;
137   if((f->fd = open(path, O_RDONLY)) < 0) goto error_errno;
138   /* Read the file header
139    *
140    *  offset  size  meaning
141    *  00      4     'RIFF'
142    *  04      4     length of rest of file
143    *  08      4     'WAVE'
144    * */
145   if((n = pread(f->fd, header, 12, 0)) < 0) goto error_errno;
146   else if(n < 12) goto einval;
147   if(strncmp(header, "RIFF", 4) || strncmp(header + 8, "WAVE", 4))
148     goto einval;
149   f->length = 8 + get32(header + 4);
150   /* Visit all the chunks */
151   for(where = 12; where + 8 <= f->length;) {
152     /* Read the chunk header
153      *
154      *  offset  size  meaning
155      *  00      4     chunk ID
156      *  04      4     length of rest of chunk
157      */
158     if((n = pread(f->fd, header, 8, where)) < 0) goto error_errno;
159     else if(n < 8) goto einval;
160     if(!strncmp(header,"fmt ", 4)) {
161       /* This is the format chunk
162        *
163        *  offset  size  meaning
164        *  00      4     'fmt'
165        *  04      4     length of rest of chunk
166        *  08      2     compression (1 = none)
167        *  0a      2     number of channels
168        *  0c      4     samples/second
169        *  10      4     average bytes/second, = (samples/sec) * (bytes/sample)
170        *  14      2     bytes/sample
171        *  16      2     bits/sample point
172        *  18      ?     extra undocumented rubbish
173        */
174       if(get32(header + 4) < 16) goto einval;
175       if((n = pread(f->fd, header + 8, 16, where + 8)) < 0) goto error_errno;
176       else if(n < 16) goto einval;
177       f->channels = get16(header + 0x0A);
178       f->rate = get32(header + 0x0C);
179       f->bits = get16(header + 0x16);
180     } else if(!strncmp(header, "data", 4)) {
181       /* Remember where the data chunk was and how big it is */
182       f->data = where;
183       f->datasize = get32(header + 4);
184     }
185     where += 8 + get32(header + 4);
186   }
187   /* There had better have been a format chunk */
188   if(f->rate == 0) goto einval;
189   /* There had better have been a data chunk */
190   if(f->data == -1) goto einval;
191   return 0;
192 einval:
193   err = EINVAL;
194   goto error;
195 error_errno:
196   err = errno;
197 error:
198   wav_destroy(f);
199   return err;
200 }
201
202 /** @brief Close a WAV file */
203 void wav_destroy(struct wavfile *f) {
204   if(f) {
205     const int save_errno = errno;
206
207     if(f->fd >= 0)
208       close(f->fd);
209     errno = save_errno;
210   }
211 }
212
213 /** @brief Visit all the data in a WAV file
214  * @param f WAV file handle
215  * @param callback Called for successive blocks of data
216  * @param u User data
217  *
218  * @p callback will only ever be passed whole frames.
219  */
220 int wav_data(struct wavfile *f,
221              wav_data_callback *callback,
222              void *u) {
223   off_t left = f->datasize;
224   off_t where = f->data + 8;
225   char buffer[4096];
226   int err;
227   ssize_t n;
228   const size_t bytes_per_frame = f->channels * ((f->bits + 7) / 8);
229
230   while(left > 0) {
231     size_t want = (off_t)sizeof buffer > left ? (size_t)left : sizeof buffer;
232
233     want -= want % bytes_per_frame;
234     if((n = pread(f->fd, buffer, want, where)) < 0) return errno;
235     if((size_t)n < want) return EINVAL;
236     if((err = callback(f, buffer, n, u))) return err;
237     where += n;
238     left -= n;
239   }
240   return 0;
241 }
242
243 /*
244 Local Variables:
245 c-basic-offset:2
246 comment-column:40
247 fill-column:79
248 indent-tabs-mode:nil
249 End:
250 */