/*
* Copyright (c) 2002, 2009, 2013, 2015, 2019, 2024 Matthew R. Green
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* WAV support for the audio tools; thanks go to the sox utility for
* clearing up issues with WAV files.
*/
#include <sys/cdefs.h>
#define ADJUST(l) do { \
if ((l) > remain) \
return (AUDIO_ESHORTHDR); \
where += (l); \
remain -= (l); \
} while (0)
if (memcmp(where, strRIFF, sizeof strRIFF) != 0)
return (AUDIO_ENOENT);
ADJUST(sizeof strRIFF);
/* XXX we ignore the RIFF length here */
ADJUST(4);
if (memcmp(where, strWAVE, sizeof strWAVE) != 0)
return (AUDIO_ENOENT);
ADJUST(sizeof strWAVE);
found = find_riff_chunk(strfmt, &remain, &where, &len);
/* too short ? */
if (!found || remain <= sizeof fmt)
return (AUDIO_ESHORTHDR);
memcpy(&fmt, where, sizeof fmt);
fmttag = getle16(fmt.tag);
if (verbose)
printf("WAVE format tag/len: %04x/%u\n", fmttag, len);
if (fmttag == WAVE_FORMAT_EXTENSIBLE) {
if (len < sizeof(fmt) + sizeof(ext)) {
if (verbose)
fprintf(stderr, "short WAVE ext fmt\n");
return (AUDIO_ESHORTHDR);
}
if (remain <= sizeof ext + sizeof fmt) {
if (verbose)
fprintf(stderr, "WAVE ext truncated\n");
return (AUDIO_ESHORTHDR);
}
memcpy(&ext, where + sizeof fmt, sizeof ext);
fmttag = getle16(ext.sub_tag);
uint16_t sublen = getle16(ext.len);
if (verbose)
printf("WAVE extensible tag/len: %04x/%u\n", fmttag, sublen);
/*
* XXXMRG: it may be that part.len (aka sizeof fmt + sizeof ext)
* should equal sizeof fmt + sizeof ext.len + sublen? this block
* is only entered for part.len == 40, where ext.len is expected
* to be 22 (sizeof ext.len = 2, sizeof fmt = 16).
*
* warn about this, but don't consider it an error.
*/
if (getle16(ext.len) != 22 && verbose) {
fprintf(stderr, "warning: WAVE ext.len %u not 22\n",
getle16(ext.len));
}
} else if (len < sizeof(fmt)) {
if (verbose)
fprintf(stderr, "WAVE fmt unsupported size %u\n", len);
return (AUDIO_EWAVUNSUPP);
}
ADJUST(len);
case WAVE_FORMAT_PCM:
case WAVE_FORMAT_ADPCM:
case WAVE_FORMAT_OKI_ADPCM:
case WAVE_FORMAT_IMA_ADPCM:
case WAVE_FORMAT_DIGIFIX:
case WAVE_FORMAT_DIGISTD:
switch (getle16(fmt.bits_per_sample)) {
case 8:
newprec = 8;
break;
case 16:
newprec = 16;
break;
case 24:
newprec = 24;
break;
case 32:
newprec = 32;
break;
default:
return (AUDIO_EWAVBADPCM);
}
if (newprec == 8)
newenc = AUDIO_ENCODING_ULINEAR_LE;
else
newenc = AUDIO_ENCODING_SLINEAR_LE;
break;
case WAVE_FORMAT_ALAW:
newenc = AUDIO_ENCODING_ALAW;
newprec = 8;
break;
case WAVE_FORMAT_MULAW:
newenc = AUDIO_ENCODING_ULAW;
newprec = 8;
break;
case WAVE_FORMAT_IEEE_FLOAT:
switch (getle16(fmt.bits_per_sample)) {
case 32:
newenc = AUDIO_ENCODING_LIBAUDIO_FLOAT32;
newprec = 32;
break;
case 64:
newenc = AUDIO_ENCODING_LIBAUDIO_FLOAT64;
newprec = 32;
break;
default:
return (AUDIO_EWAVBADPCM);
}
break;
}
found = find_riff_chunk(strdata, &remain, &where, &len);
if (!found)
return (AUDIO_EWAVNODATA);
if (channels)
*channels = (u_int)getle16(fmt.channels);
if (sample)
*sample = getle32(fmt.sample_rate);
if (enc)
*enc = newenc;
if (prec)
*prec = newprec;
if (datasize)
*datasize = (off_t)len;
return (where - (char *)hdr);
#undef ADJUST
}
/*
* prepare a WAV header for writing; we fill in hdrp, lenp and leftp,
* and expect our caller (wav_write_header()) to use them.
*/
int
wav_prepare_header(struct track_info *ti, void **hdrp, size_t *lenp, int *leftp)
{
/*
* WAV header we write looks like this:
*
* bytes purpose
* 0-3 "RIFF"
* 4-7 RIFF chunk length (file length minus 8)
* 8-15 "WAVEfmt "
* 16-19 format size
* 20-21 format tag
* 22-23 number of channels
* 24-27 sample rate
* 28-31 average bytes per second
* 32-33 block alignment
* 34-35 bits per sample
*
* then for ULAW and ALAW outputs, we have an extended chunk size
* and a WAV "fact" to add:
*
* 36-37 length of extension (== 0)
* 38-41 "fact"
* 42-45 fact size
* 46-49 number of samples written
* 50-53 "data"
* 54-57 data length
* 58- raw audio data
*
* for PCM outputs we have just the data remaining:
*
* 36-39 "data"
* 40-43 data length
* 44- raw audio data
*
* RIFF\^@^C^@WAVEfmt ^P^@^@^@^A^@^B^@D<AC>^@^@^P<B1>^B^@^D^@^P^@data^@^@^C^@^@^@^@^@^@^@^@^@^@
*/
static char wavheaderbuf[64];
char *p = wavheaderbuf;
const char *riff = "RIFF",
*wavefmt = "WAVEfmt ",
*fact = "fact",
*data = "data";
u_int32_t filelen, fmtsz, sps, abps, factsz = 4, nsample, datalen;
u_int16_t fmttag, nchan, align, extln = 0;
if (ti->header_info)
warnx("header information not supported for WAV");
*leftp = 0;
switch (ti->precision) {
case 8:
break;
case 16:
break;
case 24:
break;
case 32:
break;
default:
{
static int warned = 0;
if (warned == 0) {
warnx("can not support precision of %d", ti->precision);
warned = 1;
}
}
return (-1);
}
/*
* we could try to support RIFX but it seems to be more portable
* to output little-endian data for WAV files.
*/
case AUDIO_ENCODING_ULINEAR_BE:
case AUDIO_ENCODING_SLINEAR_BE:
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_PCM16:
default:
#if 0 // move into record.c, and maybe merge.c
{
static int warned = 0;
if (warned == 0) {
const char *s = wav_enc_from_val(ti->encoding);
if (s == NULL)
warnx("can not support encoding of %s", s);
else
warnx("can not support encoding of %d", ti->encoding);
warned = 1;
}
}
#endif
ti->format = AUDIO_FORMAT_NONE;
return (-1);
}
nchan = ti->channels;
sps = ti->sample_rate;
/* data length */
if (ti->outfd == STDOUT_FILENO)
datalen = 0;
else if (ti->total_size != -1)
datalen = ti->total_size;
else
datalen = 0;
/*
* we could try to support RIFX but it seems to be more portable
* to output little-endian data for WAV files.
*/
case AUDIO_ENCODING_ULINEAR_BE:
#if BYTE_ORDER == BIG_ENDIAN
case AUDIO_ENCODING_ULINEAR:
#endif
if (ti->precision == 16)
conv_func = change_sign16_swap_bytes_be;
else if (ti->precision == 32)
conv_func = change_sign32_swap_bytes_be;
break;
case AUDIO_ENCODING_SLINEAR_BE:
#if BYTE_ORDER == BIG_ENDIAN
case AUDIO_ENCODING_SLINEAR:
#endif
if (ti->precision == 8)
conv_func = change_sign8;
else if (ti->precision == 16)
conv_func = swap_bytes;
else if (ti->precision == 32)
conv_func = swap_bytes32;
break;
case AUDIO_ENCODING_ULINEAR_LE:
#if BYTE_ORDER == LITTLE_ENDIAN
case AUDIO_ENCODING_ULINEAR:
#endif
if (ti->precision == 16)
conv_func = change_sign16_le;
else if (ti->precision == 32)
conv_func = change_sign32_le;
break;
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_PCM16:
#if BYTE_ORDER == LITTLE_ENDIAN
case AUDIO_ENCODING_SLINEAR:
#endif
if (ti->precision == 8)
conv_func = change_sign8;
break;