xref: /netbsd-src/usr.bin/audio/common/wav.c (revision eb1fa7eb4ae6fc2d7292d5740199a587709f7021)
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