audio: replace µlawdec, add big endian and µlaw audio formats to pcmconv, µlaw in wav support

to support µ-law audio embedded in wav and big endian pcm
in sun audio files the µ-law and a-law and big endian integer
decoding was added to pcmconv. sundec now parses the sun
audio header supporting stereo now.
This commit is contained in:
cinap_lenrek 2012-12-14 04:38:45 +01:00
parent d7b7723c96
commit 5ceb834f0e
7 changed files with 297 additions and 417 deletions

View file

@ -1,6 +1,6 @@
.TH AUDIO 1
.SH NAME
mp3dec, mp3enc, oggdec, oggenc, flacdec, µlawdec, wavdec, pcmconv \- decode and encode audio files
mp3dec, mp3enc, oggdec, oggenc, flacdec, sundec, wavdec, pcmconv \- decode and encode audio files
.SH SYNOPSIS
.B audio/mp3dec
[
@ -13,7 +13,7 @@ mp3dec, mp3enc, oggdec, oggenc, flacdec, µlawdec, wavdec, pcmconv \- decode and
.br
.B audio/wavdec
.br
.B audio/µlawdec
.B audio/sundec
.PP
.B audio/oggenc
.br
@ -65,12 +65,12 @@ decodes MPEG audio (layer 1, 2 and 3). The
option enables debug output to standard error.
.I Oggdec,
.I flacdec,
.I µlawdec
.I sunwdec
and
.I wavdec
are like
.I mp3dec
but decode OGG Vorbis, FLAC lossless audio, Sun µlaw audio and PCM Wave.
but decode OGG Vorbis, FLAC lossless audio, Sun audio and RIFF wave.
.PP
The encoders read PCM on standard input and produce compressed audio
on standard output.
@ -226,17 +226,29 @@ is a concatinated string of the following parts:
.TP
.BI s #
sample format is little endian signed integer where
sample format is little-endian signed integer where
.I #
specifies the number of bits
.TP
.BI u #
unsigned little endian integer format
unsigned little-endian integer format
.TP
.BI S #
singed big-endian integer format
.TP
.BI U #
unsigned big-endian integer format
.TP
.BI f #
floating point format where
.I #
has to be 32 or 64 for single or double precisition
has to be 32 or 64 for single- or double-precisition
.TP
.B a8
8-bit a-law format
.TP
.B µ8
8-bit µ-law format
.TP
.BI c #
specifies the number of channels

View file

@ -1,7 +1,7 @@
</$objtype/mkfile
LIBS=libogg libvorbis libFLAC
PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec wavdec µlawdec
PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec wavdec sundec
#libs must be made first
DIRS=$LIBS $PROGS

View file

@ -9,8 +9,9 @@ struct Desc
int rate;
int channels;
int framesz;
int bits;
int fmt;
int abits; /* bits after input conversion */
int bits; /* bits in input stream per sample */
Rune fmt;
};
struct Chan
@ -235,6 +236,31 @@ siconv(int *dst, uchar *src, int bits, int skip, int count)
}
}
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 = 0;
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)
{
@ -261,6 +287,32 @@ uiconv(int *dst, uchar *src, int bits, int skip, int count)
}
}
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 = 0;
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)
{
@ -268,31 +320,72 @@ ficonv(int *dst, uchar *src, int bits, int skip, int count)
while(count--){
float f;
f = *((float*)src);
f = *((float*)src), src += skip;
if(f > 1.0)
*dst++ = 0x7fffffff;
else if(f < -1.0)
*dst++ = -0x80000000;
else
*dst++ = f*2147483647.f;
src += skip;
}
} else {
while(count--){
double d;
d = *((double*)src);
d = *((double*)src), src += skip;
if(d > 1.0)
*dst++ = 0x7fffffff;
else if(d < -1.0)
*dst++ = -0x80000000;
else
*dst++ = d*2147483647.f;
src += skip;
}
}
}
void
aiconv(int *dst, uchar *src, int, int skip, int count)
{
int t, seg;
uchar a;
while(count--){
a = *src, src += skip;
a ^= 0x55;
t = (a & 0xf) << 4;
seg = (a & 0x70) >> 4;
switch(seg){
case 0:
t += 8;
break;
case 1:
t += 0x108;
break;
default:
t += 0x108;
t <<= seg - 1;
}
t = (a & 0x80) ? t : -t;
*dst++ = t << (sizeof(int)*8 - 16);
}
}
void
µiconv(int *dst, uchar *src, int, int skip, int count)
{
int t;
uchar u;
while(count--){
u = *src, src += skip;
u = ~u;
t = ((u & 0xf) << 3) + 0x84;
t <<= (u & 0x70) >> 4;
t = u & 0x80 ? 0x84 - t: t - 0x84;
*dst++ = t << (sizeof(int)*8 - 16);
}
}
void
soconv(int *src, uchar *dst, int bits, int skip, int count)
{
@ -317,6 +410,30 @@ soconv(int *src, uchar *dst, int bits, int skip, int count)
}
}
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 = b;
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)
{
@ -342,6 +459,31 @@ uoconv(int *src, uchar *dst, int bits, int skip, int count)
}
}
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 = b;
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)
{
@ -362,24 +504,31 @@ Desc
mkdesc(char *f)
{
Desc d;
int c;
Rune r;
char *p;
memset(&d, 0, sizeof(d));
p = f;
while(c = *p++){
switch(c){
case 'r':
while(*p != 0){
p += chartorune(&r, p);
switch(r){
case L'r':
d.rate = strtol(p, &p, 10);
break;
case 'c':
case L'c':
d.channels = strtol(p, &p, 10);
break;
case 's':
case 'u':
case 'f':
d.fmt = c;
d.bits = strtol(p, &p, 10);
case L'm':
r = L'µ';
case L's':
case L'S':
case L'u':
case L'U':
case L'f':
case L'a':
case L'µ':
d.fmt = r;
d.bits = d.abits = strtol(p, &p, 10);
break;
default:
goto Bad;
@ -387,9 +536,14 @@ mkdesc(char *f)
}
if(d.rate <= 0)
goto Bad;
if(d.fmt == 'f'){
if(d.fmt == L'a' || d.fmt == L'µ'){
if(d.bits != 8)
goto Bad;
d.abits = sizeof(int)*8 - 16;
} else if(d.fmt == L'f'){
if(d.bits != 32 && d.bits != 64)
goto Bad;
d.abits = sizeof(int)*8;
} else if(d.bits <= 0 || d.bits > 32)
goto Bad;
d.framesz = ((d.bits+7)/8) * d.channels;
@ -472,21 +626,31 @@ main(int argc, char *argv[])
}
if(i.channels > nelem(ch))
sysfatal("too many input channels %d", i.channels);
sysfatal("too many input channels: %d", i.channels);
switch(i.fmt){
case 's': iconv = siconv; break;
case 'u': iconv = uiconv; break;
case 'f': iconv = ficonv; break;
case L's': iconv = siconv; break;
case L'S': iconv = Siconv; break;
case L'u': iconv = uiconv; break;
case L'U': iconv = Uiconv; break;
case L'f': iconv = ficonv; break;
case L'a': iconv = aiconv; break;
case L'µ': iconv = µiconv; break;
default:
sysfatal("unsupported input format: %C", i.fmt);
}
switch(o.fmt){
case 's': oconv = soconv; break;
case 'u': oconv = uoconv; break;
case 'f': oconv = foconv; break;
case L's': oconv = soconv; break;
case L'S': oconv = Soconv; break;
case L'u': oconv = uoconv; break;
case L'U': oconv = Uoconv; break;
case L'f': oconv = foconv; break;
default:
sysfatal("unsupported output format: %C", o.fmt);
}
if(i.fmt == 'f' || o.fmt == 'f')
if(i.fmt == L'f' || o.fmt == L'f')
setfcr(getfcr() & ~(FPINVAL|FPOVFL));
nin = (sizeof(ibuf)-i.framesz)/i.framesz;
@ -511,7 +675,7 @@ main(int argc, char *argv[])
l -= n;
n /= i.framesz;
(*iconv)(in, ibuf, i.bits, i.framesz, n);
dither(in, i.bits, o.bits, n);
dither(in, i.abits, o.abits, n);
m = resample(&ch[0], in, out, n) - out;
if(m < 1){
if(n == 0)
@ -521,7 +685,7 @@ main(int argc, char *argv[])
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);
dither(in, i.bits, o.bits, n);
dither(in, i.abits, o.abits, n);
resample(&ch[k], in, out, n);
if(m > 0)
(*oconv)(out, obuf + k*((o.bits+7)/8), o.bits, o.framesz, m);

View file

@ -1,8 +1,8 @@
</$objtype/mkfile
<../config
OFILES=µlawdec.$O
OFILES=sundec.$O
TARG=µlawdec
TARG=sundec
</sys/src/cmd/mkone

View file

@ -0,0 +1,62 @@
#include <u.h>
#include <libc.h>
ulong
get4(void)
{
uchar buf[4];
if(readn(0, buf, 4) != 4)
sysfatal("read: %r");
return buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3];
}
char *fmttab[] = {
[1] "µ8", /* 8-bit G.711 µ-law */
[2] "S8", /* 8-bit linear PCM */
[3] "S16", /* 16-bit linear PCM */
[4] "S24", /* 24-bit linear PCM */
[5] "S32", /* 32-bit linear PCM */
[6] "f32", /* 32-bit IEEE floating point */
[7] "f64", /* 64-bit IEEE floating point */
[27] "a8", /* 8-bit G.711 A-law */
};
void
main(int, char *argv[])
{
char buf[64], fmt[32];
ulong enc, rate, chans, len, off;
int n;
argv0 = argv[0];
if(get4() != 0x2e736e64UL)
sysfatal("no sun format");
off = get4();
if(off < 24)
sysfatal("bad data ofset");
off -= 24;
len = get4();
if(len == 0xffffffffUL)
len = 0;
enc = get4();
rate = get4();
chans = get4();
if(enc >= nelem(fmttab) || fmttab[enc] == 0)
sysfatal("unsupported encoding: %lux", enc);
snprint(fmt, sizeof(fmt), "%sc%ludr%lud", fmttab[enc], chans, rate);
while(off > 0){
n = sizeof(buf);
if(off < n)
n = off;
n = read(0, buf, n);
if(n <= 0)
sysfatal("read: %r");
off -= n;
}
if(len > 0){
snprint(buf, sizeof(buf), "%lud", len);
execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, "-l", buf, 0);
} else
execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, 0);
}

View file

@ -42,7 +42,7 @@ getcc(char tag[4])
void
main(int, char *argv[])
{
char buf[8*1024], fmt[32];
char buf[1024], fmt[32];
ulong len, n;
Wave wav;
@ -70,22 +70,32 @@ main(int, char *argv[])
len -= 2+2+4+4+2+2;
}
while(len > 0){
if(len < sizeof(buf))
n = sizeof(buf);
if(len < n)
n = len;
else
n = sizeof(buf);
if(readn(0, buf, n) != n)
n = read(0, buf, n);
if(n <= 0)
sysfatal("read: %r");
len -= n;
}
}
if(wav.fmt != 1)
sysfatal("compressed format (0x%x) not supported", wav.fmt);
snprint(fmt, sizeof(fmt), "%c%dr%dc%d",
wav.bits == 8 ? 'u' : 's', wav.bits,
wav.rate,
wav.channels);
switch(wav.fmt){
case 1:
snprint(fmt, sizeof(fmt), "%c%dr%dc%d", wav.bits == 8 ? 'u' : 's',
wav.bits, wav.rate, wav.channels);
break;
case 3:
snprint(fmt, sizeof(fmt), "f32r%dc%d", wav.rate, wav.channels);
break;
case 6:
snprint(fmt, sizeof(fmt), "a8r%dc%d", wav.rate, wav.channels);
break;
case 7:
snprint(fmt, sizeof(fmt), "µ8r%dc%d", wav.rate, wav.channels);
break;
default:
sysfatal("wave format (0x%lux) not supported", (ulong)wav.fmt);
}
snprint(buf, sizeof(buf), "%lud", len);
execl("/bin/audio/pcmconv", "pcmconv", "-i", fmt, "-l", buf, 0);
}

View file

@ -1,368 +0,0 @@
#include <u.h>
#include <libc.h>
#include <bio.h>
enum {
Qmask = 0xf, /* quantization mask */
Nsegs = 8, /* A-law segments */
Segshift = 4,
Segmask = 0x70,
};
static short segend[Nsegs] = {
0xff, 0x1ff, 0x3ff, 0x7ff,
0xfff, 0x1fff, 0x3fff, 0x7fff
};
/* copy from CCITT G.711 specifications */
static uchar u2a[128] = { /* μ- to A-law conversions */
1, 1, 2, 2, 3, 3, 4, 4,
5, 5, 6, 6, 7, 7, 8, 8,
9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24,
25, 27, 29, 31, 33, 34, 35, 36,
37, 38, 39, 40, 41, 42, 43, 44,
46, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62,
64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79,
81, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104,
105, 106, 107, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117, 118, 119, 120,
121, 122, 123, 124, 125, 126, 127, 128
};
static uchar a2u[128] = { /* A- to μ-law conversions */
1, 3, 5, 7, 9, 11, 13, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 32, 33, 33, 34, 34, 35, 35,
36, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 47, 48, 48, 49, 49,
50, 51, 52, 53, 54, 55, 56, 57,
58, 59, 60, 61, 62, 63, 64, 64,
65, 66, 67, 68, 69, 70, 71, 72,
73, 74, 75, 76, 77, 78, 79, 79,
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119,
120, 121, 122, 123, 124, 125, 126, 127
};
/* speed doesn't matter. table has 8 entires */
static int
search(int val, short *table, int size)
{
int i;
for (i = 0; i < size; i++) {
if (val <= *table++)
return i;
}
return size;
}
/*
* linear2alaw() - Convert a 16-bit linear PCM value to 8-bit A-law
*
* linear2alaw() accepts an 16-bit integer and encodes it as A-law data.
*
* Linear Input Code Compressed Code
* ------------------------ ---------------
* 0000000wxyza 000wxyz
* 0000001wxyza 001wxyz
* 000001wxyzab 010wxyz
* 00001wxyzabc 011wxyz
* 0001wxyzabcd 100wxyz
* 001wxyzabcde 101wxyz
* 01wxyzabcdef 110wxyz
* 1wxyzabcdefg 111wxyz
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
uchar
linear2alaw(int pcm) /* 2's complement (16-bit range) */
{
uchar aval;
int mask, seg;
if (pcm >= 0) {
mask = 0xd5; /* sign (7th) bit = 1 */
} else {
mask = 0x55; /* sign bit = 0 */
pcm = -pcm - 8;
}
/* Convert the scaled magnitude to segment number. */
seg = search(pcm, segend, 8);
/* Combine the sign, segment, and quantization bits. */
if (seg >= 8)
/* out of range, return maximum value. */
return 0x7f ^ mask;
else {
aval = seg << Segshift;
if (seg < 2)
aval |= pcm>>4 & Qmask;
else
aval |= pcm>>(seg + 3) & Qmask;
return aval ^ mask;
}
}
/*
* alaw2linear() - Convert an A-law value to 16-bit linear PCM
*
*/
int
alaw2linear(uchar a)
{
int t, seg;
a ^= 0x55;
t = (a & Qmask) << 4;
seg = (a & Segmask) >> Segshift;
switch (seg) {
case 0:
t += 8;
break;
case 1:
t += 0x108;
break;
default:
t += 0x108;
t <<= seg - 1;
}
return (a & 0x80) ? t : -t;
}
enum {
Bias = 0x84, /* Bias for linear code. */
};
/*
* linear2μlaw() - Convert a linear PCM value to μ-law
*
* In order to simplify the encoding process, the original linear magnitude
* is biased by adding 33 which shifts the encoding range from (0 - 8158) to
* (33 - 8191). The result can be seen in the following encoding table:
*
* Biased Linear Input Code Compressed Code
* ------------------------ ---------------
* 00000001wxyza 000wxyz
* 0000001wxyzab 001wxyz
* 000001wxyzabc 010wxyz
* 00001wxyzabcd 011wxyz
* 0001wxyzabcde 100wxyz
* 001wxyzabcdef 101wxyz
* 01wxyzabcdefg 110wxyz
* 1wxyzabcdefgh 111wxyz
*
* Each biased linear code has a leading 1 which identifies the segment
* number. The value of the segment number is equal to 7 minus the number
* of leading 0's. The quantization interval is directly available as the
* four bits wxyz. * The trailing bits (a - h) are ignored.
*
* Ordinarily the complement of the resulting code word is used for
* transmission, and so the code word is complemented before it is returned.
*
* For further information see John C. Bellamy's Digital Telephony, 1982,
* John Wiley & Sons, pps 98-111 and 472-476.
*/
uchar
linear2μlaw(int pcm)
{
int mask, seg;
uchar uval;
/* Get the sign and the magnitude of the value. */
if (pcm < 0) {
pcm = Bias - pcm;
mask = 0x7f;
} else {
pcm += Bias;
mask = 0xff;
}
/* Convert the scaled magnitude to segment number. */
seg = search(pcm, segend, 8);
/*
* Combine the sign, segment, quantization bits;
* and complement the code word.
*/
if (seg >= 8)
/* out of range, return maximum value. */
return 0x7f ^ mask;
else {
uval = seg<< 4 | ((pcm >> (seg + 3)) & 0xf);
return uval ^ mask;
}
}
/*
* μlaw2linear() - Convert a μ-law value to 16-bit linear PCM
*
* First, a biased linear code is derived from the code word. An unbiased
* output can then be obtained by subtracting 33 from the biased code.
*
* Note that this function expects to be passed the complement of the
* original code word. This is in keeping with ISDN conventions.
*/
int
μlaw2linear(uchar μ)
{
int t;
/* Complement to obtain normal μ-law value. */
μ = ~μ;
/*
* Extract and bias the quantization bits. Then
* shift up by the segment number and subtract out the bias.
*/
t = ((μ & Qmask) << 3) + Bias;
t <<= (μ & Segmask) >> Segshift;
return μ & 0x80? Bias - t: t - Bias;
}
/* A-law to μ-law conversion */
uchar
alaw2μlaw(uchar a)
{
// a &= 0xff;
if(a & 0x80)
return 0xff ^ a2u[a ^ 0xd5];
else
return 0x7f ^ a2u[a ^ 0x55];
}
/* μ-law to A-law conversion */
uchar
μlaw2alaw(uchar μ)
{
// μ &= 0xff;
if(μ & 0x80)
return 0xd5 ^ u2a[0xff ^ μ] - 1;
else
return 0x55 ^ u2a[0x7f ^ μ] - 1;
}
static void
setrate(int rate)
{
int fd;
fd = open("/dev/volume", OWRITE);
if(fd == -1){
fprint(2, "μlawdec: can't set rate %d: open: %r\n", rate);
return;
}
if(fprint(fd, "speed %d", rate) == -1)
fprint(2, "μlawdec: can't set rate %d: fprint: %r\n", rate);
else
fprint(2, "μlawdec: rate %d\n", rate);
close(fd);
}
enum {
Sig = 0,
Offset = 1,
Enc = 3,
Rate = 4,
Nchan = 5,
};
u32int
μlawword(uchar *buf, int w)
{
buf += 4*w;
return buf[0]<<24 | buf[1]<<16 | buf[2]<<8 | buf[3];
}
int
pcmconv(char *fmt)
{
int pid, pfd[2];
if(pipe(pfd) < 0)
return -1;
pid = fork();
if(pid < 0){
close(pfd[0]);
close(pfd[1]);
return -1;
}
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]);
return pfd[0];
}
void
main(void)
{
int fd, n;
uchar buf[5*4];
char fmt[32];
vlong off;
Biobuf i, o;
if(Binit(&i, 0, OREAD) == -1)
sysfatal("μlawdec: Binit: %r");
if(Bread(&i, buf, sizeof buf) != sizeof buf)
sysfatal("μlawdec: Bread: %r");
off = μlawword(buf, Offset);
if(off < 24)
sysfatal("μlawdec: bad offset: %lld", off);
if(μlawword(buf, Sig) != 0x2e736e64)
sysfatal("μlawdec: not .au file");
if(μlawword(buf, Enc) != 1)
sysfatal("μlawdec: not μlaw");
snprint(fmt, sizeof(fmt), "s16c1r%d", μlawword(buf, Rate));
fd = pcmconv(fmt);
if(fd < 0)
sysfatal("μlawdec: pcmconv: %r");
if(Binit(&o, fd, OWRITE) == -1)
sysfatal("μlawdec: Binit: %r");
while(off > 0){
n = sizeof(buf);
if(off < n)
n = off;
if(Bread(&i, buf, n) != n)
sysfatal("μlawdec: Bread: %r");
off -= n;
}
for(;;){
switch(Bread(&i, buf, 1)){
case 0:
goto done;
case -1:
sysfatal("μlawdec: Bread: %r");
}
n = μlaw2linear(buf[0]);
buf[0] = n&0xff;
buf[1] = (n&0xff00)>>8;
if(Bwrite(&o, buf, 2) != 2)
sysfatal("μlawdec: Bwrite: %r");
}
done:
Bterm(&o);
Bterm(&i);
}