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