1 /* $NetBSD: wav.c,v 1.15 2019/11/09 12:46:44 mrg 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.15 2019/11/09 12:46:44 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 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 32: 291 break; 292 default: 293 { 294 static int warned = 0; 295 296 if (warned == 0) { 297 warnx("can not support precision of %d", ti->precision); 298 warned = 1; 299 } 300 } 301 return (-1); 302 } 303 304 switch (ti->encoding) { 305 case AUDIO_ENCODING_ULAW: 306 fmttag = WAVE_FORMAT_MULAW; 307 fmtsz = 18; 308 align = ti->channels; 309 break; 310 311 case AUDIO_ENCODING_ALAW: 312 fmttag = WAVE_FORMAT_ALAW; 313 fmtsz = 18; 314 align = ti->channels; 315 break; 316 317 /* 318 * we could try to support RIFX but it seems to be more portable 319 * to output little-endian data for WAV files. 320 */ 321 case AUDIO_ENCODING_ULINEAR_BE: 322 case AUDIO_ENCODING_SLINEAR_BE: 323 case AUDIO_ENCODING_ULINEAR_LE: 324 case AUDIO_ENCODING_SLINEAR_LE: 325 case AUDIO_ENCODING_PCM16: 326 327 #if BYTE_ORDER == LITTLE_ENDIAN 328 case AUDIO_ENCODING_ULINEAR: 329 case AUDIO_ENCODING_SLINEAR: 330 #endif 331 fmttag = WAVE_FORMAT_PCM; 332 fmtsz = 16; 333 align = ti->channels * (ti->precision / 8); 334 break; 335 336 default: 337 #if 0 // move into record.c, and maybe merge.c 338 { 339 static int warned = 0; 340 341 if (warned == 0) { 342 const char *s = wav_enc_from_val(ti->encoding); 343 344 if (s == NULL) 345 warnx("can not support encoding of %s", s); 346 else 347 warnx("can not support encoding of %d", ti->encoding); 348 warned = 1; 349 } 350 } 351 #endif 352 ti->format = AUDIO_FORMAT_NONE; 353 return (-1); 354 } 355 356 nchan = ti->channels; 357 sps = ti->sample_rate; 358 359 /* data length */ 360 if (ti->outfd == STDOUT_FILENO) 361 datalen = 0; 362 else if (ti->total_size != -1) 363 datalen = ti->total_size; 364 else 365 datalen = 0; 366 367 /* file length */ 368 filelen = 4 + (8 + fmtsz) + (8 + datalen); 369 if (fmttag != WAVE_FORMAT_PCM) 370 filelen += 8 + factsz; 371 372 abps = (double)align*ti->sample_rate / (double)1 + 0.5; 373 374 nsample = (datalen / ti->precision) / ti->sample_rate; 375 376 /* 377 * now we've calculated the info, write it out! 378 */ 379 #define put32(x) do { \ 380 u_int32_t _f; \ 381 putle32(_f, (x)); \ 382 memcpy(p, &_f, 4); \ 383 } while (0) 384 #define put16(x) do { \ 385 u_int16_t _f; \ 386 putle16(_f, (x)); \ 387 memcpy(p, &_f, 2); \ 388 } while (0) 389 memcpy(p, riff, 4); 390 p += 4; /* 4 */ 391 put32(filelen); 392 p += 4; /* 8 */ 393 memcpy(p, wavefmt, 8); 394 p += 8; /* 16 */ 395 put32(fmtsz); 396 p += 4; /* 20 */ 397 put16(fmttag); 398 p += 2; /* 22 */ 399 put16(nchan); 400 p += 2; /* 24 */ 401 put32(sps); 402 p += 4; /* 28 */ 403 put32(abps); 404 p += 4; /* 32 */ 405 put16(align); 406 p += 2; /* 34 */ 407 put16(ti->precision); 408 p += 2; /* 36 */ 409 /* NON PCM formats have an extended chunk; write it */ 410 if (fmttag != WAVE_FORMAT_PCM) { 411 put16(extln); 412 p += 2; /* 38 */ 413 memcpy(p, fact, 4); 414 p += 4; /* 42 */ 415 put32(factsz); 416 p += 4; /* 46 */ 417 put32(nsample); 418 p += 4; /* 50 */ 419 } 420 memcpy(p, data, 4); 421 p += 4; /* 40/54 */ 422 put32(datalen); 423 p += 4; /* 44/58 */ 424 #undef put32 425 #undef put16 426 427 *hdrp = wavheaderbuf; 428 *lenp = (p - wavheaderbuf); 429 430 return 0; 431 } 432 433 write_conv_func 434 wav_write_get_conv_func(struct track_info *ti) 435 { 436 write_conv_func conv_func = NULL; 437 438 switch (ti->encoding) { 439 440 /* 441 * we could try to support RIFX but it seems to be more portable 442 * to output little-endian data for WAV files. 443 */ 444 case AUDIO_ENCODING_ULINEAR_BE: 445 #if BYTE_ORDER == BIG_ENDIAN 446 case AUDIO_ENCODING_ULINEAR: 447 #endif 448 if (ti->precision == 16) 449 conv_func = change_sign16_swap_bytes_be; 450 else if (ti->precision == 32) 451 conv_func = change_sign32_swap_bytes_be; 452 break; 453 454 case AUDIO_ENCODING_SLINEAR_BE: 455 #if BYTE_ORDER == BIG_ENDIAN 456 case AUDIO_ENCODING_SLINEAR: 457 #endif 458 if (ti->precision == 8) 459 conv_func = change_sign8; 460 else if (ti->precision == 16) 461 conv_func = swap_bytes; 462 else if (ti->precision == 32) 463 conv_func = swap_bytes32; 464 break; 465 466 case AUDIO_ENCODING_ULINEAR_LE: 467 #if BYTE_ORDER == LITTLE_ENDIAN 468 case AUDIO_ENCODING_ULINEAR: 469 #endif 470 if (ti->precision == 16) 471 conv_func = change_sign16_le; 472 else if (ti->precision == 32) 473 conv_func = change_sign32_le; 474 break; 475 476 case AUDIO_ENCODING_SLINEAR_LE: 477 case AUDIO_ENCODING_PCM16: 478 #if BYTE_ORDER == LITTLE_ENDIAN 479 case AUDIO_ENCODING_SLINEAR: 480 #endif 481 if (ti->precision == 8) 482 conv_func = change_sign8; 483 break; 484 485 default: 486 ti->format = AUDIO_FORMAT_NONE; 487 } 488 489 return conv_func; 490 } 491