1 /* $NetBSD: wav.c,v 1.24 2024/03/20 20:18:39 mrg Exp $ */
2
3 /*
4 * Copyright (c) 2002, 2009, 2013, 2015, 2019, 2024 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.24 2024/03/20 20:18:39 mrg 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 #include <stdbool.h>
53
54 #include "libaudio.h"
55 #include "auconv.h"
56
57 static const struct {
58 int wenc;
59 const char *wname;
60 } wavencs[] = {
61 { WAVE_FORMAT_UNKNOWN, "Microsoft Official Unknown" },
62 { WAVE_FORMAT_PCM, "Microsoft PCM" },
63 { WAVE_FORMAT_ADPCM, "Microsoft ADPCM" },
64 { WAVE_FORMAT_IEEE_FLOAT,"Microsoft IEEE Floating-Point" },
65 { WAVE_FORMAT_ALAW, "Microsoft A-law" },
66 { WAVE_FORMAT_MULAW, "Microsoft mu-law" },
67 { WAVE_FORMAT_OKI_ADPCM,"OKI ADPCM" },
68 { WAVE_FORMAT_DIGISTD, "Digistd format" },
69 { WAVE_FORMAT_DIGIFIX, "Digifix format" },
70 { -1, "?Unknown?" },
71 };
72
73 const char *
wav_enc_from_val(int encoding)74 wav_enc_from_val(int encoding)
75 {
76 int i;
77
78 for (i = 0; wavencs[i].wenc != -1; i++)
79 if (wavencs[i].wenc == encoding)
80 break;
81 return (wavencs[i].wname);
82 }
83
84 /*
85 * sample header is:
86 *
87 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
88 *
89 */
90 /*
91 * WAV format helpers
92 */
93
94 #define RIFFNAMELEN 4
95
96 static bool
find_riff_chunk(const char * search,size_t * remainp,char ** wherep,uint32_t * partlen)97 find_riff_chunk(const char *search, size_t *remainp, char **wherep, uint32_t *partlen)
98 {
99 wav_audioheaderpart part;
100
101 *partlen = 0;
102
103 #define ADJUST(l) do { \
104 if (l > *(remainp)) \
105 return false; \
106 *(wherep) += (l); \
107 *(remainp) -= (l); \
108 } while (0)
109
110 while (*remainp >= sizeof part) {
111 const char *emsg = "";
112 uint32_t len;
113
114 memcpy(&part, *wherep, sizeof part);
115 ADJUST(sizeof part);
116 len = getle32(part.len);
117 if (len % 2) {
118 emsg = " (odd length, adjusted)";
119 len += 1;
120 }
121 if (strncmp(part.name, search, RIFFNAMELEN) == 0) {
122 *partlen = len;
123 if (verbose > 1)
124 fprintf(stderr, "Found part %.04s length %d%s\n",
125 part.name, len, emsg);
126 return true;
127 }
128 ADJUST(len);
129 if (verbose > 1)
130 fprintf(stderr, "Skipping part %.04s length %d%s\n",
131 part.name, len, emsg);
132 }
133 #undef ADJUST
134
135 return false;
136 }
137
138 /*
139 * find a .wav header, etc. returns header length on success
140 */
141 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)142 audio_wav_parse_hdr(void *hdr, size_t sz, u_int *enc, u_int *prec,
143 u_int *sample, u_int *channels, off_t *datasize)
144 {
145 char *where = hdr;
146 wav_audioheaderfmt fmt;
147 wav_audiohdrextensible ext;
148 size_t remain = sz;
149 u_int newenc, newprec;
150 uint32_t len = 0;
151 u_int16_t fmttag;
152 static const char
153 strfmt[RIFFNAMELEN] = "fmt ",
154 strRIFF[RIFFNAMELEN] = "RIFF",
155 strWAVE[RIFFNAMELEN] = "WAVE",
156 strdata[RIFFNAMELEN] = "data";
157 bool found;
158
159 if (sz < 32)
160 return (AUDIO_ENOENT);
161
162 #define ADJUST(l) do { \
163 if ((l) > remain) \
164 return (AUDIO_ESHORTHDR); \
165 where += (l); \
166 remain -= (l); \
167 } while (0)
168
169 if (memcmp(where, strRIFF, sizeof strRIFF) != 0)
170 return (AUDIO_ENOENT);
171 ADJUST(sizeof strRIFF);
172 /* XXX we ignore the RIFF length here */
173 ADJUST(4);
174 if (memcmp(where, strWAVE, sizeof strWAVE) != 0)
175 return (AUDIO_ENOENT);
176 ADJUST(sizeof strWAVE);
177
178 found = find_riff_chunk(strfmt, &remain, &where, &len);
179
180 /* too short ? */
181 if (!found || remain <= sizeof fmt)
182 return (AUDIO_ESHORTHDR);
183
184 memcpy(&fmt, where, sizeof fmt);
185 fmttag = getle16(fmt.tag);
186 if (verbose)
187 printf("WAVE format tag/len: %04x/%u\n", fmttag, len);
188
189 if (fmttag == WAVE_FORMAT_EXTENSIBLE) {
190 if (len < sizeof(fmt) + sizeof(ext)) {
191 if (verbose)
192 fprintf(stderr, "short WAVE ext fmt\n");
193 return (AUDIO_ESHORTHDR);
194 }
195 if (remain <= sizeof ext + sizeof fmt) {
196 if (verbose)
197 fprintf(stderr, "WAVE ext truncated\n");
198 return (AUDIO_ESHORTHDR);
199 }
200 memcpy(&ext, where + sizeof fmt, sizeof ext);
201 fmttag = getle16(ext.sub_tag);
202 uint16_t sublen = getle16(ext.len);
203 if (verbose)
204 printf("WAVE extensible tag/len: %04x/%u\n", fmttag, sublen);
205
206 /*
207 * XXXMRG: it may be that part.len (aka sizeof fmt + sizeof ext)
208 * should equal sizeof fmt + sizeof ext.len + sublen? this block
209 * is only entered for part.len == 40, where ext.len is expected
210 * to be 22 (sizeof ext.len = 2, sizeof fmt = 16).
211 *
212 * warn about this, but don't consider it an error.
213 */
214 if (getle16(ext.len) != 22 && verbose) {
215 fprintf(stderr, "warning: WAVE ext.len %u not 22\n",
216 getle16(ext.len));
217 }
218 } else if (len < sizeof(fmt)) {
219 if (verbose)
220 fprintf(stderr, "WAVE fmt unsupported size %u\n", len);
221 return (AUDIO_EWAVUNSUPP);
222 }
223 ADJUST(len);
224
225 switch (fmttag) {
226 default:
227 return (AUDIO_EWAVUNSUPP);
228
229 case WAVE_FORMAT_PCM:
230 case WAVE_FORMAT_ADPCM:
231 case WAVE_FORMAT_OKI_ADPCM:
232 case WAVE_FORMAT_IMA_ADPCM:
233 case WAVE_FORMAT_DIGIFIX:
234 case WAVE_FORMAT_DIGISTD:
235 switch (getle16(fmt.bits_per_sample)) {
236 case 8:
237 newprec = 8;
238 break;
239 case 16:
240 newprec = 16;
241 break;
242 case 24:
243 newprec = 24;
244 break;
245 case 32:
246 newprec = 32;
247 break;
248 default:
249 return (AUDIO_EWAVBADPCM);
250 }
251 if (newprec == 8)
252 newenc = AUDIO_ENCODING_ULINEAR_LE;
253 else
254 newenc = AUDIO_ENCODING_SLINEAR_LE;
255 break;
256 case WAVE_FORMAT_ALAW:
257 newenc = AUDIO_ENCODING_ALAW;
258 newprec = 8;
259 break;
260 case WAVE_FORMAT_MULAW:
261 newenc = AUDIO_ENCODING_ULAW;
262 newprec = 8;
263 break;
264 case WAVE_FORMAT_IEEE_FLOAT:
265 switch (getle16(fmt.bits_per_sample)) {
266 case 32:
267 newenc = AUDIO_ENCODING_LIBAUDIO_FLOAT32;
268 newprec = 32;
269 break;
270 case 64:
271 newenc = AUDIO_ENCODING_LIBAUDIO_FLOAT64;
272 newprec = 32;
273 break;
274 default:
275 return (AUDIO_EWAVBADPCM);
276 }
277 break;
278 }
279
280 found = find_riff_chunk(strdata, &remain, &where, &len);
281 if (!found)
282 return (AUDIO_EWAVNODATA);
283
284 if (channels)
285 *channels = (u_int)getle16(fmt.channels);
286 if (sample)
287 *sample = getle32(fmt.sample_rate);
288 if (enc)
289 *enc = newenc;
290 if (prec)
291 *prec = newprec;
292 if (datasize)
293 *datasize = (off_t)len;
294 return (where - (char *)hdr);
295
296 #undef ADJUST
297 }
298
299
300 /*
301 * prepare a WAV header for writing; we fill in hdrp, lenp and leftp,
302 * and expect our caller (wav_write_header()) to use them.
303 */
304 int
wav_prepare_header(struct track_info * ti,void ** hdrp,size_t * lenp,int * leftp)305 wav_prepare_header(struct track_info *ti, void **hdrp, size_t *lenp, int *leftp)
306 {
307 /*
308 * WAV header we write looks like this:
309 *
310 * bytes purpose
311 * 0-3 "RIFF"
312 * 4-7 RIFF chunk length (file length minus 8)
313 * 8-15 "WAVEfmt "
314 * 16-19 format size
315 * 20-21 format tag
316 * 22-23 number of channels
317 * 24-27 sample rate
318 * 28-31 average bytes per second
319 * 32-33 block alignment
320 * 34-35 bits per sample
321 *
322 * then for ULAW and ALAW outputs, we have an extended chunk size
323 * and a WAV "fact" to add:
324 *
325 * 36-37 length of extension (== 0)
326 * 38-41 "fact"
327 * 42-45 fact size
328 * 46-49 number of samples written
329 * 50-53 "data"
330 * 54-57 data length
331 * 58- raw audio data
332 *
333 * for PCM outputs we have just the data remaining:
334 *
335 * 36-39 "data"
336 * 40-43 data length
337 * 44- raw audio data
338 *
339 * RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
340 */
341 static char wavheaderbuf[64];
342 char *p = wavheaderbuf;
343 const char *riff = "RIFF",
344 *wavefmt = "WAVEfmt ",
345 *fact = "fact",
346 *data = "data";
347 u_int32_t filelen, fmtsz, sps, abps, factsz = 4, nsample, datalen;
348 u_int16_t fmttag, nchan, align, extln = 0;
349
350 if (ti->header_info)
351 warnx("header information not supported for WAV");
352 *leftp = 0;
353
354 switch (ti->precision) {
355 case 8:
356 break;
357 case 16:
358 break;
359 case 24:
360 break;
361 case 32:
362 break;
363 default:
364 {
365 static int warned = 0;
366
367 if (warned == 0) {
368 warnx("can not support precision of %d", ti->precision);
369 warned = 1;
370 }
371 }
372 return (-1);
373 }
374
375 switch (ti->encoding) {
376 case AUDIO_ENCODING_ULAW:
377 fmttag = WAVE_FORMAT_MULAW;
378 fmtsz = 18;
379 align = ti->channels;
380 break;
381
382 case AUDIO_ENCODING_ALAW:
383 fmttag = WAVE_FORMAT_ALAW;
384 fmtsz = 18;
385 align = ti->channels;
386 break;
387
388 /*
389 * we could try to support RIFX but it seems to be more portable
390 * to output little-endian data for WAV files.
391 */
392 case AUDIO_ENCODING_ULINEAR_BE:
393 case AUDIO_ENCODING_SLINEAR_BE:
394 case AUDIO_ENCODING_ULINEAR_LE:
395 case AUDIO_ENCODING_SLINEAR_LE:
396 case AUDIO_ENCODING_PCM16:
397
398 #if BYTE_ORDER == LITTLE_ENDIAN
399 case AUDIO_ENCODING_ULINEAR:
400 case AUDIO_ENCODING_SLINEAR:
401 #endif
402 fmttag = WAVE_FORMAT_PCM;
403 fmtsz = 16;
404 align = ti->channels * (ti->precision / 8);
405 break;
406
407 default:
408 #if 0 // move into record.c, and maybe merge.c
409 {
410 static int warned = 0;
411
412 if (warned == 0) {
413 const char *s = wav_enc_from_val(ti->encoding);
414
415 if (s == NULL)
416 warnx("can not support encoding of %s", s);
417 else
418 warnx("can not support encoding of %d", ti->encoding);
419 warned = 1;
420 }
421 }
422 #endif
423 ti->format = AUDIO_FORMAT_NONE;
424 return (-1);
425 }
426
427 nchan = ti->channels;
428 sps = ti->sample_rate;
429
430 /* data length */
431 if (ti->outfd == STDOUT_FILENO)
432 datalen = 0;
433 else if (ti->total_size != -1)
434 datalen = ti->total_size;
435 else
436 datalen = 0;
437
438 /* file length */
439 filelen = 4 + (8 + fmtsz) + (8 + datalen);
440 if (fmttag != WAVE_FORMAT_PCM)
441 filelen += 8 + factsz;
442
443 abps = (double)align*ti->sample_rate / (double)1 + 0.5;
444
445 nsample = (datalen / ti->precision) / ti->sample_rate;
446
447 /*
448 * now we've calculated the info, write it out!
449 */
450 #define put32(x) do { \
451 u_int32_t _f; \
452 putle32(_f, (x)); \
453 memcpy(p, &_f, 4); \
454 } while (0)
455 #define put16(x) do { \
456 u_int16_t _f; \
457 putle16(_f, (x)); \
458 memcpy(p, &_f, 2); \
459 } while (0)
460 memcpy(p, riff, 4);
461 p += 4; /* 4 */
462 put32(filelen);
463 p += 4; /* 8 */
464 memcpy(p, wavefmt, 8);
465 p += 8; /* 16 */
466 put32(fmtsz);
467 p += 4; /* 20 */
468 put16(fmttag);
469 p += 2; /* 22 */
470 put16(nchan);
471 p += 2; /* 24 */
472 put32(sps);
473 p += 4; /* 28 */
474 put32(abps);
475 p += 4; /* 32 */
476 put16(align);
477 p += 2; /* 34 */
478 put16(ti->precision);
479 p += 2; /* 36 */
480 /* NON PCM formats have an extended chunk; write it */
481 if (fmttag != WAVE_FORMAT_PCM) {
482 put16(extln);
483 p += 2; /* 38 */
484 memcpy(p, fact, 4);
485 p += 4; /* 42 */
486 put32(factsz);
487 p += 4; /* 46 */
488 put32(nsample);
489 p += 4; /* 50 */
490 }
491 memcpy(p, data, 4);
492 p += 4; /* 40/54 */
493 put32(datalen);
494 p += 4; /* 44/58 */
495 #undef put32
496 #undef put16
497
498 *hdrp = wavheaderbuf;
499 *lenp = (p - wavheaderbuf);
500
501 return 0;
502 }
503
504 write_conv_func
wav_write_get_conv_func(struct track_info * ti)505 wav_write_get_conv_func(struct track_info *ti)
506 {
507 write_conv_func conv_func = NULL;
508
509 switch (ti->encoding) {
510
511 /*
512 * we could try to support RIFX but it seems to be more portable
513 * to output little-endian data for WAV files.
514 */
515 case AUDIO_ENCODING_ULINEAR_BE:
516 #if BYTE_ORDER == BIG_ENDIAN
517 case AUDIO_ENCODING_ULINEAR:
518 #endif
519 if (ti->precision == 16)
520 conv_func = change_sign16_swap_bytes_be;
521 else if (ti->precision == 32)
522 conv_func = change_sign32_swap_bytes_be;
523 break;
524
525 case AUDIO_ENCODING_SLINEAR_BE:
526 #if BYTE_ORDER == BIG_ENDIAN
527 case AUDIO_ENCODING_SLINEAR:
528 #endif
529 if (ti->precision == 8)
530 conv_func = change_sign8;
531 else if (ti->precision == 16)
532 conv_func = swap_bytes;
533 else if (ti->precision == 32)
534 conv_func = swap_bytes32;
535 break;
536
537 case AUDIO_ENCODING_ULINEAR_LE:
538 #if BYTE_ORDER == LITTLE_ENDIAN
539 case AUDIO_ENCODING_ULINEAR:
540 #endif
541 if (ti->precision == 16)
542 conv_func = change_sign16_le;
543 else if (ti->precision == 32)
544 conv_func = change_sign32_le;
545 break;
546
547 case AUDIO_ENCODING_SLINEAR_LE:
548 case AUDIO_ENCODING_PCM16:
549 #if BYTE_ORDER == LITTLE_ENDIAN
550 case AUDIO_ENCODING_SLINEAR:
551 #endif
552 if (ti->precision == 8)
553 conv_func = change_sign8;
554 break;
555
556 default:
557 ti->format = AUDIO_FORMAT_NONE;
558 }
559
560 return conv_func;
561 }
562