git: 9front

Download patch

ref: 8934936a63634f658769d86f645621d235d416cb
parent: 2eed9bfe084e7c24ae0bbe776e8e26b4da814707
author: cinap_lenrek <cinap_lenrek@gmx.de>
date: Sat Dec 8 03:26:50 EST 2012

audio: add audio/pcmconv program

instead of duplicating resampling and pcm format
conversion code, put it in the new pcmconv program.

--- a/sys/man/1/audio
+++ b/sys/man/1/audio
@@ -1,6 +1,6 @@
 .TH AUDIO 1
 .SH NAME
-mp3dec, mp3enc, oggdec, oggenc, flacdec, wavdec \- decode and encode audio files
+mp3dec, mp3enc, oggdec, oggenc, flacdec, wavdec, pcmconv \- decode and encode audio files
 .SH SYNOPSIS
 .B audio/mp3dec
 [
@@ -39,6 +39,18 @@
 .I "long or silly options"
 ]
 .PP
+.B audio/pcmconv
+[
+.B -i
+.I fmt
+] [
+.B -o
+.I fmt
+] [
+.B -l
+.I length
+]
+.PP
 .SH DESCRIPTION
 These programs decode and encode various audio formats from and to
 16-bit stereo PCM (little endian). The decoders read the compressed
@@ -59,6 +71,7 @@
 .PP
 The encoders read PCM on standard input and produce compressed audio
 on standard output.
+.PP
 .I Oggenc
 and
 .I mp3enc
@@ -195,7 +208,48 @@
 .TP
 .BI --voice
 experimental voice mode
-.
+
+.PP
+.I Pcmconv
+is a helper program used to convert various PCM sample formats. The
+.B -i
+and
+.B -o
+options specify the input and output format
+.I fmt
+of the conversion.
+.I Fmt
+is a concatinated string of the following parts:
+
+.TP
+.BI s #
+sample format is little endian signed integer where
+.I #
+specifies the number of bits
+.TP
+.BI u #
+unsigned little endian integer format
+.TP
+.BI f #
+floating point format where
+.I #
+has to be 32 or 64 for single or double precisition
+.TP
+.BI c #
+specifies the number of channels
+.TP
+.BI r #
+gives the samplerate in Hz
+
+.PP
+The program reads samples from standard
+input converting the data and writes the result to standard output
+until it reached end of file or, if
+.B -l
+was given, a number of
+.I length
+bytes have been consumed from input.
+
 .SH EXAMPLE
 Play back an
 .L .mp3
--- a/sys/src/cmd/audio/flacdec/flacdec.c
+++ b/sys/src/cmd/audio/flacdec/flacdec.c
@@ -1,92 +1,8 @@
 #include <stdio.h>
+#include <unistd.h>
 #include <stdlib.h>
 #include "FLAC/stream_decoder.h"
 
-int rate = 44100;
-
-typedef unsigned long ulong;
-typedef unsigned char uchar;
-typedef long long vlong;
-
-typedef struct Chan Chan;
-struct Chan
-{
-	ulong		phase;
-	FLAC__int32	last;
-	FLAC__int32	rand;
-};
-
-enum
-{
-	OutBits = 16,
-	Max = 32767,
-	Min = -32768,
-};
-
-#define PRNG(x) (((x)*0x19660dL + 0x3c6ef35fL) & 0xffffffffL)
-
-static uchar*
-resample(Chan *c, FLAC__int32 *src, uchar *dst, int mono, ulong delta, ulong count, ulong bps)
-{
-	FLAC__int32 last, val, out, rand;
-	ulong phase, pos, scale, lowmask, lowmask2;
-	vlong v;
-
-	scale = 0;
-	if(bps > OutBits){
-		scale = bps - OutBits;
-		lowmask = (1<<scale)-1;
-		lowmask2 = lowmask/2;
-	}
-
-	rand = c->rand;
-	last = c->last;
-	phase = c->phase;
-	pos = phase >> 16;
-	while(pos < count){
-		val = src[pos];
-		if(pos)
-			last = src[pos-1];
-
-		/* interpolate */
-		v = val;
-		v -= last;
-		v *= (phase & 0xFFFF);
-		out = last + (v >> 16);
-
-		/* scale / dithering */
-		if(scale){
-			out += (rand & lowmask) - lowmask2;
-			rand = PRNG(rand);
-			out >>= scale;
-		}
-
-		/* cliping */
-		if(out > Max)
-			out = Max;
-		else if(out < Min)
-			out = Min;
-
-		*dst++ = out;
-		*dst++ = out >> 8;
-		if(mono){
-			*dst++ = out;
-			*dst++ = out >> 8;
-		} else
-			dst += 2;
-		phase += delta;
-		pos = phase >> 16;
-	}
-	c->rand = rand;
-	c->last = val;
-	if(delta < 0x10000)
-		c->phase = phase & 0xFFFF;
-	else
-		c->phase = phase - (count << 16);
-
-	return dst;
-}
-
 static FLAC__StreamDecoderReadStatus
 decinput(FLAC__StreamDecoder *dec, FLAC__byte buffer[], unsigned *bytes, void *client_data)
 {
@@ -105,24 +21,80 @@
 static FLAC__StreamDecoderWriteStatus
 decoutput(FLAC__StreamDecoder *dec, FLAC__Frame *frame, FLAC__int32 *buffer[], void *client_data)
 {
-	static uchar *buf;
-	static int nbuf;
-	static Chan c1, c0;
-	ulong length, n, delta, bps;
-	uchar *p;
+	static int rate, chans, bits;
+	static unsigned char *buf;
+	static int nbuf, ifd = -1;
+	FLAC__int32 *s, v;
+	unsigned char *p;
+	int i, j, n, b, len;
 
-	bps = frame->header.bits_per_sample;
-	length = frame->header.blocksize;
-	delta = (frame->header.sample_rate << 16) / rate;
-	n = 4 * (frame->header.sample_rate + length * rate) / frame->header.sample_rate;
+	/* start converter if format changed */
+	if(rate != frame->header.sample_rate
+	|| chans != frame->header.channels
+	|| bits != frame->header.bits_per_sample){
+		int pid, pfd[2];
+		char fmt[32];
+
+		rate = frame->header.sample_rate;
+		chans = frame->header.channels;
+		bits = frame->header.bits_per_sample;
+		sprintf(fmt, "s%dr%dc%d", bits, rate, chans);
+
+		if(ifd >= 0)
+			close(ifd);
+		if(pipe(pfd) < 0){
+			fprintf(stderr, "Error creating pipe\n");
+			exit(1);
+		}
+		pid = fork();
+		if(pid < 0){
+			fprintf(stderr, "Error forking\n");
+			exit(1);
+		}
+		if(pid == 0){
+			dup2(pfd[1], 0);
+			close(pfd[1]);
+			close(pfd[0]);
+			execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, 0);
+			fprintf(stderr, "Error executing converter\n");
+			exit(1);
+		}
+		close(pfd[1]);
+		ifd = pfd[0];
+	}
+	len = frame->header.blocksize;
+	b = (bits+7)/8;
+	n = b * chans * len;
 	if(n > nbuf){
 		nbuf = n;
 		buf = realloc(buf, nbuf);
+		if(buf == NULL){
+			fprintf(stderr, "Error allocating memory\n");
+			exit(1);
+		}
 	}
-	if(frame->header.channels == 2)
-		resample(&c1, buffer[1], buf+2, 0, delta, length, bps);
-	p = resample(&c0, buffer[0], buf, frame->header.channels == 1, delta, length, bps);
-	fwrite(buf, p-buf, 1, stdout);
+	p = buf;
+	for(j=0; j < chans; j++){
+		s = buffer[j];
+		p = buf + j*b;
+		for(i=0; i < len; i++){
+			n = 0;
+			v = *s++;
+			switch(b){
+			case 4:
+				p[n++] = v, v>>=8;
+			case 3:
+				p[n++] = v, v>>=8;
+			case 2:
+				p[n++] = v, v>>=8;
+			case 1:
+				p[n] = v;
+			}
+			p += chans*b;
+		}
+	}
+	if(p > buf)
+		write(ifd, buf, p - buf);
 	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
 }
 
--- a/sys/src/cmd/audio/libFLAC/FLAC/stream_decoder.h
+++ b/sys/src/cmd/audio/libFLAC/FLAC/stream_decoder.h
@@ -396,7 +396,7 @@
  * \retval FLAC__StreamDecoder*
  *    \c NULL if there was an error allocating memory, else the new instance.
  */
-FLAC_API FLAC__StreamDecoder *FLAC__stream_decoder_new();
+FLAC_API FLAC__StreamDecoder *FLAC__stream_decoder_new(void);
 
 /** Free a decoder instance.  Deletes the object pointed to by \a decoder.
  *
--- a/sys/src/cmd/audio/mkfile
+++ b/sys/src/cmd/audio/mkfile
@@ -1,7 +1,7 @@
 </$objtype/mkfile
 
 LIBS=libogg libvorbis libFLAC
-PROGS=oggdec oggenc mp3dec mp3enc flacdec wavdec
+PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec wavdec
 #libs must be made first
 DIRS=$LIBS $PROGS
 
--- a/sys/src/cmd/audio/mp3dec/main.c
+++ b/sys/src/cmd/audio/mp3dec/main.c
@@ -7,14 +7,14 @@
 
 /* Current input file */
 vlong offset;
-int rate = 44100;
 int debug = 0;
+int ifd = -1;
 
 static enum mad_flow
 input(void *, struct mad_stream *stream)
 {
-	int fd, n, m;
 	static uchar buf[32768];
+	int fd, n, m;
 
 	n = stream->bufend - stream->next_frame;
 	memmove(buf, stream->next_frame, n);
@@ -29,99 +29,67 @@
 	return MAD_FLOW_CONTINUE;
 }
 
-typedef struct Chan Chan;
-struct Chan
-{
-	ulong		phase;
-	mad_fixed_t	last;
-	mad_fixed_t	rand;
-};
-
-#define PRNG(x) (((x)*0x19660dL + 0x3c6ef35fL) & 0xffffffffL)
-
-enum
-{
-	FracBits = MAD_F_FRACBITS,
-	OutBits = 16,
-	ScaleBits = FracBits + 1 - OutBits,
-	LowMask  = (1<<ScaleBits) - 1,
-	Min = -MAD_F_ONE,
-	Max = MAD_F_ONE - 1,
-};
-
-static uchar*
-resample(Chan *c, mad_fixed_t *src, uchar *dst, int mono, ulong delta, ulong count)
-{
-	mad_fixed_t last, val, out, rand;
-	ulong phase, pos;
-	vlong v;
-
-	rand = c->rand;
-	last = c->last;
-	phase = c->phase;
-	pos = phase >> 16;
-	while(pos < count){
-		val = src[pos];
-		if(pos)
-			last = src[pos-1];
-
-		/* interpolate */
-		v = val;
-		v -= last;
-		v *= (phase & 0xFFFF);
-		out = last + (v >> 16);
-
-		/* dithering */
-		out += (rand & LowMask) - LowMask/2;
-		rand = PRNG(rand);
-
-		/* cliping */
-		if(out > Max)
-			out = Max;
-		else if(out < Min)
-			out = Min;
-
-		out >>= ScaleBits;
-
-		*dst++ = out;
-		*dst++ = out >> 8;
-		if(mono){
-			*dst++ = out;
-			*dst++ = out >> 8;
-		} else
-			dst += 2;
-		phase += delta;
-		pos = phase >> 16;
-	}
-	c->rand = rand;
-	c->last = val;
-	if(delta < 0x10000)
-		c->phase = phase & 0xFFFF;
-	else
-		c->phase = phase - (count << 16);
-
-	return dst;
-}
-
 static enum mad_flow
 output(void *, struct mad_header const* header, struct mad_pcm *pcm)
 {
+	static int rate, chans;
 	static uchar *buf;
 	static int nbuf;
-	static Chan c1, c0;
-	ulong n, delta;
+	mad_fixed_t v, *s;
+	int i, j, n;
 	uchar *p;
 
-	delta = (pcm->samplerate << 16) / rate;
-	n = 4 * (pcm->samplerate + pcm->length * rate) / pcm->samplerate;
+	/* start converter if format changed */
+	if(rate != pcm->samplerate || chans != pcm->channels){
+		int pid, pfd[2];
+		char fmt[32];
+
+		rate = pcm->samplerate;
+		chans = pcm->channels;
+		snprint(fmt, sizeof(fmt), "s32r%dc%d", rate, chans);
+
+		if(ifd >= 0){
+			close(ifd);
+			waitpid();
+		}
+		if(pipe(pfd) < 0)
+			sysfatal("pipe: %r");
+		pid = fork();
+		if(pid < 0)
+			sysfatal("fork: %r");
+		if(pid == 0){
+			dup(pfd[1], 0);
+			close(pfd[1]);
+			close(pfd[0]);
+			execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, 0);
+			sysfatal("exec: %r");
+		}
+		close(pfd[1]);
+		ifd = pfd[0];
+	}
+	n = 4 * chans * pcm->length;
 	if(n > nbuf){
 		nbuf = n;
 		buf = realloc(buf, nbuf);
+		if(buf == nil)
+			sysfatal("realloc: %r");
 	}
-	if(pcm->channels == 2)
-		resample(&c1, pcm->samples[1], buf+2, 0, delta, pcm->length);
-	p = resample(&c0, pcm->samples[0], buf, pcm->channels == 1, delta, pcm->length);
-	write(1, buf, p-buf);
+	p = buf;
+	for(j=0; j < chans; j++){
+		s = pcm->samples[j];
+		n = pcm->length;
+		p = buf + j*4;
+		for(i=0; i < n; i++){
+			v = *s++;
+			p[0] = v, v>>=8;
+			p[1] = v, v>>=8;
+			p[2] = v, v>>=8;
+			p[3] = v;
+			p += chans*4;
+		}
+	}
+	if(p > buf)
+		write(ifd, buf, p - buf);
 	return MAD_FLOW_CONTINUE;
 }
 
@@ -175,5 +143,11 @@
 	mad_decoder_init(&decoder, nil, input, nil, nil, output, error, nil);
 	mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
 	mad_decoder_finish(&decoder);
+
+	if(ifd >= 0){
+		close(ifd);
+		waitpid();
+	}
+
 	exits(0);
 }
--- a/sys/src/cmd/audio/oggdec/mkfile
+++ b/sys/src/cmd/audio/oggdec/mkfile
@@ -4,7 +4,7 @@
 TARGET=oggdec
 
 CC=pcc
-CFLAGS=-I../libvorbis -I../libogg
+CFLAGS=-I../libvorbis -I../libogg -D_POSIX_SOURCE
 
 %.$O: %.c
 	$CC $CFLAGS -c $stem.c
--- a/sys/src/cmd/audio/oggdec/oggdec.c
+++ b/sys/src/cmd/audio/oggdec/oggdec.c
@@ -22,85 +22,72 @@
 /* Note that this is POSIX, not ANSI code */
 
 #include <stdio.h>
+#include <unistd.h>
 #include <stdlib.h>
 #include <math.h>
 #include <vorbis/codec.h>
 
-int rate = 44100;
-
-enum {
-	Max = 32767,
-	Min = -32768,
-};
-
-typedef unsigned long ulong;
-typedef unsigned char uchar;
-typedef struct Chan Chan;
-
-struct Chan
-{
-	unsigned long	phase;
-	float		last;
-};
-
-static uchar*
-resample(Chan *c, float *src, uchar *dst, int mono, ulong delta, ulong count)
-{
-	float f, last, val;
-	ulong phase, pos;
-	int out;
-	
-	last = c->last;
-	phase = c->phase;
-	pos = phase >> 16;
-	while(pos < count){
-		val = src[pos];
-		if(pos)
-			last = src[pos-1];
-		f = (float)(phase&0xFFFF)/0x10000;
-		out = (last + (val - last) * f) * 32767.f;
-		/* cliping */
-		if(out > Max)
-			out = Max;
-		else if(out < Min)
-			out = Min;
-		*dst++ = out;
-		*dst++ = out >> 8;
-		if(mono){
-			*dst++ = out;
-			*dst++ = out >> 8;
-		} else
-			dst += 2;
-		phase += delta;
-		pos = phase >> 16;
-	}
-	c->last = val;
-	if(delta < 0x10000)
-		c->phase = phase & 0xFFFF;
-	else
-		c->phase = phase - (count << 16);
-	return dst;
-}
-
 static void
 output(float **pcm, int samples, vorbis_info *vi)
 {
-	static uchar *buf;
-	static int nbuf;
-	static Chan c1, c0;
-	ulong n, delta;
-	uchar *p;
+	static int rate, chans;
+	static unsigned char *buf;
+	static int nbuf, ifd = -1;
+	unsigned char *p;
+	int i, j, n, v;
+	float *s;
 
-	delta = ((ulong)vi->rate << 16) / rate;
-	n = 4 * ((ulong)vi->rate + samples * rate) / (ulong)vi->rate;
+	/* start converter if format changed */
+	if(rate != vi->rate || chans != vi->channels){
+		int pid, pfd[2];
+		char fmt[32];
+
+		rate = vi->rate;
+		chans = vi->channels;
+		sprintf(fmt, "f%dr%dc%d", sizeof(float)*8, rate, chans);
+
+		if(ifd >= 0)
+			close(ifd);
+		if(pipe(pfd) < 0){
+			fprintf(stderr, "Error creating pipe\n");
+			exit(1);
+		}
+		pid = fork();
+		if(pid < 0){
+			fprintf(stderr, "Error forking\n");
+			exit(1);
+		}
+		if(pid == 0){
+			dup2(pfd[1], 0);
+			close(pfd[1]);
+			close(pfd[0]);
+			execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, 0);
+			fprintf(stderr, "Error executing converter\n");
+			exit(1);
+		}
+		close(pfd[1]);
+		ifd = pfd[0];
+	}
+	n = sizeof(float) * chans * samples;
 	if(n > nbuf){
 		nbuf = n;
 		buf = realloc(buf, nbuf);
+		if(buf == NULL){
+			fprintf(stderr, "Error allocating memory\n");
+			exit(1);
+		}
 	}
-	if(vi->channels == 2)
-		resample(&c1, pcm[1], buf+2, 0, delta, samples);
-	p = resample(&c0, pcm[0], buf, vi->channels == 1, delta, samples);
-	fwrite(buf, p-buf, 1, stdout);
+	p = buf;
+	for(j=0; j < chans; j++){
+		s = pcm[j];
+		p = buf + j*sizeof(float);
+		for(i=0; i < samples; i++){
+			*((float*)p) = *s++;
+			p += chans*sizeof(float);
+		}
+	}
+	if(p > buf)
+		write(ifd, buf, p - buf);
 }
 
 int main(){
--- /dev/null
+++ b/sys/src/cmd/audio/pcmconv/mkfile
@@ -1,0 +1,8 @@
+</$objtype/mkfile
+<../config
+
+OFILES=pcmconv.$O
+
+TARG=pcmconv
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/cmd/audio/pcmconv/pcmconv.c
@@ -1,0 +1,328 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Desc Desc;
+typedef struct Chan Chan;
+
+struct Desc
+{
+	int	rate;
+	int	channels;
+	int	framesz;
+	int	bits;
+	int	fmt;
+};
+
+struct Chan
+{
+	ulong	phase;
+	int	last;
+};
+
+int*
+resample(Chan *c, int *src, int *dst, ulong delta, ulong count)
+{
+	int last, val;
+	ulong phase, pos;
+	vlong v;
+
+	if(delta == 0x10000){
+		/* same frequency */
+		memmove(dst, src, count*sizeof(int));
+		return dst + count;
+	}
+
+	val = 0;
+	last = c->last;
+	phase = c->phase;
+	pos = phase >> 16;
+	while(pos < count){
+		val = src[pos];
+		if(pos)
+			last = src[pos-1];
+
+		/* interpolate */
+		v = val;
+		v -= last;
+		v *= (phase & 0xFFFF);
+		v >>= 16;
+		v += last;
+
+		/* clipping */
+		if(v > 0x7fffffffLL)
+			v = 0x7fffffff;
+		else if(v < -0x80000000LL)
+			v = -0x80000000;
+
+		*dst++ = v;
+
+		phase += delta;
+		pos = phase >> 16;
+	}
+	c->last = val;
+	if(delta < 0x10000)
+		c->phase = phase & 0xFFFF;
+	else
+		c->phase = phase - (count << 16);
+	return dst;
+}
+
+void
+siconv(int *dst, uchar *src, int bits, int skip, int count)
+{
+	int i, v, s, b;
+
+	b = (bits+7)/8;
+	s = sizeof(int)*8-bits;
+	while(count--){
+		v = 0;
+		i = b;
+		switch(b){
+		case 4:
+			v = src[--i];
+		case 3:
+			v = (v<<8) | src[--i];
+		case 2:
+			v = (v<<8) | src[--i];
+		case 1:
+			v = (v<<8) | src[--i];
+		}
+		*dst++ = v << s;
+		src += skip;
+	}
+}
+
+void
+uiconv(int *dst, uchar *src, int bits, int skip, int count)
+{
+	int i, s, b;
+	uint v;
+
+	b = (bits+7)/8;
+	s = sizeof(uint)*8-bits;
+	while(count--){
+		v = 0;
+		i = b;
+		switch(b){
+		case 4:
+			v = src[--i];
+		case 3:
+			v = (v<<8) | src[--i];
+		case 2:
+			v = (v<<8) | src[--i];
+		case 1:
+			v = (v<<8) | src[--i];
+		}
+		*dst++ = (v << s) - (~0UL>>1);
+		src += skip;
+	}
+}
+
+void
+ficonv(int *dst, uchar *src, int bits, int skip, int count)
+{
+	while(count--){
+		if(bits == 32)
+			*dst++ = *((float*)src) * 2147483647.f;
+		else
+			*dst++ = *((double*)src) * 2147483647.f;
+		src += skip;
+	}
+}
+
+void
+soconv(int *src, uchar *dst, int bits, int skip, int count)
+{
+	int i, v, s, b;
+
+	b = (bits+7)/8;
+	s = sizeof(int)*8-bits;
+	while(count--){
+		v = *src++ >> s;
+		i = 0;
+		switch(b){
+		case 4:
+			dst[i++] = v, v >>= 8;
+		case 3:
+			dst[i++] = v, v >>= 8;
+		case 2:
+			dst[i++] = v, v >>= 8;
+		case 1:
+			dst[i] = v;
+		}
+		dst += skip;
+	}
+}
+
+void
+uoconv(int *src, uchar *dst, int bits, int skip, int count)
+{
+	int i, s, b;
+	uint v;
+
+	b = (bits+7)/8;
+	s = sizeof(uint)*8-bits;
+	while(count--){
+		v = ((~0UL>>1) + *src++) >> s;
+		i = 0;
+		switch(b){
+		case 4:
+			dst[i++] = v, v >>= 8;
+		case 3:
+			dst[i++] = v, v >>= 8;
+		case 2:
+			dst[i++] = v, v >>= 8;
+		case 1:
+			dst[i] = v;
+		}
+		dst += skip;
+	}
+}
+
+void
+foconv(int *src, uchar *dst, int bits, int skip, int count)
+{
+	while(count--){
+		if(bits == 32)
+			*((float*)dst) = *src++ / 2147483647.f;
+		else
+			*((double*)dst) = *src++ / 2147483647.f;
+		dst += skip;
+	}
+}
+
+Desc
+mkdesc(char *f)
+{
+	Desc d;
+	int c;
+	char *p;
+
+	memset(&d, 0, sizeof(d));
+	p = f;
+	while(c = *p++){
+		switch(c){
+		case 'r':
+			d.rate = strtol(p, &p, 10);
+			break;
+		case 'c':
+			d.channels = strtol(p, &p, 10);
+			break;
+		case 's':
+		case 'u':
+		case 'f':
+			d.fmt = c;
+			d.bits = strtol(p, &p, 10);
+			break;
+		default:
+			goto Bad;
+		}
+	}
+	if(d.rate <= 0)
+		goto Bad;
+	if(d.fmt == 'f'){
+		if(d.bits != 32 && d.bits != 64)
+			goto Bad;
+	} else if(d.bits <= 0 || d.bits > 32)
+		goto Bad;
+	d.framesz = ((d.bits+7)/8) * d.channels;
+	if(d.framesz <= 0)
+		goto Bad;
+	return d;
+Bad:
+	sysfatal("bad format: %s", f);
+	return d;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-i fmt] [-o fmt] [-l length]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	uchar ibuf[8*1024], *obuf;
+	int *out, *in;
+	Chan ch[8];
+	Desc i, o;
+	ulong delta;
+	int k, r, n, m, p;
+	vlong l;
+
+	void (*oconv)(int *, uchar *, int, int, int) = nil;
+	void (*iconv)(int *, uchar *, int, int, int) = nil;
+
+	o = mkdesc("s16c2r44100");
+	i = o;
+	l = -1LL;
+	ARGBEGIN {
+	case 'i':
+		i = mkdesc(EARGF(usage()));
+		break;
+	case 'o':
+		o = mkdesc(EARGF(usage()));
+		break;
+	case 'l':
+		l = atoll(EARGF(usage()));
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	switch(i.fmt){
+	case 's': iconv = siconv; break;
+	case 'u': iconv = uiconv; break;
+	case 'f': iconv = ficonv; break;
+	}
+
+	switch(o.fmt){
+	case 's': oconv = soconv; break;
+	case 'u': oconv = uoconv; break;
+	case 'f': oconv = foconv; break;
+	}
+
+	delta = ((uvlong)i.rate << 16) / o.rate;
+	memset(ch, 0, sizeof(ch));
+	n = (sizeof(ibuf)-i.framesz)/i.framesz;
+	r = n*i.framesz;
+	m = (i.rate + n*o.rate)/i.rate;
+	in = sbrk(sizeof(int) * n);
+	out = sbrk(sizeof(int) * m);
+	obuf = sbrk(o.framesz * m);
+	if(in == nil || out == nil || obuf == nil)
+		sysfatal("out of memory");
+
+	for(;;){
+		if(l >= 0 && l < r)
+			r = l;
+		n = read(0, ibuf, r);
+		if(n < 0)
+			sysfatal("read: %r");
+		if(n == 0)
+			break;
+		if(l > 0)
+			l -= n;
+		n /= i.framesz;
+		(*iconv)(in, ibuf, i.bits, i.framesz, n);
+		m = resample(&ch[0], in, out, delta, n) - out;
+		if(m < 1)
+			continue;
+		(*oconv)(out, obuf, o.bits, o.framesz, m);
+		if(i.channels == o.channels){
+			for(k=1; k<i.channels; k++){
+				(*iconv)(in, ibuf + k*((i.bits+7)/8), i.bits, i.framesz, n);
+				resample(&ch[k], in, out, delta, n);
+				(*oconv)(out, obuf + k*((o.bits+7)/8), o.bits, o.framesz, m);
+			}
+		} else {
+			for(k=1; k<o.channels; k++)
+				(*oconv)(out, obuf + k*((o.bits+7)/8), o.bits, o.framesz, m);
+		}
+		m *= o.framesz;
+		write(1, obuf, m);
+	}
+	exits(0);
+}
--- a/sys/src/cmd/audio/wavdec/mkfile
+++ b/sys/src/cmd/audio/wavdec/mkfile
@@ -6,5 +6,3 @@
 TARG=wavdec
 
 </sys/src/cmd/mkone
-
-CFLAGS=-FVp
--- a/sys/src/cmd/audio/wavdec/wavdec.c
+++ b/sys/src/cmd/audio/wavdec/wavdec.c
@@ -1,18 +1,7 @@
 #include <u.h>
 #include <libc.h>
 
-int debug = 0;
-int rate = 44100;
-
 typedef struct Wave Wave;
-typedef struct Chan Chan;
-
-struct Chan
-{
-	ulong	phase;
-	int	last;
-};
-
 struct Wave
 {
 	int	rate;
@@ -42,8 +31,8 @@
 	return buf[0] | buf[1]<<8 | buf[2]<<16 | buf[3]<<24;
 }
 
-uchar*
-getcc(uchar tag[4])
+char*
+getcc(char tag[4])
 {
 	if(readn(0, tag, 4) != 4)
 		sysfatal("read: %r");
@@ -50,98 +39,14 @@
 	return tag;
 }
 
-uchar*
-resample(Chan *c, int *src, uchar *dst, int mono, ulong delta, ulong count)
-{
-	int last, val, out;
-	ulong phase, pos;
-	vlong v;
-
-	last = c->last;
-	phase = c->phase;
-	pos = phase >> 16;
-	while(pos < count){
-		val = src[pos];
-		if(pos)
-			last = src[pos-1];
-
-		/* interpolate */
-		v = val;
-		v -= last;
-		v *= (phase & 0xFFFF);
-		out = (last + (v >> 16)) >> (sizeof(int)*8 - 16);
-
-		*dst++ = out;
-		*dst++ = out >> 8;
-		if(mono){
-			*dst++ = out;
-			*dst++ = out >> 8;
-		} else
-			dst += 2;
-		phase += delta;
-		pos = phase >> 16;
-	}
-	c->last = val;
-	if(delta < 0x10000)
-		c->phase = phase & 0xFFFF;
-	else
-		c->phase = phase - (count << 16);
-
-	return dst;
-}
-
 void
-conv(int *dst, uchar *src, int bits, int skip, int n)
+main(int, char *argv[])
 {
-	int i, v;
-
-	while(n--){
-		if(bits == 8)
-			v = (int)src[0] - 127;
-		else {
-			v = 0;
-			switch(i = bits/8){
-			case 4:
-				v = src[--i];
-			case 3:
-				v = (v<<8) | src[--i];
-			case 2:
-				v = (v<<8) | src[--i];
-			case 1:
-				v = (v<<8) | src[--i];
-			}
-		}
-		v <<= sizeof(int)*8-bits;
-		*dst++ = v;
-		src += skip;
-	}
-}
-
-void
-usage(void)
-{
-	fprint(2, "usage: %s [ -d ]\n", argv0);
-	exits("usage");
-}
-
-void
-main(int argc, char *argv[])
-{
-	uchar buf[8*1024], *out, *p;
-	int *samples;
-	Chan c0, c1;
+	char buf[8*1024], fmt[32];
+	ulong len, n;
 	Wave wav;
-	ulong delta, len;
-	int n, z;
 
-	ARGBEGIN {
-	case 'd':
-		debug++;
-		break;
-	default:
-		usage();
-	} ARGEND;
-
+	argv0 = argv[0];
 	if(memcmp(getcc(buf), "RIFF", 4) != 0)
 		sysfatal("no riff format");
 	get4();
@@ -177,39 +82,10 @@
 
 	if(wav.fmt != 1)
 		sysfatal("compressed format (0x%x) not supported", wav.fmt);
-	if(wav.framesz <= 0 || wav.bits <= 0 || wav.framesz != wav.channels*wav.bits/8)
-		sysfatal("bad format");
-	if(debug)
-		fprint(2, "wave: PCM %d Hz, %d ch, %d bits\n",
-			wav.rate, wav.channels, wav.bits);
-
-	delta = (wav.rate << 16) / rate;
-	n = sizeof(buf)/wav.framesz;
-	samples = malloc(sizeof(int) * n);
-	out = malloc(4 * ((wav.rate + n*rate)/wav.rate));
-	if(samples == nil || out == nil)
-		sysfatal("out of memory");
-
-	while(len % wav.framesz)
-		--len;
-	while(len){
-		if(len < sizeof(buf))
-			n = len;
-		else
-			n = sizeof(buf);
-		while(n % wav.framesz)
-			--n;
-		if(readn(0, buf, n) != n)
-			sysfatal("read: %r");
-		len -= n;
-		n /= wav.framesz;
-		if(wav.channels == 2){
-			conv(samples, buf + wav.bits/8, wav.bits, wav.framesz, n);
-			resample(&c1, samples, out+2, 0, delta, n);
-		}
-		conv(samples, buf, wav.bits, wav.framesz, n);
-		p = resample(&c0, samples, out, wav.channels == 1, delta, n);
-		write(1, out, p-out);
-	}
-	exits(0);
+	snprint(fmt, sizeof(fmt), "%c%dr%dc%d",
+		wav.bits == 8 ? 'u' : 's', wav.bits,
+		wav.rate,
+		wav.channels);
+	snprint(buf, sizeof(buf), "%lud", len);
+	execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, "-l", buf, 0);
 }
--