1 /* $NetBSD: wav.c,v 1.16 2023/04/15 12:39:44 mlelstv Exp $ */
2
3 /*
4 * Copyright (c) 2002, 2009, 2013, 2015, 2019 Matthew R. Green
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 /*
30 * WAV support for the audio tools; thanks go to the sox utility for
31 * clearing up issues with WAV files.
32 */
33 #include <sys/cdefs.h>
34
35 #ifndef lint
36 __RCSID("$NetBSD: wav.c,v 1.16 2023/04/15 12:39:44 mlelstv Exp $");
37 #endif
38
39
40 #include <sys/types.h>
41 #include <sys/audioio.h>
42 #include <sys/ioctl.h>
43 #include <sys/time.h>
44
45 #include <ctype.h>
46 #include <err.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <stdint.h>
51 #include <unistd.h>
52
53 #include "libaudio.h"
54 #include "auconv.h"
55
56 static const struct {
57 int wenc;
58 const char *wname;
59 } wavencs[] = {
60 { WAVE_FORMAT_UNKNOWN, "Microsoft Official Unknown" },
61 { WAVE_FORMAT_PCM, "Microsoft PCM" },
62 { WAVE_FORMAT_ADPCM, "Microsoft ADPCM" },
63 { WAVE_FORMAT_IEEE_FLOAT,"Microsoft IEEE Floating-Point" },
64 { WAVE_FORMAT_ALAW, "Microsoft A-law" },
65 { WAVE_FORMAT_MULAW, "Microsoft mu-law" },
66 { WAVE_FORMAT_OKI_ADPCM,"OKI ADPCM" },
67 { WAVE_FORMAT_DIGISTD, "Digistd format" },
68 { WAVE_FORMAT_DIGIFIX, "Digifix format" },
69 { -1, "?Unknown?" },
70 };
71
72 const char *
wav_enc_from_val(int encoding)73 wav_enc_from_val(int encoding)
74 {
75 int i;
76
77 for (i = 0; wavencs[i].wenc != -1; i++)
78 if (wavencs[i].wenc == encoding)
79 break;
80 return (wavencs[i].wname);
81 }
82
83 /*
84 * sample header is:
85 *
86 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
87 *
88 */
89 /*
90 * WAV format helpers
91 */
92 /*
93 * find a .wav header, etc. returns header length on success
94 */
95 ssize_t
audio_wav_parse_hdr(void * hdr,size_t sz,u_int * enc,u_int * prec,u_int * sample,u_int * channels,off_t * datasize)96 audio_wav_parse_hdr(void *hdr, size_t sz, u_int *enc, u_int *prec,
97 u_int *sample, u_int *channels, off_t *datasize)
98 {
99 char *where = hdr, *owhere;
100 wav_audioheaderpart part;
101 wav_audioheaderfmt fmt;
102 wav_audiohdrextensible ext;
103 char *end = (((char *)hdr) + sz);
104 u_int newenc, newprec;
105 u_int16_t fmttag;
106 static const char
107 strfmt[4] = "fmt ",
108 strRIFF[4] = "RIFF",
109 strWAVE[4] = "WAVE",
110 strdata[4] = "data";
111
112 if (sz < 32)
113 return (AUDIO_ENOENT);
114
115 if (strncmp(where, strRIFF, sizeof strRIFF))
116 return (AUDIO_ENOENT);
117 where += 8;
118 if (strncmp(where, strWAVE, sizeof strWAVE))
119 return (AUDIO_ENOENT);
120 where += 4;
121
122 do {
123 memcpy(&part, where, sizeof part);
124 owhere = where;
125 where += getle32(part.len) + 8;
126 } while (where < end && strncmp(part.name, strfmt, sizeof strfmt));
127
128 /* too short ? */
129 if (where + sizeof fmt > end)
130 return (AUDIO_ESHORTHDR);
131
132 memcpy(&fmt, (owhere + 8), sizeof fmt);
133
134 fmttag = getle16(fmt.tag);
135 if (verbose)
136 printf("WAVE format tag: %x\n", fmttag);
137
138 if (fmttag == WAVE_FORMAT_EXTENSIBLE) {
139 if ((uintptr_t)(where - owhere) < sizeof(fmt) + sizeof(ext))
140 return (AUDIO_ESHORTHDR);
141 memcpy(&ext, owhere + sizeof fmt, sizeof ext);
142 if (getle16(ext.len) < sizeof(ext) - sizeof(ext.len))
143 return (AUDIO_ESHORTHDR);
144 fmttag = getle16(ext.sub_tag);
145 if (verbose)
146 printf("WAVE extensible sub tag: %x\n", fmttag);
147 }
148
149 switch (fmttag) {
150 case WAVE_FORMAT_UNKNOWN:
151 case IBM_FORMAT_MULAW:
152 case IBM_FORMAT_ALAW:
153 case IBM_FORMAT_ADPCM:
154 default:
155 return (AUDIO_EWAVUNSUPP);
156
157 case WAVE_FORMAT_PCM:
158 case WAVE_FORMAT_ADPCM:
159 case WAVE_FORMAT_OKI_ADPCM:
160 case WAVE_FORMAT_IMA_ADPCM:
161 case WAVE_FORMAT_DIGIFIX:
162 case WAVE_FORMAT_DIGISTD:
163 switch (getle16(fmt.bits_per_sample)) {
164 case 8:
165 newprec = 8;
166 break;
167 case 16:
168 newprec = 16;
169 break;
170 case 24:
171 newprec = 24;
172 break;
173 case 32:
174 newprec = 32;
175 break;
176 default:
177 return (AUDIO_EWAVBADPCM);
178 }
179 if (newprec == 8)
180 newenc = AUDIO_ENCODING_ULINEAR_LE;
181 else
182 newenc = AUDIO_ENCODING_SLINEAR_LE;
183 break;
184 case WAVE_FORMAT_ALAW:
185 newenc = AUDIO_ENCODING_ALAW;
186 newprec = 8;
187 break;
188 case WAVE_FORMAT_MULAW:
189 newenc = AUDIO_ENCODING_ULAW;
190 newprec = 8;
191 break;
192 case WAVE_FORMAT_IEEE_FLOAT:
193 switch (getle16(fmt.bits_per_sample)) {
194 case 32:
195 newenc = AUDIO_ENCODING_LIBAUDIO_FLOAT32;
196 newprec = 32;
197 break;
198 case 64:
199 newenc = AUDIO_ENCODING_LIBAUDIO_FLOAT64;
200 newprec = 32;
201 break;
202 default:
203 return (AUDIO_EWAVBADPCM);
204 }
205 break;
206 }
207
208 do {
209 memcpy(&part, where, sizeof part);
210 owhere = where;
211 where += (getle32(part.len) + 8);
212 } while (where < end && strncmp(part.name, strdata, sizeof strdata));
213
214 if ((where - getle32(part.len)) <= end) {
215 if (channels)
216 *channels = (u_int)getle16(fmt.channels);
217 if (sample)
218 *sample = getle32(fmt.sample_rate);
219 if (enc)
220 *enc = newenc;
221 if (prec)
222 *prec = newprec;
223 if (datasize)
224 *datasize = (off_t)getle32(part.len);
225 return (owhere - (char *)hdr + 8);
226 }
227 return (AUDIO_EWAVNODATA);
228 }
229
230
231 /*
232 * prepare a WAV header for writing; we fill in hdrp, lenp and leftp,
233 * and expect our caller (wav_write_header()) to use them.
234 */
235 int
wav_prepare_header(struct track_info * ti,void ** hdrp,size_t * lenp,int * leftp)236 wav_prepare_header(struct track_info *ti, void **hdrp, size_t *lenp, int *leftp)
237 {
238 /*
239 * WAV header we write looks like this:
240 *
241 * bytes purpose
242 * 0-3 "RIFF"
243 * 4-7 file length (minus 8)
244 * 8-15 "WAVEfmt "
245 * 16-19 format size
246 * 20-21 format tag
247 * 22-23 number of channels
248 * 24-27 sample rate
249 * 28-31 average bytes per second
250 * 32-33 block alignment
251 * 34-35 bits per sample
252 *
253 * then for ULAW and ALAW outputs, we have an extended chunk size
254 * and a WAV "fact" to add:
255 *
256 * 36-37 length of extension (== 0)
257 * 38-41 "fact"
258 * 42-45 fact size
259 * 46-49 number of samples written
260 * 50-53 "data"
261 * 54-57 data length
262 * 58- raw audio data
263 *
264 * for PCM outputs we have just the data remaining:
265 *
266 * 36-39 "data"
267 * 40-43 data length
268 * 44- raw audio data
269 *
270 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
271 */
272 static char wavheaderbuf[64];
273 char *p = wavheaderbuf;
274 const char *riff = "RIFF",
275 *wavefmt = "WAVEfmt ",
276 *fact = "fact",
277 *data = "data";
278 u_int32_t filelen, fmtsz, sps, abps, factsz = 4, nsample, datalen;
279 u_int16_t fmttag, nchan, align, extln = 0;
280
281 if (ti->header_info)
282 warnx("header information not supported for WAV");
283 *leftp = 0;
284
285 switch (ti->precision) {
286 case 8:
287 break;
288 case 16:
289 break;
290 case 24:
291 break;
292 case 32:
293 break;
294 default:
295 {
296 static int warned = 0;
297
298 if (warned == 0) {
299 warnx("can not support precision of %d", ti->precision);
300 warned = 1;
301 }
302 }
303 return (-1);
304 }
305
306 switch (ti->encoding) {
307 case AUDIO_ENCODING_ULAW:
308 fmttag = WAVE_FORMAT_MULAW;
309 fmtsz = 18;
310 align = ti->channels;
311 break;
312
313 case AUDIO_ENCODING_ALAW:
314 fmttag = WAVE_FORMAT_ALAW;
315 fmtsz = 18;
316 align = ti->channels;
317 break;
318
319 /*
320 * we could try to support RIFX but it seems to be more portable
321 * to output little-endian data for WAV files.
322 */
323 case AUDIO_ENCODING_ULINEAR_BE:
324 case AUDIO_ENCODING_SLINEAR_BE:
325 case AUDIO_ENCODING_ULINEAR_LE:
326 case AUDIO_ENCODING_SLINEAR_LE:
327 case AUDIO_ENCODING_PCM16:
328
329 #if BYTE_ORDER == LITTLE_ENDIAN
330 case AUDIO_ENCODING_ULINEAR:
331 case AUDIO_ENCODING_SLINEAR:
332 #endif
333 fmttag = WAVE_FORMAT_PCM;
334 fmtsz = 16;
335 align = ti->channels * (ti->precision / 8);
336 break;
337
338 default:
339 #if 0 // move into record.c, and maybe merge.c
340 {
341 static int warned = 0;
342
343 if (warned == 0) {
344 const char *s = wav_enc_from_val(ti->encoding);
345
346 if (s == NULL)
347 warnx("can not support encoding of %s", s);
348 else
349 warnx("can not support encoding of %d", ti->encoding);
350 warned = 1;
351 }
352 }
353 #endif
354 ti->format = AUDIO_FORMAT_NONE;
355 return (-1);
356 }
357
358 nchan = ti->channels;
359 sps = ti->sample_rate;
360
361 /* data length */
362 if (ti->outfd == STDOUT_FILENO)
363 datalen = 0;
364 else if (ti->total_size != -1)
365 datalen = ti->total_size;
366 else
367 datalen = 0;
368
369 /* file length */
370 filelen = 4 + (8 + fmtsz) + (8 + datalen);
371 if (fmttag != WAVE_FORMAT_PCM)
372 filelen += 8 + factsz;
373
374 abps = (double)align*ti->sample_rate / (double)1 + 0.5;
375
376 nsample = (datalen / ti->precision) / ti->sample_rate;
377
378 /*
379 * now we've calculated the info, write it out!
380 */
381 #define put32(x) do { \
382 u_int32_t _f; \
383 putle32(_f, (x)); \
384 memcpy(p, &_f, 4); \
385 } while (0)
386 #define put16(x) do { \
387 u_int16_t _f; \
388 putle16(_f, (x)); \
389 memcpy(p, &_f, 2); \
390 } while (0)
391 memcpy(p, riff, 4);
392 p += 4; /* 4 */
393 put32(filelen);
394 p += 4; /* 8 */
395 memcpy(p, wavefmt, 8);
396 p += 8; /* 16 */
397 put32(fmtsz);
398 p += 4; /* 20 */
399 put16(fmttag);
400 p += 2; /* 22 */
401 put16(nchan);
402 p += 2; /* 24 */
403 put32(sps);
404 p += 4; /* 28 */
405 put32(abps);
406 p += 4; /* 32 */
407 put16(align);
408 p += 2; /* 34 */
409 put16(ti->precision);
410 p += 2; /* 36 */
411 /* NON PCM formats have an extended chunk; write it */
412 if (fmttag != WAVE_FORMAT_PCM) {
413 put16(extln);
414 p += 2; /* 38 */
415 memcpy(p, fact, 4);
416 p += 4; /* 42 */
417 put32(factsz);
418 p += 4; /* 46 */
419 put32(nsample);
420 p += 4; /* 50 */
421 }
422 memcpy(p, data, 4);
423 p += 4; /* 40/54 */
424 put32(datalen);
425 p += 4; /* 44/58 */
426 #undef put32
427 #undef put16
428
429 *hdrp = wavheaderbuf;
430 *lenp = (p - wavheaderbuf);
431
432 return 0;
433 }
434
435 write_conv_func
wav_write_get_conv_func(struct track_info * ti)436 wav_write_get_conv_func(struct track_info *ti)
437 {
438 write_conv_func conv_func = NULL;
439
440 switch (ti->encoding) {
441
442 /*
443 * we could try to support RIFX but it seems to be more portable
444 * to output little-endian data for WAV files.
445 */
446 case AUDIO_ENCODING_ULINEAR_BE:
447 #if BYTE_ORDER == BIG_ENDIAN
448 case AUDIO_ENCODING_ULINEAR:
449 #endif
450 if (ti->precision == 16)
451 conv_func = change_sign16_swap_bytes_be;
452 else if (ti->precision == 32)
453 conv_func = change_sign32_swap_bytes_be;
454 break;
455
456 case AUDIO_ENCODING_SLINEAR_BE:
457 #if BYTE_ORDER == BIG_ENDIAN
458 case AUDIO_ENCODING_SLINEAR:
459 #endif
460 if (ti->precision == 8)
461 conv_func = change_sign8;
462 else if (ti->precision == 16)
463 conv_func = swap_bytes;
464 else if (ti->precision == 32)
465 conv_func = swap_bytes32;
466 break;
467
468 case AUDIO_ENCODING_ULINEAR_LE:
469 #if BYTE_ORDER == LITTLE_ENDIAN
470 case AUDIO_ENCODING_ULINEAR:
471 #endif
472 if (ti->precision == 16)
473 conv_func = change_sign16_le;
474 else if (ti->precision == 32)
475 conv_func = change_sign32_le;
476 break;
477
478 case AUDIO_ENCODING_SLINEAR_LE:
479 case AUDIO_ENCODING_PCM16:
480 #if BYTE_ORDER == LITTLE_ENDIAN
481 case AUDIO_ENCODING_SLINEAR:
482 #endif
483 if (ti->precision == 8)
484 conv_func = change_sign8;
485 break;
486
487 default:
488 ti->format = AUDIO_FORMAT_NONE;
489 }
490
491 return conv_func;
492 }
493