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