chiark / gitweb /
Don't keep audio files open except when they are actually being read
[disorder] / lib / wav.c
CommitLineData
ce6c36be 1/*
2 * This file is part of DisOrder
3 * Copyright (C) 2007 Richard Kettlewell
4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
ce6c36be 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
ce6c36be 8 * (at your option) any later version.
e7eb3a27
RK
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 *
ce6c36be 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
ce6c36be 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
05b75f8d 104#include "common.h"
ce6c36be 105
106#include <unistd.h>
107#include <fcntl.h>
108#include <errno.h>
ce6c36be 109
110#include "log.h"
111#include "wav.h"
112
113static inline uint16_t get16(const char *ptr) {
114 return (uint8_t)ptr[0] + 256 * (uint8_t)ptr[1];
115}
116
117static 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 */
125int 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);
ce6c36be 131 f->data = -1;
21237d05 132 if(hreader_init(path, f->input)) goto error_errno;
ce6c36be 133 /* Read the file header
134 *
135 * offset size meaning
136 * 00 4 'RIFF'
137 * 04 4 length of rest of file
138 * 08 4 'WAVE'
139 * */
281d0fd4 140 if((n = hreader_pread(f->input, header, 12, 0)) < 0) goto error_errno;
ce6c36be 141 else if(n < 12) goto einval;
142 if(strncmp(header, "RIFF", 4) || strncmp(header + 8, "WAVE", 4))
143 goto einval;
144 f->length = 8 + get32(header + 4);
145 /* Visit all the chunks */
146 for(where = 12; where + 8 <= f->length;) {
147 /* Read the chunk header
148 *
149 * offset size meaning
150 * 00 4 chunk ID
151 * 04 4 length of rest of chunk
152 */
281d0fd4 153 if((n = hreader_pread(f->input, header, 8, where)) < 0) goto error_errno;
ce6c36be 154 else if(n < 8) goto einval;
155 if(!strncmp(header,"fmt ", 4)) {
156 /* This is the format chunk
157 *
158 * offset size meaning
159 * 00 4 'fmt'
160 * 04 4 length of rest of chunk
161 * 08 2 compression (1 = none)
162 * 0a 2 number of channels
163 * 0c 4 samples/second
164 * 10 4 average bytes/second, = (samples/sec) * (bytes/sample)
165 * 14 2 bytes/sample
166 * 16 2 bits/sample point
167 * 18 ? extra undocumented rubbish
168 */
169 if(get32(header + 4) < 16) goto einval;
281d0fd4
RK
170 if((n = hreader_pread(f->input, header + 8, 16, where + 8)) < 0)
171 goto error_errno;
ce6c36be 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;
188einval:
189 err = EINVAL;
190 goto error;
191error_errno:
192 err = errno;
193error:
194 wav_destroy(f);
195 return err;
196}
197
198/** @brief Close a WAV file */
6ebc4527
RK
199void wav_destroy(struct wavfile *f) {
200 hreader_close(f->input);
ce6c36be 201}
202
203/** @brief Visit all the data in a WAV file
717ba987 204 * @param f WAV file handle
ce6c36be 205 * @param callback Called for successive blocks of data
717ba987 206 * @param u User data
ce6c36be 207 *
208 * @p callback will only ever be passed whole frames.
209 */
210int wav_data(struct wavfile *f,
211 wav_data_callback *callback,
212 void *u) {
213 off_t left = f->datasize;
214 off_t where = f->data + 8;
215 char buffer[4096];
216 int err;
217 ssize_t n;
218 const size_t bytes_per_frame = f->channels * ((f->bits + 7) / 8);
219
220 while(left > 0) {
221 size_t want = (off_t)sizeof buffer > left ? (size_t)left : sizeof buffer;
222
223 want -= want % bytes_per_frame;
281d0fd4 224 if((n = hreader_pread(f->input, buffer, want, where)) < 0) return errno;
ce6c36be 225 if((size_t)n < want) return EINVAL;
226 if((err = callback(f, buffer, n, u))) return err;
227 where += n;
228 left -= n;
229 }
230 return 0;
231}
232
233/*
234Local Variables:
235c-basic-offset:2
236comment-column:40
237fill-column:79
238indent-tabs-mode:nil
239End:
240*/