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