/* * Copyright (c) 2006 Ariff Abdullah * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. * */ #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_DSP_DEV "/dev/dsp" #define DEFAULT_CHANNELS 2 #define DEFAULT_FORMAT AFMT_S16_LE #define DEFAULT_RATE 44100 #define RATE_MIN 4000 #define RATE_MAX 1102500 #define BUFSZ 65536 #define BLKSZ 512 #define AFMT_8BIT (AFMT_MU_LAW|AFMT_A_LAW|AFMT_U8|AFMT_S8) #define AFMT_16BIT (AFMT_S16_LE|AFMT_U16_LE|AFMT_S16_BE|AFMT_U16_BE) #define AFMT_24BIT (AFMT_S24_LE|AFMT_U24_LE|AFMT_S24_BE|AFMT_U24_BE) #define AFMT_32BIT (AFMT_S32_LE|AFMT_U32_LE|AFMT_S32_BE|AFMT_U32_BE) #define AFMT_SIGNED (AFMT_S8|AFMT_S16_LE|AFMT_S16_BE|AFMT_S24_LE|AFMT_S24_BE|\ AFMT_S32_LE|AFMT_S32_BE) #define AFMT_BIGENDIAN (AFMT_S16_BE|AFMT_U16_BE|AFMT_S24_BE|AFMT_U24_BE|\ AFMT_S32_BE|AFMT_U32_BE) #define VGAIN 10 #define read16(b) ((b)[0] | (b)[1] << 8) #define read32(b) ((b)[0] | (b)[1] << 8 | (b)[2] << 16 | (b)[3] << 24) static void usage(void) { fprintf(stderr, "%s [-b blocksize] [-c channels] [-d dsp_device] [-f format]" " [-q] [-r rate] [-s skip_bytes] [-v gain] [-w] [-S|-E] \n", getprogname()); exit(EX_USAGE); } static uint32_t fmt_to_bit(uint32_t fmt) { if (fmt & AFMT_8BIT) return 8; else if (fmt & AFMT_16BIT) return 16; else if (fmt & AFMT_24BIT) return 24; else if (fmt & AFMT_32BIT) return 32; else { fprintf(stderr, "Unknown format 0x%08x.\n", fmt); usage(); } return 0; } static uint32_t fmtstr_to_fmt(const char *fmtstr) { if (strcmp(fmtstr, "mulaw") == 0) return AFMT_MU_LAW; else if (strcmp(fmtstr, "alaw") == 0) return AFMT_A_LAW; else if (strcmp(fmtstr, "s8") == 0) return AFMT_S8; else if (strcmp(fmtstr, "u8") == 0) return AFMT_U8; else if (strcmp(fmtstr, "s16le") == 0) return AFMT_S16_LE; else if (strcmp(fmtstr, "u16le") == 0) return AFMT_U16_LE; else if (strcmp(fmtstr, "s16be") == 0) return AFMT_S16_BE; else if (strcmp(fmtstr, "u16be") == 0) return AFMT_U16_BE; else if (strcmp(fmtstr, "s24le") == 0) return AFMT_S24_LE; else if (strcmp(fmtstr, "u24le") == 0) return AFMT_U24_LE; else if (strcmp(fmtstr, "s24be") == 0) return AFMT_S24_BE; else if (strcmp(fmtstr, "u24be") == 0) return AFMT_U24_BE; else if (strcmp(fmtstr, "s32le") == 0) return AFMT_S32_LE; else if (strcmp(fmtstr, "u32le") == 0) return AFMT_U32_LE; else if (strcmp(fmtstr, "s32be") == 0) return AFMT_S32_BE; else if (strcmp(fmtstr, "u32be") == 0) return AFMT_U32_BE; else { fprintf(stderr, "Unknown format '%s'.\n", fmtstr); usage(); } return 0; } static void fmtstr_swap(char *fmtstr, int sign, int endian) { if (fmtstr[0] == 'a' || fmtstr[0] == 'm') return; if (sign) { if (fmtstr[0] == 's') fmtstr[0] = 'u'; else if (fmtstr[0] == 'u') fmtstr[0] = 's'; } if (fmtstr[1] == '8') return; if (endian) { if (fmtstr[3] == 'l') fmtstr[3] = 'b'; else if (fmtstr[3] == 'b') fmtstr[3] = 'l'; } } static void closeall(int fdin, int fdout, int realfile) { if (fdin > 0 && realfile) close(fdin); if (fdout > 0) close(fdout); } #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif static off_t wvlseek(int fd, off_t sz, off_t ofs, int wh) { off_t pos; pos = lseek(fd, ofs, wh); if (pos == -1) { ssize_t rsz; uint8_t junk[512]; /* warnx("lseek() failed, using read()"); */ switch (wh) { case SEEK_SET: if (ofs < sz) { warnx("unsupported seek offset: %lld < %lld", ofs, sz); return sz; } pos = sz; ofs -= pos; break; case SEEK_CUR: pos = sz; break; case SEEK_END: default: warnx("unsupported seek %d", wh); return sz; } while (ofs > 0) { rsz = read(fd, junk, MIN(sizeof(junk), ofs)); if (rsz == -1 || rsz == 0) return pos; ofs -= rsz; pos += rsz; } } return pos; } static int read_waveheader(int fd, uint32_t *rtotalrsz, uint32_t *rfmt, uint32_t *rbits, uint32_t *rrate, uint32_t *rchannels, uint32_t *rdatasz, char *fmtstr) { off_t pos; uint32_t sz, datasz, fmt, bits, rate, channels; ssize_t rsz, totalrsz; uint8_t buf[40]; int ret; ret = 0; if (rtotalrsz) totalrsz = *rtotalrsz; else totalrsz = 0; datasz = 0; fmt = 0; bits = 0; rate = 0; channels = 0; for (;;) { rsz = read(fd, buf, 8); if (rsz != 8) { warnx("read() size != 8 (%d)", rsz); ret = EX_IOERR; break; } totalrsz += rsz; sz = read32(buf + 4); if (strncmp(buf, "RIFF", 4) == 0) { rsz = read(fd, buf, 4); if (rsz != 4) { warnx("read() size != 4 (%d)", rsz); ret = EX_IOERR; break; } if (strncmp(buf, "WAVE", 4) != 0) { warnx("not a WAVE file"); ret = EX_DATAERR; break; } totalrsz += rsz; } else if (strncmp(buf, "fmt ", 4) == 0) { if (!(sz == 16 || sz == 18 || sz == 40)) { warnx("illegal header size=%d", sz); ret = EX_DATAERR; break; } rsz = read(fd, buf, sz); if (rsz != sz) { warnx("read() size != %d (%d)", sz, rsz); ret = EX_IOERR; break; } fmt = read16(buf); switch (fmt) { case 0x0001: /* PCM */ break; case 0x0006: /* A-Law */ break; case 0x0007: /* MU-Law */ break; default: warnx("unsupported format: 0x%04x", fmt); ret = EX_DATAERR; break; } channels = read16(buf + 2); rate = read32(buf + 4); bits = read16(buf + 14); totalrsz += rsz; } else if (strncmp(buf, "data", 4) == 0) { datasz = sz; break; } else { if (totalrsz == 8) { warnx("not a RIFF file"); ret = EX_DATAERR; break; } pos = wvlseek(fd, totalrsz, sz, SEEK_CUR); if (pos != (totalrsz + sz)) { warn("wvlseek() %lld != %d", pos, totalrsz + sz); ret = EX_IOERR; break; } totalrsz += sz; } } if (rtotalrsz) *rtotalrsz = totalrsz; if (ret == 0) { if (rchannels) *rchannels = channels; if (rrate) *rrate = rate; if (rbits) *rbits = bits; if (rdatasz) *rdatasz = datasz; if (rfmt) { if (fmt == 0x0006) { *rfmt = AFMT_A_LAW; if (fmtstr) strcpy(fmtstr, "alaw"); } else if (fmt == 0x0007) { *rfmt = AFMT_MU_LAW; if (fmtstr) strcpy(fmtstr, "mulaw"); } else { switch (bits) { case 32: *rfmt = AFMT_S32_LE; if (fmtstr) strcpy(fmtstr, "s32le"); break; case 24: *rfmt = AFMT_S24_LE; if (fmtstr) strcpy(fmtstr, "s24le"); break; case 16: *rfmt = AFMT_S16_LE; if (fmtstr) strcpy(fmtstr, "s16le"); break; case 8: *rfmt = AFMT_U8; if (fmtstr) strcpy(fmtstr, "u8"); break; default: break; } } } } return ret; } int main(int argc, char *argv[]) { struct stat st; uint32_t channels = DEFAULT_CHANNELS; uint32_t blk = BLKSZ, blksz; uint32_t format = DEFAULT_FORMAT; uint32_t rate = DEFAULT_RATE; uint32_t bits, bps, frag; uint8_t buf[BUFSZ]; ssize_t rsz, arsz, wsz, left, total = 0, fullsize = -1; int32_t sample; int i, sign, be, max, vgain = 0, swapsign = 0, swapendian = 0; int ch, fd, ffd, skip = 0, verbose = 1, realfile = 0, checkwave = 0; char *dsp = DEFAULT_DSP_DEV, *file = NULL, fmtstr[6]; strcpy(fmtstr, "s16le"); while ((ch = getopt(argc, argv, "b:c:d:f:hpqr:s:v:wSE")) != -1) { switch (ch) { case 'b': blk = atoi(optarg); break; case 'c': channels = atoi(optarg); if (!(channels == 1 || channels == 2)) { fprintf(stderr, "Channels option must be " "either 1 (mono) or " "2 (stereo).\n"); usage(); } break; case 'd': dsp = optarg; break; case 'f': format = fmtstr_to_fmt(optarg); strcpy(fmtstr, optarg); break; case 'q': verbose = 0; break; case 'r': rate = atoi(optarg); if (rate < RATE_MIN || rate > RATE_MAX) { fprintf(stderr, "Rate option must lie " "between %d to %d.\n", RATE_MIN, RATE_MAX); usage(); } break; case 's': skip = atoi(optarg); if (skip < 0) { fprintf(stderr, "Cannot seek to " "negative value.\n"); usage(); } break; case 'v': vgain = atoi(optarg); if (vgain < -VGAIN || vgain > VGAIN) { fprintf(stderr, "volume gain must lie in range" " -%d to %d\n", VGAIN, VGAIN); usage(); } break; case 'w': checkwave = 1; break; case 'S': swapsign = 1; break; case 'E': swapendian = 1; break; case 'h': case '?': default: usage(); break; } } argc -= optind; argv += optind; if (argc > 1) usage(); if (argc == 0 || strcmp(argv[0], "-") == 0) { ffd = fileno(stdin); file = ""; } else { ffd = open(argv[0], O_RDONLY); file = argv[0]; } if (ffd < 0) { warn("open()"); exit(EX_OSFILE); } if (fstat(ffd, &st) != 0) { warn("fstat()"); exit(EX_OSFILE); } realfile = S_ISREG(st.st_mode); if (realfile) fullsize = st.st_size; if (checkwave) { int ret; ret = read_waveheader(ffd, &total, &format, &bits, &rate, &channels, NULL, fmtstr); if (ret != 0) { closeall(ffd, 0, realfile); exit(ret); } } if (skip > 0) { off_t skip_pos; skip_pos = wvlseek(ffd, (off_t)total, (off_t)skip, SEEK_CUR); if (skip_pos != (total + skip)) { closeall(ffd, 0, realfile); exit(EX_IOERR); } total = skip_pos; } if (swapsign || swapendian) { fmtstr_swap(fmtstr, swapsign, swapendian); format = fmtstr_to_fmt(fmtstr); } fd = open(dsp, O_WRONLY); if (fd < 0) { warn("open()"); closeall(ffd, 0, realfile); exit(EX_OSFILE); } if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0) { warn("ioctl(SNDCTL_DSP_SETFMT)"); closeall(ffd, fd, realfile); exit(EX_DATAERR); } if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) { warn("ioctl(SNDCTL_DSP_CHANNELS)"); closeall(ffd, fd, realfile); exit(EX_DATAERR); } if (ioctl(fd, SNDCTL_DSP_SPEED, &rate) < 0) { warn("ioctl(SNDCTL_DSP_SPEED)"); closeall(ffd, fd, realfile); exit(EX_DATAERR); } bits = fmt_to_bit(format); bps = (bits / 8) * channels; blksz = (uint32_t)((rate / (float)DEFAULT_RATE) * blk * bps); if (blksz < bps) blksz = bps; else if (blksz > BUFSZ) blksz = BUFSZ; blksz -= blksz % bps; frag = 0; while (blksz >> frag) frag++; frag |= 0xffff0000; /*if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &frag) < 0) { warn("ioctl(SNDCTL_DSP_SETFRAGMENT)"); closeall(ffd, fd, realfile); exit(EX_DATAERR); }*/ if (verbose) fprintf(stderr, "Format : %s (0x%08x)\n" "File name : %s (%s)\n" "Sampling rate : %d hz\n" "Bits/Sample : %d bit / %d bps\n" "Blocksize : %d blocks / %d bytes\n" "Channels : %d (%s)\n" "Volume Gain : %d\n" "Size : [%11u|%11u] bytes\r", fmtstr, format, file, realfile ? "regular file" : "pipe", rate, fmt_to_bit(format), bps, blk, blksz, channels, channels == 1 ? "mono" : "stereo", vgain, total, fullsize); if (vgain > 0) vgain *= vgain; vgain += VGAIN; be = (format & AFMT_BIGENDIAN) ? 1 : 0; sign = (format & AFMT_SIGNED) ? 0x00 : 0x80; for (;;) { rsz = read(ffd, buf, blksz); if (rsz == 0) break; if (rsz < 0) { warn("read()"); closeall(ffd, fd, realfile); exit(EX_IOERR); } if (rsz % bps) { arsz = read(ffd, buf + rsz, bps - (rsz % bps)); if (arsz == (bps - (rsz % bps))) rsz += arsz; else { rsz += arsz; for (i = 0; i < (bps - (rsz % bps)); i++) { buf[rsz++] = sign; } } } if (vgain != VGAIN && !(format & (AFMT_A_LAW|AFMT_MU_LAW))) { max = rsz - (rsz % (bps / channels)); for (i = 0; i < max; i += (bps / channels)) { switch (bps / channels) { case 4: #if 0 if (be) { sample = (buf[i] ^ sign) << 16 | buf[i + 1] << 8 | buf[i + 2]; } else { sample = buf[i + 1] | buf[i + 2] << 8 | (buf[i + 3] ^ sign) << 16; } if (sample & 0x800000) sample ^= ~0xffffff; sample = (sample * vgain) / VGAIN; if (sample > 0x7fffff) sample = 0x7fffff; else if (sample < -0x800000) sample = -0x800000; if (be) { buf[i] = (sample >> 16) ^ sign; buf[i + 1] = sample >> 8; buf[i + 2] = sample; buf[i + 3] = 0; } else { buf[i] = 0; buf[i + 1] = sample; buf[i + 2] = sample >> 8; buf[i + 3] = (sample >> 16) ^ sign; } #else if (be) { sample = (buf[i] ^ sign) << 24 | buf[i + 1] << 16 | buf[i + 2] << 8 | buf[i + 3]; } else { sample = buf[i] | buf[i + 1] << 8 | buf[i + 2] << 16 | (buf[i + 3] ^ sign) << 24; } sample = ((sample >> 8) * vgain) / VGAIN; if (sample > 0x7fffff) sample = 0x7fffffff; else if (sample < -0x800000) sample = -0x80000000; else sample <<= 8; if (be) { buf[i] = (sample >> 24) ^ sign; buf[i + 1] = sample >> 16; buf[i + 2] = sample >> 8; buf[i + 3] = sample; } else { buf[i] = sample; buf[i + 1] = sample >> 8; buf[i + 2] = sample >> 16; buf[i + 3] = (sample >> 24) ^ sign; } #endif break; case 3: if (be) { sample = (buf[i] ^ sign) << 16 | buf[i + 1] << 8 | buf[i + 2]; } else { sample = buf[i] | buf[i + 1] << 8 | (buf[i + 2] ^ sign) << 16; } if (sample & 0x800000) sample ^= ~0xffffff; sample = (sample * vgain) / VGAIN; if (sample > 0x7fffff) sample = 0x7fffff; else if (sample < -0x800000) sample = -0x800000; if (be) { buf[i] = (sample >> 16) ^ sign; buf[i + 1] = sample >> 8; buf[i + 2] = sample; } else { buf[i] = sample; buf[i + 1] = sample >> 8; buf[i + 2] = (sample >> 16) ^ sign; } break; case 2: if (be) { sample = (buf[i] ^ sign) << 8 | buf[i + 1]; } else { sample = buf[i] | (buf[i + 1] ^ sign) << 8; } if (sample & 0x8000) sample ^= ~0xffff; sample = (sample * vgain) / VGAIN; if (sample > 0x7fff) sample = 0x7fff; else if (sample < -0x8000) sample = -0x8000; if (be) { buf[i] = (sample >> 8) ^ sign; buf[i + 1] = sample; } else { buf[i] = sample; buf[i + 1] = (sample >> 8) ^ sign; } break; case 1: sample = buf[i] ^ sign; if (sample & 0x80) sample ^= ~0xff; sample = (sample * vgain) / VGAIN; if (sample > 0x7f) sample = 0x7f; else if (sample < -0x80) sample = -0x80; buf[i] = sample ^ sign; break; } } } left = rsz; while (left > 0) { wsz = write(fd, buf + (rsz - left), left); if (wsz < 0) { warn("write()"); closeall(ffd, fd, realfile); exit(EX_IOERR); } left -= wsz; total += wsz; if (verbose) fprintf(stderr, "Size : [%11u|%11u] bytes\r", total, fullsize); } } fprintf(stderr, "\n"); closeall(ffd, fd, realfile); return 0; }