#include #include #include #include #include #include #include #include #include extern const char *__progname; static int argno = 0; static char *filename; static int hdrs = 0; static int data = 0; static int make = 0; typedef struct chunk CHUNK; struct chunk { off_t at; unsigned char type[4]; unsigned int size; } ; static int ffd; static unsigned int filesize; static int nchunks; static int achunks; static CHUNK **chunks; static unsigned int channels; static unsigned int samplerate; static unsigned int bytespersec; static unsigned int blockalign; static unsigned int bitspersample; static off_t data_at; static unsigned int data_size; static void usage(void) __attribute__((__noreturn__)); static void usage(void) { fprintf(stderr,"Usage: %s -h|-d filename\n",__progname); fprintf(stderr," %s -m hz chans bits datafile\n",__progname); fprintf(stderr,"\ -h -> print headers\n\ -d -> output data\n\ If just one option is given, output is to stdout; if both, header goes\n\ to stderr and data to stdout. At least one option must be given.\n\ -m -> make\n\ Constructs a WAV file on stdout, given sample rate (hz), channel count\n\ (chans), bits/sample (bits), and a filename containing the data.\n\ "); exit(1); } static int numarg(unsigned int *loc, char *arg) { unsigned long int lv; unsigned int iv; char *ep; lv = strtoul(arg,&ep,0); if (*ep || (ep == arg)) { fprintf(stderr,"%s: invalid numeric argument `%s'\n",__progname,arg); return(1); } iv = lv; if (iv != lv) { fprintf(stderr,"%s: out-of-range numeric argument `%s'\n",__progname,arg); return(1); } *loc = iv; return(0); } static int nonopt_arg(char *arg) { if ((hdrs+data+make) == 0) { fprintf(stderr,"%s: need -h, -d, or -m before any non-option arguments\n",__progname); return(1); } if (hdrs+data) { if (! filename) { filename = arg; } else { fprintf(stderr,"%s: extra argument `%s'\n",__progname,arg); return(1); } } else if (make) { switch (argno++) { case 0: return(numarg(&samplerate,arg)); break; case 1: return(numarg(&channels,arg)); break; case 2: return(numarg(&bitspersample,arg)); break; case 3: filename = arg; break; default: fprintf(stderr,"%s: extra argument `%s'\n",__progname,arg); return(1); break; } } else { abort(); } return(0); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 0) { skip --; continue; } if (**av != '-') { errs += nonopt_arg(*av); continue; } #if 0 if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #endif #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-h")) { hdrs = 1; if (make) { fprintf(stderr,"%s: can't make and analyze in the same run\n",__progname); errs ++; } continue; } if (!strcmp(*av,"-d")) { data = 1; if (make) { fprintf(stderr,"%s: can't make and analyze in the same run\n",__progname); errs ++; } continue; } if (!strcmp(*av,"-m")) { make = 1; if (hdrs || data) { fprintf(stderr,"%s: can't make and analyze in the same run\n",__progname); errs ++; } continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if ((hdrs+data+make) == 0) { errs ++; fprintf(stderr,"%s: must use at least one of -h, -d, or -m\n",__progname); } if (errs) usage(); } static int open_it(const char *fn) { int fd; fd = open(fn,O_RDONLY,0); if (fd < 0) { fprintf(stderr,"%s: %s: %s\n",__progname,fn,strerror(errno)); exit(1); } return(fd); } static void notwav(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void notwav(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: %s: not a WAV file: ",__progname,filename); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } static void badwav(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void badwav(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: %s: ",__progname,filename); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); exit(1); } static void mustread(off_t at, void *buf, int len) { int n; n = pread(ffd,buf,len,at); if (n < 0) { fprintf(stderr,"%s: %s: read %d@%lld: %s\n",__progname,filename,len,(long long int)at,strerror(errno)); exit(1); } if (n != len) { fprintf(stderr,"%s: %s: read %d@%lld: wanted %d, got %d\n",__progname,filename,len,(long long int)at,len,n); exit(1); } } static unsigned int get16(const void *data) { return( ((const unsigned char *)data)[0] + (((const unsigned char *)data)[1] * 0x100) ); } static unsigned int get32(const void *data) { return( ((const unsigned char *)data)[0] + (((const unsigned char *)data)[1] * 0x100) + (((const unsigned char *)data)[2] * 0x10000) + (((const unsigned char *)data)[3] * 0x1000000) ); } static void find_hdrs(void) { unsigned char rbuf[4]; off_t at; CHUNK *c; mustread(0,&rbuf[0],4); if (bcmp(&rbuf[0],"RIFF",4)) notwav("RIFF header magic number wrong"); mustread(4,&rbuf[0],4); filesize = 8 + get32(&rbuf[0]); mustread(8,&rbuf[0],4); if (bcmp(&rbuf[0],"WAVE",4)) notwav("RIFF type isn't WAVE"); nchunks = 0; achunks = 0; chunks = 0; at = 12; while (at < filesize) { c = malloc(sizeof(CHUNK)); if (nchunks >= achunks) chunks = realloc(chunks,(achunks=nchunks+8)*sizeof(*chunks)); chunks[nchunks++] = c; c->at = at; mustread(at,&c->type[0],4); mustread(at+4,&rbuf[0],4); c->size = get32(&rbuf[0]); if (at+8+c->size > filesize) notwav("RIFF container overruns file"); at += 8 + c->size + (c->size & 1); } } static void find_format(void) { int i; CHUNK *c; CHUNK *fc; unsigned char fmt[16]; fc = 0; for (i=nchunks-1;i>=0;i--) { c = chunks[i]; if (! bcmp(&c->type[0],"fmt ",4)) { if (fc) notwav("multiple `fmt ' chunks"); fc = c; } } if (! fc) notwav("no `fmt ' chunk"); if (fc->size < 16) notwav("`fmt ' chunk too small (%u)\n",fc->size); mustread(fc->at+8,&fmt[0],16); i = get16(&fmt[0]); switch (i) { case 1: break; default: badwav("compression type %d unimplemented",i); break; } channels = get16(&fmt[2]); samplerate = get32(&fmt[4]); bytespersec = get32(&fmt[8]); blockalign = get16(&fmt[12]); bitspersample = get16(&fmt[14]); } static void find_data(void) { int i; CHUNK *c; CHUNK *dc; dc = 0; for (i=nchunks-1;i>=0;i--) { c = chunks[i]; if (! bcmp(&c->type[0],"data",4)) { if (dc) notwav("multiple `data' chunks"); dc = c; } } if (! dc) notwav("no `data' chunk"); data_at = dc->at + 8; data_size = dc->size; } static void print_hdrs(FILE *to) { int i; CHUNK *c; int j; switch (channels) { case 1: fprintf(to,"mono"); break; case 2: fprintf(to,"stereo"); break; default: fprintf(to,"%u-channel",channels); break; } fprintf(to," %uHz %u-bit\n",samplerate,bitspersample); fprintf(to,"Chunks:\n"); for (i=0;iat, c->type[0],c->type[1],c->type[2],c->type[3] ); for (j=0;j<4;j++) { if ( ((c->type[j] >= 32) && (c->type[j] <= 126)) || (c->type[j] >= 160) ) { putchar(c->type[j]); } else { putchar('·'); } } printf("), size=%u\n",c->size); } } static void dump_data(FILE *to) { unsigned char data[8192]; int n; unsigned int left; off_t at; at = data_at; left = data_size; while (left > 0) { n = sizeof(data); if (n > left) n = left; mustread(at,&data[0],n); fwrite(&data[0],1,n,to); at += n; left -= n; } } static void stat_it(void) { struct stat stb; if (fstat(ffd,&stb) < 0) { fprintf(stderr,"%s: fstat %s: %s\n",__progname,filename,strerror(errno)); exit(1); } filesize = stb.st_size; if (filesize != stb.st_size) { fprintf(stderr,"%s: %s: too large\n",__progname,filename); exit(1); } } static void put16(unsigned int v) { putchar(v&0xff); putchar((v>>8)&0xff); } static void put32(unsigned int v) { putchar(v&0xff); putchar((v>>8)&0xff); putchar((v>>16)&0xff); putchar((v>>24)&0xff); } static void write_hdrs(void) { unsigned int totalsize; totalsize = 4 /* "WAVE" */ + 8+16 /* "fmt ", len, chunk */ + 8+filesize /* "data", len, data */ ; if (totalsize < filesize) { fprintf(stderr,"%s: %s: too large\n",__progname,filename); exit(1); } printf("RIFF"); put32(totalsize); printf("WAVEfmt "); put32(16); /* length of `fmt ' chunk */ put16(1); /* no compression */ put16(channels); put32(samplerate); put32((samplerate*channels*bitspersample)>>3); put16(((channels*bitspersample)+7)>>3); put16(bitspersample); printf("data"); put32(filesize); } static void write_data(void) { unsigned int off; unsigned int len; unsigned int left; void *mrv; int w; fflush(stdout); left = filesize; off = 0; while (left > 0) { len = left; if (len > 1048576) len = 1048576; mrv = mmap(0,len,PROT_READ,MAP_FILE|MAP_SHARED,ffd,off); if (mrv == MAP_FAILED) { fprintf(stderr,"%s: mmap %s (%u @ %u): %s\n",__progname,filename,len,off,strerror(errno)); exit(1); } w = write(1,mrv,len); if (w < 0) { fprintf(stderr,"%s: output write: %s\n",__progname,strerror(errno)); exit(1); } if (w != len) { fprintf(stderr,"%s: output write wanted %u, did %d\n",__progname,len,w); exit(1); } munmap(mrv,len); off += len; left -= len; } } int main(int, char **); int main(int ac, char **av) { handleargs(ac,av); if (hdrs+data) { ffd = open_it(filename); find_hdrs(); find_format(); find_data(); if (hdrs) print_hdrs(data?stderr:stdout); if (data) dump_data(stdout); } else if (make) { ffd = open_it(filename); stat_it(); write_hdrs(); write_data(); } else { abort(); } exit(0); }