#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: wav.c,v 1.24 2024/03/20 20:18:39 mrg Exp $");
#endif
#include <sys/types.h>
#include <sys/audioio.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <ctype.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <stdbool.h>
#include "libaudio.h"
#include "auconv.h"
static const struct {
int wenc;
const char *wname;
} wavencs[] = {
{ WAVE_FORMAT_UNKNOWN, "Microsoft Official Unknown" },
{ WAVE_FORMAT_PCM, "Microsoft PCM" },
{ WAVE_FORMAT_ADPCM, "Microsoft ADPCM" },
{ WAVE_FORMAT_IEEE_FLOAT,"Microsoft IEEE Floating-Point" },
{ WAVE_FORMAT_ALAW, "Microsoft A-law" },
{ WAVE_FORMAT_MULAW, "Microsoft mu-law" },
{ WAVE_FORMAT_OKI_ADPCM,"OKI ADPCM" },
{ WAVE_FORMAT_DIGISTD, "Digistd format" },
{ WAVE_FORMAT_DIGIFIX, "Digifix format" },
{ -1, "?Unknown?" },
};
const char *
wav_enc_from_val(int encoding)
{
int i;
for (i = 0; wavencs[i].wenc != -1; i++)
if (wavencs[i].wenc == encoding)
break;
return (wavencs[i].wname);
}
#define RIFFNAMELEN 4
static bool
find_riff_chunk(const char *search, size_t *remainp, char **wherep, uint32_t *partlen)
{
wav_audioheaderpart part;
*partlen = 0;
#define ADJUST(l) do { \
if (l > *(remainp)) \
return false; \
*(wherep) += (l); \
*(remainp) -= (l); \
} while (0)
while (*remainp >= sizeof part) {
const char *emsg = "";
uint32_t len;
memcpy(&part, *wherep, sizeof part);
ADJUST(sizeof part);
len = getle32(part.len);
if (len % 2) {
emsg = " (odd length, adjusted)";
len += 1;
}
if (strncmp(part.name, search, RIFFNAMELEN) == 0) {
*partlen = len;
if (verbose > 1)
fprintf(stderr, "Found part %.04s length %d%s\n",
part.name, len, emsg);
return true;
}
ADJUST(len);
if (verbose > 1)
fprintf(stderr, "Skipping part %.04s length %d%s\n",
part.name, len, emsg);
}
#undef ADJUST
return false;
}
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)
{
char *where = hdr;
wav_audioheaderfmt fmt;
wav_audiohdrextensible ext;
size_t remain = sz;
u_int newenc, newprec;
uint32_t len = 0;
u_int16_t fmttag;
static const char
strfmt[RIFFNAMELEN] = "fmt ",
strRIFF[RIFFNAMELEN] = "RIFF",
strWAVE[RIFFNAMELEN] = "WAVE",
strdata[RIFFNAMELEN] = "data";
bool found;
if (sz < 32)
return (AUDIO_ENOENT);
#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);
ADJUST(4);
if (memcmp(where, strWAVE, sizeof strWAVE) != 0)
return (AUDIO_ENOENT);
ADJUST(sizeof strWAVE);
found = find_riff_chunk(strfmt, &remain, &where, &len);
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);
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);
switch (fmttag) {
default:
return (AUDIO_EWAVUNSUPP);
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
}
int
wav_prepare_header(struct track_info *ti, void **hdrp, size_t *lenp, int *leftp)
{
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);
}
switch (ti->encoding) {
case AUDIO_ENCODING_ULAW:
fmttag = WAVE_FORMAT_MULAW;
fmtsz = 18;
align = ti->channels;
break;
case AUDIO_ENCODING_ALAW:
fmttag = WAVE_FORMAT_ALAW;
fmtsz = 18;
align = ti->channels;
break;
case AUDIO_ENCODING_ULINEAR_BE:
case AUDIO_ENCODING_SLINEAR_BE:
case AUDIO_ENCODING_ULINEAR_LE:
case AUDIO_ENCODING_SLINEAR_LE:
case AUDIO_ENCODING_PCM16:
#if BYTE_ORDER == LITTLE_ENDIAN
case AUDIO_ENCODING_ULINEAR:
case AUDIO_ENCODING_SLINEAR:
#endif
fmttag = WAVE_FORMAT_PCM;
fmtsz = 16;
align = ti->channels * (ti->precision / 8);
break;
default:
#if 0
{
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;
if (ti->outfd == STDOUT_FILENO)
datalen = 0;
else if (ti->total_size != -1)
datalen = ti->total_size;
else
datalen = 0;
filelen = 4 + (8 + fmtsz) + (8 + datalen);
if (fmttag != WAVE_FORMAT_PCM)
filelen += 8 + factsz;
abps = (double)align*ti->sample_rate / (double)1 + 0.5;
nsample = (datalen / ti->precision) / ti->sample_rate;
#define put32(x) do { \
u_int32_t _f; \
putle32(_f, (x)); \
memcpy(p, &_f, 4); \
} while (0)
#define put16(x) do { \
u_int16_t _f; \
putle16(_f, (x)); \
memcpy(p, &_f, 2); \
} while (0)
memcpy(p, riff, 4);
p += 4;
put32(filelen);
p += 4;
memcpy(p, wavefmt, 8);
p += 8;
put32(fmtsz);
p += 4;
put16(fmttag);
p += 2;
put16(nchan);
p += 2;
put32(sps);
p += 4;
put32(abps);
p += 4;
put16(align);
p += 2;
put16(ti->precision);
p += 2;
if (fmttag != WAVE_FORMAT_PCM) {
put16(extln);
p += 2;
memcpy(p, fact, 4);
p += 4;
put32(factsz);
p += 4;
put32(nsample);
p += 4;
}
memcpy(p, data, 4);
p += 4;
put32(datalen);
p += 4;
#undef put32
#undef put16
*hdrp = wavheaderbuf;
*lenp = (p - wavheaderbuf);
return 0;
}
write_conv_func
wav_write_get_conv_func(struct track_info *ti)
{
write_conv_func conv_func = NULL;
switch (ti->encoding) {
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;
default:
ti->format = AUDIO_FORMAT_NONE;
}
return conv_func;
}