chiark / gitweb /
Fiddle with CSS+HTML in effort to get more consistent buttons
[disorder] / lib / wav.c
CommitLineData
ce6c36be 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
05b75f8d 106#include "common.h"
ce6c36be 107
108#include <unistd.h>
109#include <fcntl.h>
110#include <errno.h>
ce6c36be 111
112#include "log.h"
113#include "wav.h"
114
115static inline uint16_t get16(const char *ptr) {
116 return (uint8_t)ptr[0] + 256 * (uint8_t)ptr[1];
117}
118
119static inline uint32_t get32(const char *ptr) {
120 return (uint8_t)ptr[0] + 256 * (uint8_t)ptr[1]
121 + 65536 * (uint8_t)ptr[2] + 16777216 * (uint8_t)ptr[3];
122}
123
124/** @brief Open a WAV file
125 * @return 0 on success, an errno value on error
126 */
127int wav_init(struct wavfile *f, const char *path) {
128 int err, n;
129 char header[64];
130 off_t where;
131
132 memset(f, 0, sizeof *f);
133 f->fd = -1;
134 f->data = -1;
135 if((f->fd = open(path, O_RDONLY)) < 0) goto error_errno;
136 /* Read the file header
137 *
138 * offset size meaning
139 * 00 4 'RIFF'
140 * 04 4 length of rest of file
141 * 08 4 'WAVE'
142 * */
143 if((n = pread(f->fd, header, 12, 0)) < 0) goto error_errno;
144 else if(n < 12) goto einval;
145 if(strncmp(header, "RIFF", 4) || strncmp(header + 8, "WAVE", 4))
146 goto einval;
147 f->length = 8 + get32(header + 4);
148 /* Visit all the chunks */
149 for(where = 12; where + 8 <= f->length;) {
150 /* Read the chunk header
151 *
152 * offset size meaning
153 * 00 4 chunk ID
154 * 04 4 length of rest of chunk
155 */
156 if((n = pread(f->fd, header, 8, where)) < 0) goto error_errno;
157 else if(n < 8) goto einval;
158 if(!strncmp(header,"fmt ", 4)) {
159 /* This is the format chunk
160 *
161 * offset size meaning
162 * 00 4 'fmt'
163 * 04 4 length of rest of chunk
164 * 08 2 compression (1 = none)
165 * 0a 2 number of channels
166 * 0c 4 samples/second
167 * 10 4 average bytes/second, = (samples/sec) * (bytes/sample)
168 * 14 2 bytes/sample
169 * 16 2 bits/sample point
170 * 18 ? extra undocumented rubbish
171 */
172 if(get32(header + 4) < 16) goto einval;
173 if((n = pread(f->fd, header + 8, 16, where + 8)) < 0) goto error_errno;
174 else if(n < 16) goto einval;
175 f->channels = get16(header + 0x0A);
176 f->rate = get32(header + 0x0C);
177 f->bits = get16(header + 0x16);
178 } else if(!strncmp(header, "data", 4)) {
179 /* Remember where the data chunk was and how big it is */
180 f->data = where;
181 f->datasize = get32(header + 4);
182 }
183 where += 8 + get32(header + 4);
184 }
185 /* There had better have been a format chunk */
186 if(f->rate == 0) goto einval;
187 /* There had better have been a data chunk */
188 if(f->data == -1) goto einval;
189 return 0;
190einval:
191 err = EINVAL;
192 goto error;
193error_errno:
194 err = errno;
195error:
196 wav_destroy(f);
197 return err;
198}
199
200/** @brief Close a WAV file */
201void wav_destroy(struct wavfile *f) {
202 if(f) {
203 const int save_errno = errno;
204
205 if(f->fd >= 0)
206 close(f->fd);
207 errno = save_errno;
208 }
209}
210
211/** @brief Visit all the data in a WAV file
717ba987 212 * @param f WAV file handle
ce6c36be 213 * @param callback Called for successive blocks of data
717ba987 214 * @param u User data
ce6c36be 215 *
216 * @p callback will only ever be passed whole frames.
217 */
218int wav_data(struct wavfile *f,
219 wav_data_callback *callback,
220 void *u) {
221 off_t left = f->datasize;
222 off_t where = f->data + 8;
223 char buffer[4096];
224 int err;
225 ssize_t n;
226 const size_t bytes_per_frame = f->channels * ((f->bits + 7) / 8);
227
228 while(left > 0) {
229 size_t want = (off_t)sizeof buffer > left ? (size_t)left : sizeof buffer;
230
231 want -= want % bytes_per_frame;
232 if((n = pread(f->fd, buffer, want, where)) < 0) return errno;
233 if((size_t)n < want) return EINVAL;
234 if((err = callback(f, buffer, n, u))) return err;
235 where += n;
236 left -= n;
237 }
238 return 0;
239}
240
241/*
242Local Variables:
243c-basic-offset:2
244comment-column:40
245fill-column:79
246indent-tabs-mode:nil
247End:
248*/