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 * 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 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 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 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 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